def test_nr_diagram_IP_from_file(): # collect and save commands output to files commands_output = runner.cmd( fun="nr.call", arg=["cli", "show run", "show ip arp"], kwarg={ "FB": "ceos[12]", "tf": "ip_data", "tgt": "nrp1", "tgt_type": "glob", }, ) # build diagram out of files ret = runner.cmd( fun="nr.diagram", arg=["IP"], kwarg={ "FB": "ceos[12]", "outfile": "/tmp/pytest/test_nr_diagram_IP_from_file.graphml", "filegroup": "ip_data", "last": 1, "tgt": "nrp1", "tgt_type": "glob", }, ) pprint.pprint(ret) with open("/tmp/pytest/test_nr_diagram_IP_from_file.graphml") as f: diagram_content = f.read() root = ET.fromstring(diagram_content) assert "/tmp/pytest/test_nr_diagram_IP_from_file.graphml" in ret and "saved" in ret assert "Traceback" not in diagram_content assert root and len(root) > 1
def winrepo_genrepo(name, cli=True): ''' Execute the winrepo.genrepo runner Salter Example:: init.winrepo: winrepo_genrep CLI Examples: .. code-block:: bash salt-run salter.winrepo_genrepo ''' ret = {'name': name, 'result': True, 'changes': {}, 'comment': ''} pillar = salt.client.Caller().sminion.functions['pillar.data']() runner = salt.runner.RunnerClient(pillar['master']) log.info('generating windows package repository') ret['changes'] = {'winrepo': runner.cmd('winrepo.genrepo', [])} if not ret['changes']: ret['result'] = False ret['comment'] = 'winrepo returned empty !' log.error(ret['comment']) if cli: _end_func_from_cli(ret) return ret
def get_jobs_from_runner(self, outstanding_jids): """ Given a list of job_ids, return a dictionary of those job_ids that have completed and their results. Query the salt event bus via the jobs runner. jobs.list_job will show a job in progress, jobs.lookup_jid will return a job that has completed. returns a dictionary of job id: result """ # Can't use the runner because of https://github.com/saltstack/salt/issues/40671 runner = salt.runner.RunnerClient(__opts__) source = __opts__.get("ext_job_cache") if not source: source = __opts__.get("master_job_cache") results = {} for jid in outstanding_jids: # results[jid] = runner.cmd('jobs.lookup_jid', [jid]) if self.master_minion.returners["{}.get_jid".format(source)](jid): job_result = runner.cmd("jobs.list_job", [jid]) jid_result = job_result.get("Result", {}) jid_function = job_result.get("Function", {}) # emulate lookup_jid's return, which is just minion:return results[jid] = { "data": salt.utils.json.loads(salt.utils.json.dumps(jid_result)), "function": jid_function, } return results
def check_compliance(hostname, test_file): opts = salt.config.master_config('/etc/salt/master') runner = salt.runner.RunnerClient(opts) pillar = runner.cmd('pillar.show_pillar', [hostname]) config_file_object = { "hosts": [ { "device": pillar['proxy']['host'], "username": pillar['proxy']['username'], "passwd": pillar['proxy']['passwd'] } ], "tests": [ test_file ] } config_file = yaml.dump(config_file_object) js = SnapAdmin() snapcheck_output = js.snapcheck(config_file, "automatic_snapshot_from_saltstack") json_output = {} for item in snapcheck_output: json_output = { 'jsnapy_device_name': hostname, 'jsnapy_device_ip': item.device, 'jsnapy_result': item.result, 'jsnapy_nbr_passed': item.no_passed, 'jsnapy_nbr_failed': item.no_failed, 'jsnapy_test_file': test_file } if json_output['jsnapy_result'] == 'Failed': caller = salt.client.LocalClient() caller.cmd(hostname, 'event.send', ['jnpr/compliance/failed'], kwarg={'result': json_output}) return json_output
def test_nr_diagram_IP_save_data_path(): ret = runner.cmd( fun="nr.diagram", arg=["IP"], kwarg={ "FB": "ceos[12]", "outfile": "/tmp/pytest/test_nr_diagram_IP_save_data_path.graphml", "save_data": "/tmp/pytest/test_nr_diagram_IP_save_data_path/", "tgt": "nrp1", "tgt_type": "glob", }, ) pprint.pprint(ret) with open("/tmp/pytest/test_nr_diagram_IP_save_data_path.graphml") as f: diagram_content = f.read() root = ET.fromstring(diagram_content) assert "/tmp/pytest/test_nr_diagram_IP_save_data_path.graphml" in ret and "saved" in ret assert "Traceback" not in diagram_content assert root and len(root) > 1 assert "test_nr_diagram_IP_save_data_path" in os.listdir("/tmp/pytest/") assert "arista_eos" in os.listdir( "/tmp/pytest/test_nr_diagram_IP_save_data_path/") assert "ceos1.txt" in os.listdir( "/tmp/pytest/test_nr_diagram_IP_save_data_path/arista_eos/") assert "ceos2.txt" in os.listdir( "/tmp/pytest/test_nr_diagram_IP_save_data_path/arista_eos/") # test_nr_diagram_IP_save_data_path()
def getGrainsFromCache(g, __opts__): runner = salt.runner.Runner(__opts__) stdout_bak = sys.stdout with open(os.devnull, 'wb') as f: sys.stdout = f items = runner.cmd("cache.grains", [g['salt_id']]) sys.stdout = stdout_bak dc = "" env = "" version = "" if g['salt_id'] in items: if 'dc' in items[g['salt_id']]: dc = items[g['salt_id']]['dc'] else: dc = 'null' if 'env' in items[g['salt_id']]: env = items[g['salt_id']]['env'] else: env = 'null' if 'version' in items[g['salt_id']]: version = items[g['salt_id']]['version'] else: version = 'null' return dc, env, version
def minions_list(): runner = salt.runner.RunnerClient(opts) minions_list = runner.cmd('manage.up', []) return minions_list
def active_jobs(): runner = salt.runner.RunnerClient(opts) active_task_list = [] try: active_tasks_list = runner.cmd('jobs.active', []) except: raise exceptions.NotAvailable(_('Salt-master is not available')) active_task_list_converted = [] # bug is here for task_id in active_tasks_list.keys(): single_active_task = active_tasks_list[task_id] active_task_list_converted.append( ActiveTask( id=task_id, function=single_active_task['Function'] , user=single_active_task['User'], target_type=single_active_task['Target-type'], returned=single_active_task['Returned'], running_on=single_active_task['Running'], arguments=single_active_task['Arguments'] )) return active_task_list_converted
def genrepo(name, force=False, allow_empty=False): ''' Refresh the winrepo.p file of the repository (salt-run winrepo.genrepo) if force is True no checks will be made and the repository will be generated if allow_empty is True then the state will not return an error if there are 0 packages Example: .. code-block:: yaml winrepo: winrepo.genrepo ''' ret = {'name': name, 'result': True, 'changes': {}, 'comment': ''} master_config = salt.config.master_config(os.path.join(salt.syspaths.CONFIG_DIR, 'master')) win_repo = master_config['win_repo'] win_repo_mastercachefile = master_config['win_repo_mastercachefile'] # Check if the win_repo directory exists # if not search for a file with a newer mtime than the win_repo_mastercachefile file execute = False if not force: if not os.path.exists(win_repo): ret['result'] = False ret['comment'] = 'missing {0}'.format(win_repo) return ret elif not os.path.exists(win_repo_mastercachefile): execute = True ret['comment'] = 'missing {0}'.format(win_repo_mastercachefile) else: win_repo_mastercachefile_mtime = os.stat(win_repo_mastercachefile)[stat.ST_MTIME] for root, dirs, files in os.walk(win_repo): for name in itertools.chain(files, dirs): full_path = os.path.join(root, name) if os.stat(full_path)[stat.ST_MTIME] > win_repo_mastercachefile_mtime: ret['comment'] = 'mtime({0}) < mtime({1})'.format(win_repo_mastercachefile, full_path) execute = True break if __opts__['test']: ret['result'] = None return ret if not execute and not force: return ret runner = salt.runner.RunnerClient(master_config) runner_ret = runner.cmd('winrepo.genrepo', []) ret['changes'] = {'winrepo': runner_ret} if isinstance(runner_ret, dict) and runner_ret == {} and not allow_empty: os.remove(win_repo_mastercachefile) ret['result'] = False ret['comment'] = 'winrepo.genrepo returned empty' return ret
def target(mod='test.ping'): ''' run exec module on random minion ''' print 'execution module selected: {0}'.format(mod) # load master config opts = salt.config.master_config('/etc/salt/master') print "master config loaded" # load runner client with master config runner = salt.runner.RunnerClient(opts) print 'fetching minions' # run manage.up to fetch active minions list_of_minions = runner.cmd('manage.up', []) # let python randomly choose a minion random_target = choice(list_of_minions) print 'random minion chosen' # load the execution module client local = salt.client.LocalClient() print 'local client loaded' # run execution module on randomly targeted minion return local.cmd(random_target, mod)
def get_jobs_from_runner(outstanding_jids): """ Given a list of job_ids, return a dictionary of those job_ids that have completed and their results. Query the salt event bus via the jobs runner. jobs.list_job will show a job in progress, jobs.lookup_jid will return a job that has completed. returns a dictionary of job id: result """ # Can't use the runner because of https://github.com/saltstack/salt/issues/40671 runner = salt.runner.RunnerClient(__opts__) # log.debug("Getting job IDs {} will run via runner jobs.lookup_jid".format(outstanding_jids)) mm = salt.minion.MasterMinion(__opts__) source = __opts__.get('ext_job_cache') if not source: source = __opts__.get('master_job_cache') results = dict() for jid in outstanding_jids: # results[jid] = runner.cmd('jobs.lookup_jid', [jid]) if mm.returners['{}.get_jid'.format(source)](jid): jid_result = runner.cmd('jobs.list_job', [jid]).get('Result', {}) # emulate lookup_jid's return, which is just minion:return # pylint is tripping # pylint: disable=missing-whitespace-after-comma job_data = json.dumps( {key: val['return'] for key, val in jid_result.items()}) results[jid] = yaml.load(job_data) return results
def status(request): if request.method == "GET": #salt-run saltrun = request.GET.get('saltrun', None) if saltrun != None: runner = salt.runner.RunnerClient(master_opts) ret = runner.cmd(saltrun, []) return HttpResponse(json.dumps(ret), content_type="application/json")
def put_device_in_maintenance(dev): opts = salt.config.master_config('/etc/salt/master') caller = salt.client.Caller() local_minion_id = caller.cmd('grains.get', 'id') runner = salt.runner.RunnerClient(opts) pillar = runner.cmd('pillar.show_pillar', [local_minion_id]) url = pillar['northstar']['url'] maintenance_event_duration = pillar['northstar'][ 'maintenance_event_duration'] url_base = pillar['northstar']['url_base'] authuser = pillar['northstar']['authuser'] authpwd = pillar['northstar']['authpwd'] requests.packages.urllib3.disable_warnings(InsecureRequestWarning) headers = {'content-type': 'application/json'} data_to_get_token = { "grant_type": "password", "username": authuser, "password": authpwd } r = requests.post(url, data=json.dumps(data_to_get_token), auth=(authuser, authpwd), headers=headers, verify=False) headers = { 'Authorization': str('Bearer ' + r.json()['access_token']), 'Accept': 'application/json', 'Content-Type': 'application/json' } url = url_base + '1/topology/1/nodes' r = requests.get(url, headers=headers, verify=False) for i in r.json(): if i['hostName'] == dev: node_index = i['nodeIndex'] maintenance_url = url_base + '1/topology/1/maintenances' maintenance_data = { "topoObjectType": "maintenance", "topologyIndex": 1, "user": "******", "name": "event_" + dev, "startTime": datetime.now().isoformat(), "endTime": (datetime.now() + timedelta(minutes=maintenance_event_duration)).isoformat(), "elements": [{ "topoObjectType": "node", "index": node_index }] } m_res = requests.post(maintenance_url, data=json.dumps(maintenance_data), headers=headers, verify=False) return "done"
def display_minions(): jj = _load_environment() context = {} context['conductors'] = list(conductors.keys()) key_states = wheel.cmd('key.list_all') context['key_states'] = key_states connected_minions = runner.cmd('manage.up') context['connected_minions'] = connected_minions page_template = jj.get_template('minions.j2') return page_template.render(context)
def __call__(self, query): opts = salt.config.master_config(strongr.core.Core.config().clouddomain.OpenNebula.salt_config + '/master') opts['quiet'] = True runner = salt.runner.RunnerClient(opts) cache = strongr.core.gateways.Gateways.cache() if not cache.exists('clouddomain.jobs.running'): cache.set('clouddomain.jobs.running', runner.cmd('jobs.active'), 1) jobs = cache.get('clouddomain.jobs.running') if jobs is None: jobs = {} if query.jid not in jobs: # we only want to give status when the job is finished running result = runner.cmd('jobs.lookup_jid', [query.jid]) return result return None
def checkTableJobs(): opts = salt.config.master_config('/etc/salt/master') runner = salt.runner.RunnerClient(opts) ret = runner.cmd('jobs.active', print_event=False) activeJobs = set() for jobs in ret: for runs in ret[jobs]['Running']: for tables in runs: activeJobs.add(tables) return activeJobs
def test_nr_call_cli(): ret = runner.cmd( fun="nr.call", full_return=True, arg=["cli", "show clock"], kwarg={"FB": "ceos[12]"}, ) # pprint.pprint(ret) assert ret["success"] == True assert isinstance(ret["return"]["ceos1"][0]["result"], str) assert isinstance(ret["return"]["ceos2"][0]["result"], str)
def test_nr_call_cli_ret_struct_list(): ret = runner.cmd( fun="nr.call", arg=["cli", "show clock"], kwarg={"FB": "ceos[12]", "ret_struct": "list"}, ) pprint.pprint(ret) for i in ret: assert i["name"] == "show clock" assert "Traceback" not in i["result"] assert "minion_id" in i assert "host" in i
def test_nr_call_cli_non_existing_minions(): """ This call should return results for nrp1 only, as this is the minion that manages ceos1 and 2. """ ret = runner.cmd( fun="nr.call", arg=["cli", "show clock", "show hostname"], kwarg={"FB": "ceos*", "tgt": "foo", "tgt_type": "glob"} ) pprint.pprint(ret) assert "CommandExecutionError: No minions matched" in ret
def test_nr_call_cli_non_existing_hosts(): """ This call should return results for nrp1 only, as this is the minion that manages ceos1 and 2. """ ret = runner.cmd( fun="nr.call", arg=["cli", "show clock", "show hostname"], kwarg={"FB": "ceos-foo"}, ) pprint.pprint(ret) assert "CommandExecutionError: Hosts not found" in ret
def test_nr_call_cfg(): ret = runner.cmd( fun="nr.call", full_return=True, arg=["cfg", "logging host 1.2.3.4", "logging host 1.2.3.5"], kwarg={"plugin": "netmiko", "progress": False, "FB": "ceos[12]"}, ) # pprint.pprint(ret) assert ret["success"] == True assert ret["return"]["ceos1"][0]["failed"] == False assert ret["return"]["ceos2"][0]["failed"] == False assert isinstance(ret["return"]["ceos1"][0]["result"], str) assert isinstance(ret["return"]["ceos2"][0]["result"], str)
def test_nr_call_cli_multiple_commands_ret_struct_dict(): ret = runner.cmd( fun="nr.call", arg=["cli", "show clock", "show hostname"], kwarg={"FB": "ceos[12]", "ret_struct": "dictionary"}, ) pprint.pprint(ret) for hostname, results in ret.items(): assert len(results) == 2 for item in results: assert "Traceback" not in item["result"] assert "minion_id" in item assert item["name"] in ["show clock", "show hostname"]
def salt_status(): # __opts__ = salt.config.master_config('/etc/salt/master') # client = salt.client.LocalClient(__opts__['conf_file']) # minions = client.cmd('*', 'test.ping', timeout=__opts__['timeout']) # key = salt.key.Key(__opts__) # keys = key.list_keys() # ret = {} # ret['up'] = sorted(minions) # ret['down'] = sorted(set(keys['minions']) - set(minions)) # return ret['down'] opts = salt.config.master_config('/etc/salt/master') runner = salt.runner.RunnerClient(opts) ret = runner.cmd('manage.down', []) return ret
def test_nr_call_cli_table_brief_nrp1_only(): """ This call should return results for nrp1 only, as this is the minion that manages ceos1 and 2. """ ret = runner.cmd( fun="nr.call", arg=["cli", "show clock", "show hostname"], kwarg={"FB": "ceos[12]", "ret_struct": "dictionary", "table": "brief"}, ) pprint.pprint(ret) assert len(ret) == 1 assert isinstance(ret["nrp1"], str) assert "Traceback" not in ret["nrp1"]
def slave_list(): slaves = [] # under management nodes = runner.cmd('manage.status', [False]) for s in nodes.values(): for n in s: if n.startswith('mesoslave-'): slaves.append(n) # in cloud map with open(SLAVEYAML, 'r') as fh: d = yaml.load(fh) if not d: return slaves return list(set(d.get(SLAVEIMG, []) + slaves))
def getGrains(dc, env, __opts__): runner = salt.runner.Runner(__opts__) stdout_bak = sys.stdout with open(os.devnull, 'wb') as f: sys.stdout = f items = runner.cmd("cache.grains", "") sys.stdout = stdout_bak arr = [] if len(dc) > 0 and len(env) > 0: i = 0 for key in items: #matchObj = re.match( env, items[key]['env'], re.I) #if items[key]['dc'] == dc and matchObj: #print items[key]['dc'], items[key]['env'] if 'dc' in items[key]: itemdc = items[key]['dc'].lower() else: continue if 'env' in items[key]: itemenv = items[key]['env'].lower() else: continue dc = dc.lower() env = env.lower() if itemdc == dc and itemenv == env: arr.append(items[key]['id']) ++i elif len(dc) > 0: for key in items: if 'dc' in items[key]: itemdc = items[key]['dc'].lower() else: continue dc = dc.lower() if itemdc == dc: arr.append(items[key]['id']) else: for key in items: arr.append(items[key]['id']) return sorted(arr), items
def print_report(job_id): summary = runner.cmd('jobs.lookup_jid', arg=[job_id]) report = [] for minion in summary: # print(len(summary[minion])) if len(summary[minion]) > 20: if '-' in minion: s_minion = minion.split('-')[-2] + '-' + minion.split('-')[-1] elif '.' in minion: s_minion = minion.split('.')[0] else: s_minion = minion else: s_minion = minion if 'linux' in nodegroup: if len(summary[minion]) == 0: info = "System is updated" report.append((s_minion, good_minions[minion]['ip'], info)) else: patches = [] for patch in summary[minion]: patches.append(patch) report.append( (s_minion, good_minions[minion]['ip'], '\n'.join(patches))) elif 'win' in nodegroup: if isinstance(summary[minion], dict): patches = [] for patch in summary[minion].keys(): patches.append(summary[minion][patch]['Title']) report.append((s_minion, good_minions[minion]['ip'][0], '\n'.join(patches))) else: info = "System is updated" report.append((s_minion, good_minions[minion]['ip'][0], info)) #print(tabulate(report,headers=['minion id','info'],tablefmt="fancy_grid")) print colored( "======================================updateinfo===============================================", 'green') print( tabulate(sorted(report, key=itemgetter(2)), headers=['minion id', 'ip', 'update info'], tablefmt="grid")) print colored( "============================================================================================", 'green')
def test_nr_diagram_IP(): ret = runner.cmd( fun="nr.diagram", arg=["IP"], kwarg={ "FB": "ceos[12]", "outfile": "/tmp/pytest/test_nr_diagram_IP.graphml", "tgt": "nrp1", "tgt_type": "glob", }, ) pprint.pprint(ret) with open("/tmp/pytest/test_nr_diagram_IP.graphml") as f: diagram_content = f.read() root = ET.fromstring(diagram_content) assert "/tmp/pytest/test_nr_diagram_IP.graphml" in ret and "saved" in ret assert "Traceback" not in diagram_content assert root and len(root) > 1
def pytest_generate_tests(metafunc): if "_testinfra_host" in metafunc.fixturenames: if (metafunc.config.option.hosts == "*" and metafunc.config.option.connection == "salt"): import salt.runner opts = salt.config.master_config("/etc/salt/master") runner = salt.runner.RunnerClient(opts) minions_list = runner.cmd("manage.up", []) metafunc.config.option.hosts = ",".join(minions_list) if metafunc.config.option.hosts is not None: params = metafunc.config.option.hosts.split(",") ids = params elif hasattr(metafunc.module, "testinfra_hosts"): params = metafunc.module.testinfra_hosts ids = params else: params = [None] ids = ["local"] metafunc.parametrize( "_testinfra_host", params, ids=ids, scope="module")
def runner(fun, arg=None, timeout=5): ''' Execute a runner on the master and return the data from the runnr function CLI Example: .. code-block:: bash salt-ssh '*' publish.runner jobs.lookup_jid 20140916125524463507 ''' # Form args as list if not isinstance(arg, list): arg = [salt.utils.args.yamlify_arg(arg)] else: arg = [salt.utils.args.yamlify_arg(x) for x in arg] if len(arg) == 1 and arg[0] is None: arg = [] # Create and run the runner runner = salt.runner.RunnerClient(__opts__['__master_opts__']) return runner.cmd(fun, arg)
def test_runner_nr_cfg_fromdir_ret_struct_list(): ret = runner.cmd( fun="nr.cfg", arg=[], kwarg={ "fromdir": "salt://templates/per_host_cfg_runner_cfg_test/", "plugin": "netmiko", "progress": False, "interactive": False, "FB": "ceos*", "ret_struct": "list", }, ) pprint.pprint(ret) assert isinstance(ret, list) assert len(ret) == 2 for result in ret: assert "host" in result assert not result["exception"] assert result["failed"] == False assert "minion_id" in result
def test_runner_nr_cfg_fromdir(): ret = runner.cmd( fun="nr.cfg", arg=[], kwarg={ "fromdir": "salt://templates/per_host_cfg_runner_cfg_test/", "plugin": "netmiko", "progress": False, "interactive": False, "FB": "ceos*" }, ) pprint.pprint(ret) assert isinstance(ret, dict) assert len(ret) == 2 for host_name, results in ret.items(): assert len(results) == 1 for result in results: assert "ceos" in result["result"] assert not result["exception"] assert result["failed"] == False assert "minion_id" in result
def test_runner_nr_cfg_fromdir_ret_struct_list_table(): ret = runner.cmd( fun="nr.cfg", arg=[], kwarg={ "fromdir": "salt://templates/per_host_cfg_runner_cfg_test/", "plugin": "netmiko", "progress": False, "interactive": False, "FB": "ceos*", "ret_struct": "list", "table": "brief", "tgt": "nrp1", "tgt_type": "glob", }, ) pprint.pprint(ret) assert isinstance(ret, list) assert len(ret) == 1 for result in ret: assert "Traceback" not in result assert isinstance(result, str)
def test_nr_diagram_IP_save_data_true(): ret = runner.cmd( fun="nr.diagram", arg=["IP"], kwarg={ "FB": "ceos[12]", "outfile": "/tmp/pytest/test_nr_diagram_IP_save_data_true.graphml", "save_data": True, "tgt": "nrp1", "tgt_type": "glob", }, ) pprint.pprint(ret) with open("/tmp/pytest/test_nr_diagram_IP_save_data_true.graphml") as f: diagram_content = f.read() root = ET.fromstring(diagram_content) assert "/tmp/pytest/test_nr_diagram_IP_save_data_true.graphml" in ret and "saved" in ret assert "Traceback" not in diagram_content assert root and len(root) > 1 assert len( [i for i in os.listdir("/tmp/pytest/") if i.startswith("IP_Data")]) > 0
def runOrch(self, runner, tmpData, count): with open('/srv/salt/loot.sls', 'w+') as tmpFile: yaml.dump(tmpData, tmpFile, default_flow_style=False) ret = None trys = 0 while ret == None or trys < 3: try: ret = runner.cmd('state.orchestrate', ['loot'], print_event=False) except: ret = None trys = trys + 1 if ret == None or not self.isMinionConnected(ret, count): while table.checkTableJobs(): time.sleep(2) else: break if ret == None: self.writeOuput( "Salt Master disconnected, cannot complete installation") return False else: return self.checkSaltDeploy(ret, count)
def get_running_jobs_info(): master_opts = salt.config.client_config('/etc/salt/master') runner= salt.runner.RunnerClient(master_opts) resp = runner.cmd('jobs.active',print_event=False) return len(resp),resp
def start(token, aliases, valid_users, valid_commands, control=False, tag='salt/engines/slack'): ''' Listen to Slack events and forward them to Salt ''' if __opts__.get('__role') == 'master': fire_master = salt.utils.event.get_master_event( __opts__, __opts__['sock_dir']).fire_event else: fire_master = None def fire(tag, msg): if fire_master: fire_master(msg, tag) else: __salt__['event.send'](tag, msg) if not token: log.debug('Slack Bot token not found') return all_users = _get_users(token) sc = slackclient.SlackClient(token) slack_connect = sc.rtm_connect() runner_functions = sorted(salt.runner.Runner(__opts__).functions) if slack_connect: while True: _msg = sc.rtm_read() for _m in _msg: if 'type' in _m: if _m['type'] == 'message': # Find the channel where the message came from channel = sc.server.channels.find(_m['channel']) # Edited messages have text in message _text = _m.get('text', None) or _m.get('message', {}).get('text', None) if _text: if _text.startswith('!') and control: # Ensure the user is allowed to run commands if valid_users: log.debug('{0} {1}'.format(all_users, _m['user'])) if _m['user'] not in valid_users and all_users.get(_m['user'], None) not in valid_users: channel.send_message('{0} not authorized to run Salt commands'.format(all_users[_m['user']])) return # Trim the ! from the front # cmdline = _text[1:].split(' ', 1) cmdline = shlex.split(_text[1:]) cmd = cmdline[0] args = [] kwargs = {} # Ensure the command is allowed if valid_commands: if cmd not in valid_commands: channel.send_message('Using {0} is not allowed.'.format(cmd)) return if len(cmdline) > 1: for item in cmdline[1:]: if '=' in item: (key, value) = item.split('=', 1) kwargs[key] = value else: args.append(item) if 'target' not in kwargs: target = '*' else: target = kwargs['target'] del kwargs['target'] ret = {} if aliases and isinstance(aliases, dict) and cmd in aliases.keys(): salt_cmd = aliases[cmd].get('cmd') if 'type' in aliases[cmd]: if aliases[cmd]['type'] == 'runner': runner = salt.runner.RunnerClient(__opts__) ret = runner.cmd(salt_cmd, arg=args, kwarg=kwargs) else: local = salt.client.LocalClient() ret = local.cmd('{0}'.format(target), salt_cmd, args, kwargs) elif cmd in runner_functions: runner = salt.runner.RunnerClient(__opts__) ret = runner.cmd(cmd, arg=args, kwarg=kwargs) # default to trying to run as a client module. else: local = salt.client.LocalClient() ret = local.cmd('{0}'.format(target), cmd, args, kwargs) if ret: pp = pprint.PrettyPrinter(indent=4) return_text = pp.pformat(ret) # Slack messages need to be under 4000 characters. length = 4000 if len(return_text) >= length: channel.send_message(return_text[0:3999]) channel.send_message('Returned first 4k characters.') else: channel.send_message(return_text) else: # Fire event to event bus fire('{0}/{1}'.format(tag, _m['type']), _m) else: # Fire event to event bus fire('{0}/{1}'.format(tag, _m['type']), _m)
def start(token, room='salt', aliases=None, valid_users=None, valid_commands=None, control=False, trigger="!", tag='salt/engines/hipchat/incoming'): ''' Listen to Hipchat messages and forward them to Salt ''' target_room = None if __opts__.get('__role') == 'master': fire_master = salt.utils.event.get_master_event( __opts__, __opts__['sock_dir']).fire_event else: fire_master = None def fire(tag, msg): ''' fire event to salt bus ''' if fire_master: fire_master(msg, tag) else: __salt__['event.send'](tag, msg) def _eval_bot_mentions(all_messages, trigger): ''' yield partner message ''' for message in all_messages: message_text = message['message'] if message_text.startswith(trigger): fire(tag, message) text = message_text.replace(trigger, '').strip() yield message['from']['mention_name'], text if not token: raise UserWarning("Hipchat token not found") runner_functions = sorted(salt.runner.Runner(__opts__).functions) hipc = hypchat.HypChat(token) if not hipc: raise UserWarning("Unable to connect to hipchat") log.debug('Connected to Hipchat') all_rooms = hipc.rooms(max_results=1000)['items'] for a_room in all_rooms: if a_room['name'] == room: target_room = a_room if not target_room: log.debug("Unable to connect to room {0}".format(room)) # wait for a bit as to not burn through api calls time.sleep(30) raise UserWarning("Unable to connect to room {0}".format(room)) after_message_id = target_room.latest(maxResults=1)['items'][0]['id'] while True: try: new_messages = target_room.latest( not_before=after_message_id)['items'] except hypchat.requests.HttpServiceUnavailable: time.sleep(15) continue after_message_id = new_messages[-1]['id'] for partner, text in _eval_bot_mentions(new_messages[1:], trigger): # bot summoned by partner if not control: log.debug("Engine not configured for control") return # Ensure the user is allowed to run commands if valid_users: if partner not in valid_users: target_room.message('{0} not authorized to run Salt commands'.format(partner)) return args = [] kwargs = {} cmdline = salt.utils.shlex_split(text) cmd = cmdline[0] # Evaluate aliases if aliases and isinstance(aliases, dict) and cmd in aliases.keys(): cmdline = aliases[cmd].get('cmd') cmdline = salt.utils.shlex_split(cmdline) cmd = cmdline[0] # Parse args and kwargs if len(cmdline) > 1: for item in cmdline[1:]: if '=' in item: (key, value) = item.split('=', 1) kwargs[key] = value else: args.append(item) # Check for target. Otherwise assume * if 'target' not in kwargs: target = '*' else: target = kwargs['target'] del kwargs['target'] # Ensure the command is allowed if valid_commands: if cmd not in valid_commands: target_room.message('Using {0} is not allowed.'.format(cmd)) return ret = {} if cmd in runner_functions: runner = salt.runner.RunnerClient(__opts__) ret = runner.cmd(cmd, arg=args, kwarg=kwargs) # Default to trying to run as a client module. else: local = salt.client.LocalClient() ret = local.cmd('{0}'.format(target), cmd, args, kwargs) tmp_path_fn = salt.utils.files.mkstemp() with salt.utils.fopen(tmp_path_fn, 'w+') as fp_: fp_.write(json.dumps(ret, sort_keys=True, indent=4)) message_string = '@{0} Results for: {1} {2} {3} on {4}'.format(partner, cmd, args, kwargs, target) _publish_file(token, room, tmp_path_fn, message=message_string) salt.utils.safe_rm(tmp_path_fn) time.sleep(5)
def start(token, aliases=None, valid_users=None, valid_commands=None, control=False, trigger="!", tag='salt/engines/slack'): ''' Listen to Slack events and forward them to Salt ''' if __opts__.get('__role') == 'master': fire_master = salt.utils.event.get_master_event( __opts__, __opts__['sock_dir']).fire_event else: fire_master = None def fire(tag, msg): ''' Fire event to salt bus ''' if fire_master: fire_master(msg, tag) else: __salt__['event.send'](tag, msg) if not token: raise UserWarning('Slack Bot token not found') all_users = _get_users(token) sc = slackclient.SlackClient(token) slack_connect = sc.rtm_connect() log.debug('connected to slack') runner_functions = sorted(salt.runner.Runner(__opts__).functions) if slack_connect: while True: _msg = sc.rtm_read() for _m in _msg: if 'type' in _m: if _m['type'] == 'message': # Find the channel where the message came from channel = sc.server.channels.find(_m['channel']) # Edited messages have text in message _text = _m.get('text', None) or _m.get('message', {}).get('text', None) # Convert UTF to string _text = json.dumps(_text) _text = yaml.safe_load(_text) if _text: if _text.startswith(trigger) and control: # Ensure the user is allowed to run commands if valid_users: log.debug('{0} {1}'.format(all_users, _m['user'])) if _m['user'] not in valid_users and all_users.get(_m['user'], None) not in valid_users: channel.send_message('{0} not authorized to run Salt commands'.format(all_users[_m['user']])) return # Trim the ! from the front # cmdline = _text[1:].split(' ', 1) cmdline = salt.utils.shlex_split(_text[len(trigger):]) # Remove slack url parsing # Translate target=<http://host.domain.net|host.domain.net> # to target=host.domain.net cmdlist = [] for cmditem in cmdline: pattern = r'(?P<begin>.*)(<.*\|)(?P<url>.*)(>)(?P<remainder>.*)' m = re.match(pattern, cmditem) if m: origtext = m.group('begin') + m.group('url') + m.group('remainder') cmdlist.append(origtext) else: cmdlist.append(cmditem) cmdline = cmdlist cmd = cmdline[0] args = [] kwargs = {} # Ensure the command is allowed if valid_commands: if cmd not in valid_commands: channel.send_message('Using {0} is not allowed.'.format(cmd)) return # Evaluate aliases if aliases and isinstance(aliases, dict) and cmd in aliases.keys(): cmdline = aliases[cmd].get('cmd') cmdline = salt.utils.shlex_split(cmdline) cmd = cmdline[0] # Parse args and kwargs if len(cmdline) > 1: for item in cmdline[1:]: if '=' in item: (key, value) = item.split('=', 1) kwargs[key] = value else: args.append(item) # Check for target. Otherwise assume * if 'target' not in kwargs: target = '*' else: target = kwargs['target'] del kwargs['target'] ret = {} if cmd in runner_functions: runner = salt.runner.RunnerClient(__opts__) ret = runner.cmd(cmd, arg=args, kwarg=kwargs) # Default to trying to run as a client module. else: local = salt.client.LocalClient() ret = local.cmd('{0}'.format(target), cmd, args, kwargs) if ret: return_text = json.dumps(ret, sort_keys=True, indent=1) ts = time.time() st = datetime.datetime.fromtimestamp(ts).strftime('%Y%m%d%H%M%S%f') filename = 'salt-results-{0}.yaml'.format(st) result = sc.api_call( "files.upload", channels=_m['channel'], filename=filename, content=return_text ) # Handle unicode return result = json.dumps(result) result = yaml.safe_load(result) if 'ok' in result and result['ok'] is False: channel.send_message('Error: {0}'.format(result['error'])) else: # Fire event to event bus fire('{0}/{1}'.format(tag, _m['type']), _m) else: # Fire event to event bus fire('{0}/{1}'.format(tag, _m['type']), _m) else: raise UserWarning("Could not connect to slack")
def _salt(fun, *args, **kw): '''Execute a salt function on a specific minion Special kwargs: salt_target target to exec things on salt_timeout timeout for jobs salt_job_poll poll interval to wait for job finish result ''' try: poll = kw.pop('salt_job_poll') except KeyError: poll = 0.1 try: target = kw.pop('salt_target') except KeyError: target = None try: timeout = int(kw.pop('salt_timeout')) except (KeyError, ValueError): # try to has some low timeouts for very basic commands timeout = __FUN_TIMEOUT.get( fun, 900 # wait up to 15 minutes for the default timeout ) try: kwargs = kw.pop('kwargs') except KeyError: kwargs = {} if not target: infos = get_configured_provider() if not infos: return target = infos['target'] laps = time.time() cache = False if fun in __CACHED_FUNS: cache = True laps = laps // __CACHED_FUNS[fun] try: sargs = json.dumps(args) except TypeError: sargs = '' try: skw = json.dumps(kw) except TypeError: skw = '' try: skwargs = json.dumps(kwargs) except TypeError: skwargs = '' cache_key = (laps, target, fun, sargs, skw, skwargs) if not cache or (cache and (not cache_key in __CACHED_CALLS)): conn = _client() runner = _runner() rkwargs = kwargs.copy() rkwargs['timeout'] = timeout rkwargs.setdefault('expr_form', 'list') kwargs.setdefault('expr_form', 'list') jid = conn.cmd_async(tgt=target, fun=fun, arg=args, kwarg=kw, **rkwargs) cret = conn.cmd(tgt=target, fun='saltutil.find_job', arg=[jid], timeout=10, **kwargs) running = bool(cret.get(target, False)) endto = time.time() + timeout while running: rkwargs = { 'tgt': target, 'fun': 'saltutil.find_job', 'arg': [jid], 'timeout': 10 } cret = conn.cmd(**rkwargs) running = bool(cret.get(target, False)) if not running: break if running and (time.time() > endto): raise Exception('Timeout {0}s for {1} is elapsed'.format( timeout, pformat(rkwargs))) time.sleep(poll) # timeout for the master to return data about a specific job wait_for_res = float({ 'test.ping': '5', }.get(fun, '120')) while wait_for_res: wait_for_res -= 0.5 cret = runner.cmd( 'jobs.lookup_jid', [jid, {'__kwarg__': True, 'output': False}]) if target in cret: ret = cret[target] break # special case, some answers may be crafted # to handle the unresponsivness of a specific command # which is also meaningfull, eg a minion not yet provisionned if fun in ['test.ping'] and not wait_for_res: ret = { 'test.ping': False, }.get(fun, False) time.sleep(0.5) try: if 'is not available.' in ret: raise SaltCloudSystemExit( 'module/function {0} is not available'.format(fun)) except SaltCloudSystemExit: raise except TypeError: pass if cache: __CACHED_CALLS[cache_key] = ret elif cache and cache_key in __CACHED_CALLS: ret = __CACHED_CALLS[cache_key] return ret
def _get_up_minions(): up_minions = set(runner.cmd("manage.up", [])) if minion_set: return up_minions.intersection(minion_set) else: return up_minions
def _salt(fun, *args, **kw): '''Execute a salt function on a specific minion Special kwargs: salt_target target to exec things on salt_timeout timeout for jobs salt_job_poll poll interval to wait for job finish result ''' try: poll = kw.pop('salt_job_poll') except KeyError: poll = 0.1 try: target = kw.pop('salt_target') except KeyError: target = None try: timeout = int(kw.pop('salt_timeout')) except (KeyError, ValueError): # try to has some low timeouts for very basic commands timeout = __FUN_TIMEOUT.get( fun, 900 # wait up to 15 minutes for the default timeout ) try: kwargs = kw.pop('kwargs') except KeyError: kwargs = {} if not target: infos = get_configured_provider() if not infos: return target = infos['target'] laps = time.time() cache = False if fun in __CACHED_FUNS: cache = True laps = laps // __CACHED_FUNS[fun] try: sargs = json.dumps(args) except TypeError: sargs = '' try: skw = json.dumps(kw) except TypeError: skw = '' try: skwargs = json.dumps(kwargs) except TypeError: skwargs = '' cache_key = (laps, target, fun, sargs, skw, skwargs) if not cache or (cache and (cache_key not in __CACHED_CALLS)): conn = _client() runner = _runner() rkwargs = kwargs.copy() rkwargs['timeout'] = timeout rkwargs.setdefault('expr_form', 'list') kwargs.setdefault('expr_form', 'list') ping_retries = 0 # the target(s) have environ one minute to respond # we call 60 ping request, this prevent us # from blindly send commands to unmatched minions ping_max_retries = 60 ping = True # do not check ping... if we are pinguing if fun == 'test.ping': ping_retries = ping_max_retries + 1 # be sure that the executors are alive while ping_retries <= ping_max_retries: try: if ping_retries > 0: time.sleep(1) pings = conn.cmd(tgt=target, timeout=10, fun='test.ping') values = list(pings.values()) if not values: ping = False for v in values: if v is not True: ping = False if not ping: raise ValueError('Unreachable') break except Exception: ping = False ping_retries += 1 log.error('{0} unreachable, retrying'.format(target)) if not ping: raise SaltCloudSystemExit('Target {0} unreachable'.format(target)) jid = conn.cmd_async(tgt=target, fun=fun, arg=args, kwarg=kw, **rkwargs) cret = conn.cmd(tgt=target, fun='saltutil.find_job', arg=[jid], timeout=10, **kwargs) running = bool(cret.get(target, False)) endto = time.time() + timeout while running: rkwargs = { 'tgt': target, 'fun': 'saltutil.find_job', 'arg': [jid], 'timeout': 10 } cret = conn.cmd(**rkwargs) running = bool(cret.get(target, False)) if not running: break if running and (time.time() > endto): raise Exception('Timeout {0}s for {1} is elapsed'.format( timeout, pformat(rkwargs))) time.sleep(poll) # timeout for the master to return data about a specific job wait_for_res = float({ 'test.ping': '5', }.get(fun, '120')) while wait_for_res: wait_for_res -= 0.5 cret = runner.cmd( 'jobs.lookup_jid', [jid, {'__kwarg__': True}]) if target in cret: ret = cret[target] break # recent changes elif 'data' in cret and 'outputter' in cret: ret = cret['data'] break # special case, some answers may be crafted # to handle the unresponsivness of a specific command # which is also meaningful, e.g. a minion not yet provisioned if fun in ['test.ping'] and not wait_for_res: ret = { 'test.ping': False, }.get(fun, False) time.sleep(0.5) try: if 'is not available.' in ret: raise SaltCloudSystemExit( 'module/function {0} is not available'.format(fun)) except SaltCloudSystemExit: raise except TypeError: pass if cache: __CACHED_CALLS[cache_key] = ret elif cache and cache_key in __CACHED_CALLS: ret = __CACHED_CALLS[cache_key] return ret
def genrepo(name, force=False, allow_empty=False): ''' Refresh the winrepo.p file of the repository (salt-run winrepo.genrepo) If ``force`` is ``True`` no checks will be made and the repository will be generated if ``allow_empty`` is ``True`` then the state will not return an error if there are 0 packages, .. note:: This state only loads on minions that have the ``roles: salt-master`` grain set. Example: .. code-block:: yaml winrepo: winrepo.genrepo ''' ret = {'name': name, 'result': True, 'changes': {}, 'comment': ''} master_config = salt.config.master_config( os.path.join(salt.syspaths.CONFIG_DIR, 'master') ) if 'win_repo' in master_config: salt.utils.warn_until( 'Nitrogen', 'The \'win_repo\' config option is deprecated, please use ' '\'winrepo_dir\' instead.' ) winrepo_dir = master_config['win_repo'] else: winrepo_dir = master_config['winrepo_dir'] if 'win_repo_mastercachefile' in master_config: salt.utils.warn_until( 'Nitrogen', 'The \'win_repo_mastercachefile\' config option is deprecated, ' 'please use \'winrepo_cachefile\' instead.' ) winrepo_cachefile = master_config['win_repo_mastercachefile'] else: winrepo_cachefile = master_config['winrepo_cachefile'] # We're actually looking for the full path to the cachefile here, so # prepend the winrepo_dir winrepo_cachefile = os.path.join(winrepo_dir, winrepo_cachefile) # Check if the winrepo directory exists # if not search for a file with a newer mtime than the winrepo_cachefile file execute = False if not force: if not os.path.exists(winrepo_dir): ret['result'] = False ret['comment'] = '{0} is missing'.format(winrepo_dir) return ret elif not os.path.exists(winrepo_cachefile): execute = True ret['comment'] = '{0} is missing'.format(winrepo_cachefile) else: winrepo_cachefile_mtime = os.stat(winrepo_cachefile)[stat.ST_MTIME] for root, dirs, files in os.walk(winrepo_dir): for name in itertools.chain(files, dirs): full_path = os.path.join(root, name) if os.stat(full_path)[stat.ST_MTIME] > winrepo_cachefile_mtime: ret['comment'] = 'mtime({0}) < mtime({1})'.format(winrepo_cachefile, full_path) execute = True break if __opts__['test']: ret['result'] = None return ret if not execute and not force: return ret runner = salt.runner.RunnerClient(master_config) runner_ret = runner.cmd('winrepo.genrepo', []) ret['changes'] = {'winrepo': runner_ret} if isinstance(runner_ret, dict) and runner_ret == {} and not allow_empty: os.remove(winrepo_cachefile) ret['result'] = False ret['comment'] = 'winrepo.genrepo returned empty' return ret