def get_confirmation(self, title, prompt, default='No'): '''Display a confirmation dialog''' script = ''' on run argv tell application "Alfred 2" activate set alfredPath to (path to application "Alfred 2") set alfredIcon to path to resource "appicon.icns" in bundle ¬ (alfredPath as alias) try display dialog "{p}" with title "{t}" ¬ buttons {{"Yes", "No"}} default button "{d}" ¬ with icon alfredIcon set answer to (button returned of result) on error number -128 set answer to "No" end end tell end run'''.format(p=prompt, t=title, d=default) from subprocess import Popen, PIPE p = Popen(['osascript', '-'], stdin=PIPE, stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate(script) return stdout.decode('utf-8').rstrip('\n')
def run_script(self, script): '''Run an AppleScript, returning its output''' from subprocess import Popen, PIPE p = Popen(['osascript', '-ss', '-'], stdin=PIPE, stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate(script) return stdout.decode('utf-8'), stderr.decode('utf-8')
def run(self, mname, is_sys=False, is_demo=False): mode_sample = 1 # create first line in stella files for sec in self.Cfg.Sections: opts = self.Cfg.Options(sec) pattern = None if 'mode_sample' in opts: mode_sample = int(self.Cfg.get(sec, 'mode_sample')) if 'dir' in opts: path = os.path.join(self.Cfg.Root, self.Cfg.get(sec, 'dir')) else: path = self.Cfg.Root path = os.path.realpath(path) # print("{}: mode_sample= {}".format(sec, mode_sample)); if 'pattern' in opts: pattern = self.Cfg.get(sec, 'pattern') if 'file_add_line' in opts: fname = os.path.join(self.Cfg.Root, self.Cfg.get(sec, 'file_add_line')) self.add_line(fname, mname, pattern=pattern, is_demo=is_demo) logger.info( ' {}: Added new line to {} with pattern= {}'.format( sec, fname, pattern)) if 'sample' in opts: fname = os.path.join(self.Cfg.Root, self.Cfg.get(sec, 'sample')) extension = os.path.splitext(fname)[1] fout = os.path.join(os.path.dirname(fname), '{}{}'.format(mname, extension)) self.cp_sample(fname, fout, mode=mode_sample, is_demo=is_demo) if mode_sample == -1: cmd = './delstel.pl {};'.format(mname) return_code, stdout, stderr = self.eval_cmd(cmd, path, is_demo=is_demo) if is_sys and 'cmd' in opts: cmd = self.Cfg.get(sec, 'cmd') return_code, stdout, stderr = self.eval_cmd(cmd, path, is_demo=is_demo) # return_code, stdout, stderr = self.eval_cmd_log(cmd, path, is_demo=is_demo) if stdout: for line in stdout.decode('utf8').strip().split("\n"): logger.info('stdout: {}'.format(line)) if stderr: for line in stderr.decode('utf8').strip().split("\n"): logger.error('stderr: {}'.format(line))
def exec_or_die(cmd): print_cmd(shlex.join(cmd)) proc = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) stdout, stderr = proc.communicate() result = proc.wait() if result != 0: for line in stdout.decode('utf-8').split('\n'): print_error(line) print_error("Command returned %d" % result) exit(1)
def get_from_user(self, title, prompt, hidden=False, value=None, extra_buttons=None): '''Popup a dialog to request some piece of information. The main use for this function is to request information that you don't want showing up in Alfred's command history. ''' if value is None: value = '' buttons = ['Cancel', 'Ok'] if extra_buttons: if isinstance(extra_buttons, (list, tuple)): buttons = extra_buttons + buttons else: buttons.insert(0, extra_buttons) buttons = '{%s}' % ', '.join(['"%s"' % b for b in buttons]) hidden = 'with hidden answer' if hidden else '' script = ''' on run argv tell application "Alfred 2" activate set alfredPath to (path to application "Alfred 2") set alfredIcon to path to resource "appicon.icns" in bundle ¬ (alfredPath as alias) try display dialog "{p}:" with title "{t}" ¬ default answer "{v}" ¬ buttons {b} default button "Ok" with icon alfredIcon {h} set answer to (button returned of result) & "|" & ¬ (text returned of result) on error number -128 set answer to "Cancel|" end end tell end run'''.format(v=value, p=prompt, t=title, h=hidden, b=buttons) from subprocess import Popen, PIPE p = Popen(['osascript', '-'], stdin=PIPE, stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate(script) response = stdout.decode('utf-8').rstrip('\n') button, sep, value = response.partition('|') return (button, value)
def get_selection_from_user(cls, title, prompt, choices, default=None): '''Popup a dialog to let a user select a value from a list of choices. The main use for this function is to request information that you don't want showing up in Alfred's command history. ''' if default is None: default = '' if not isinstance(choices, (tuple, list)): choices = [choices] choices = '{{"{0}"}}'.format('","'.join(choices)) with open(os.path.join(BASE_DIR, 'get_selection.scpt')) as sfile: script = sfile.read().format(default=default, prompt=prompt, title=title, choices=choices) from subprocess import Popen, PIPE p = Popen(['osascript', '-'], stdin=PIPE, stdout=PIPE, stderr=PIPE) stdout, stderr = p.communicate(script) response = stdout.decode('utf-8').rstrip('\n') button, sep, value = response.partition('|') return (button, value)
def osascript_tell(app, script): p = Popen(['osascript'], stdin=PIPE, stdout=PIPE) stdout, stderr = p.communicate( ('tell application "{}"\n{}\nend tell'.format(app, script).encode('utf-8'))) return stdout.decode('utf-8').rstrip('\n')
def index(): """ Main WSGI application entry. """ path = normpath(abspath(dirname(__file__))) # Only POST is implemented if request.method != 'POST': abort(501) # Load config with open(join(path, 'config.json'), 'r') as cfg: config = loads(cfg.read()) hooks = config.get('hooks_path', join(path, 'hooks')) # Allow Github IPs only if config.get('github_ips_only', True): src_ip = ip_address(u'{}'.format( request.remote_addr) # Fix stupid ipaddress issue ) whitelist = requests.get('https://api.github.com/meta').json()['hooks'] for valid_ip in whitelist: if src_ip in ip_network(valid_ip): break else: abort(403) # Enforce secret secret = config.get('enforce_secret', '') if secret: # Only SHA1 is supported header_signature = request.headers.get('X-Hub-Signature') if header_signature is None: abort(403) sha_name, signature = header_signature.split('=') if sha_name != 'sha1': abort(501) # HMAC requires the key to be bytes, but data is string mac = hmac.new(str(secret), msg=request.data, digestmod=sha1) # Python prior to 2.7.7 does not have hmac.compare_digest if hexversion >= 0x020707F0: if not hmac.compare_digest(str(mac.hexdigest()), str(signature)): abort(403) else: # What compare_digest provides is protection against timing # attacks; we can live without this protection for a web-based # application if not str(mac.hexdigest()) == str(signature): abort(403) # Implement ping event = request.headers.get('X-GitHub-Event', 'ping') if event == 'ping': return dumps({'msg': 'pong'}) # Gather data try: payload = request.get_json() except: app.logger.error("Loading payload failed.") abort(400) # Determining the branch is tricky, as it only appears for certain event # types an at different levels branch = None try: # Case 1: a ref_type indicates the type of ref. # This true for create and delete events. if 'ref_type' in payload: if payload['ref_type'] == 'branch': branch = payload['ref'] # Case 2: a pull_request object is involved. This is pull_request and # pull_request_review_comment events. elif 'pull_request' in payload: # This is the TARGET branch for the pull-request, not the source # branch branch = payload['pull_request']['base']['ref'] elif event in ['push']: # Push events provide a full Git ref in 'ref' and not a 'ref_type'. branch = payload['ref'].split('/')[2] except KeyError: # If the payload structure isn't what we expect, we'll live without # the branch name pass # All current events have a repository, but some legacy events do not, # so let's be safe name = payload['repository']['name'] if 'repository' in payload else None meta = {'name': name, 'branch': branch, 'event': event} app.logger.info('Metadata: {}'.format(dumps(meta))) # Possible hooks scripts = [] if branch and name: scripts.append(join(hooks, '{event}-{name}-{branch}'.format(**meta))) if name: scripts.append(join(hooks, '{event}-{name}'.format(**meta))) scripts.append(join(hooks, '{event}'.format(**meta))) scripts.append(join(hooks, 'all')) # Check permissions scripts = [s for s in scripts if isfile(s) and access(s, X_OK)] if not scripts: app.logger.error("No scripts to run.") return '' # Save payload to temporal file osfd, tmpfile = mkstemp() with fdopen(osfd, 'w') as pf: pf.write(dumps(payload)) # If the process does not terminate after timeout seconds, # an empty response will be sent without output, while the # script finishes in the background. execution_timeout = config.get('execution_timeout', 10) # Run scripts ran = {} for s in scripts: proc = Popen([s, tmpfile, event], env={'PATH': getenv('PATH')}, stdout=PIPE, stderr=PIPE) try: stdout, stderr = proc.communicate(timeout=execution_timeout) ran[basename(s)] = { 'returncode': proc.returncode, 'stdout': stdout.decode('utf-8'), 'stderr': stderr.decode('utf-8'), } # Log errors if a hook failed if proc.returncode != 0: app.logger.error('{} : {} \n{}'.format(s, proc.returncode, stderr)) except TimeoutExpired: ran[basename(s)] = { 'returncode': None, 'stdout': "Script took to long to finish. Will finish in background.", 'stderr': None, } # Remove temporal file remove(tmpfile) info = config.get('return_scripts_info', False) if not info: return '' output = jsonify(ran) app.logger.info(output) return output
def perform_inventory(args): Variables = dict() # Parse command line arguments optlist = [] command_line_length = len(args) argIndex = 0 inArgument = False currentArgument = "" arg = "" while argIndex < command_line_length: arg = args[argIndex] if argIndex == 0: # skip the program name argIndex += 1 continue if inArgument: Variables[currentArgument] = arg inArgument = False else: if arg[0:2] == "--": inArgument = True currentArgument = arg[2:].lower() else: # The rest are not options args = args[argIndex:] break argIndex += 1 if inArgument: Variables[currentArgument] = arg AcceptableOptions = ["inmof", "outxml", "help"] if "help" in Variables: usage() exit(0) optionsValid = True for arg in Variables.keys(): if arg.lower() not in AcceptableOptions: optionsValid = False exitWithError("Error: %s is not a valid option" % arg) if optionsValid == False: usage() exit(1) dsc_sysconfdir = join(helperlib.CONFIG_SYSCONFDIR, helperlib.CONFIG_SYSCONFDIR_DSC) dsc_reportdir = join(dsc_sysconfdir, 'InventoryReports') omicli_path = join(helperlib.CONFIG_BINDIR, 'omicli') dsc_host_base_path = helperlib.DSC_HOST_BASE_PATH dsc_host_path = join(dsc_host_base_path, 'bin/dsc_host') dsc_host_output_path = join(dsc_host_base_path, 'output') dsc_host_lock_path = join(dsc_host_base_path, 'dsc_host_lock') dsc_host_switch_path = join(dsc_host_base_path, 'dsc_host_ready') dsc_configuration_path = join(dsc_sysconfdir, 'configuration') temp_report_path = join(dsc_configuration_path, 'Inventory.xml.temp') report_path = join(dsc_configuration_path, 'Inventory.xml') inventorylock_path = join(dsc_sysconfdir, 'inventory_lock') if ("omsconfig" in helperlib.DSC_SCRIPT_PATH): write_omsconfig_host_switch_event(pathToCurrentScript, isfile(dsc_host_switch_path)) if ("omsconfig" in helperlib.DSC_SCRIPT_PATH) and (isfile(dsc_host_switch_path)): use_omsconfig_host = True else: use_omsconfig_host = False if "outxml" in Variables: report_path = Variables["outxml"] parameters = [] if use_omsconfig_host: parameters.append(dsc_host_path) parameters.append(dsc_host_output_path) if "inmof" in Variables: parameters.append("PerformInventoryOOB") parameters.append(Variables["inmof"]) else: parameters.append("PerformInventory") else: parameters.append(omicli_path) parameters.append("iv") parameters.append(helperlib.DSC_NAMESPACE) parameters.append("{") parameters.append("MSFT_DSCLocalConfigurationManager") parameters.append("}") if "inmof" in Variables: parameters.append("PerformInventoryOOB") parameters.append("{") parameters.append("InventoryMOFPath") parameters.append(Variables["inmof"]) parameters.append("}") else: parameters.append("PerformInventory") # Ensure inventory lock file permission is set correctly before opening operationStatusUtility.ensure_file_permissions(inventorylock_path, '644') # Open the inventory lock file. This also creates a file if it does not exist. inventorylock_filehandle = open(inventorylock_path, 'w') printVerboseMessage("Opened the inventory lock file at the path '" + inventorylock_path + "'") retval = 0 inventorylock_acquired = True dschostlock_filehandle = None inmof_file = '' if "inmof" in Variables: inmof_file = Variables["inmof"] try: # Acquire inventory file lock try: flock(inventorylock_filehandle, LOCK_EX | LOCK_NB) write_omsconfig_host_log('Inventory lock is acquired by : ' + inmof_file, pathToCurrentScript) except IOError: inventorylock_acquired = False write_omsconfig_host_log('Failed to acquire inventory lock.', pathToCurrentScript, 'WARNING') if inventorylock_acquired: dschostlock_acquired = False if use_omsconfig_host: if isfile(dsc_host_lock_path): stop_old_host_instances(dsc_host_lock_path) # Open the dsc host lock file. This also creates a file if it does not exist. dschostlock_filehandle = open(dsc_host_lock_path, 'w') printVerboseMessage("Opened the dsc host lock file at the path '" + dsc_host_lock_path + "'") # Acquire dsc host file lock for retry in range(10): try: flock(dschostlock_filehandle, LOCK_EX | LOCK_NB) dschostlock_acquired = True write_omsconfig_host_log('dsc_host lock file is acquired by : ' + inmof_file, pathToCurrentScript) break except IOError: write_omsconfig_host_log('dsc_host lock file not acquired. retry (#' + str(retry) + ') after 60 seconds...', pathToCurrentScript) sleep(60) else: write_omsconfig_host_log('dsc_host lock file does not exist. Skipping this operation until next consistency hits.', pathToCurrentScript, 'WARNING') if dschostlock_acquired or (not use_omsconfig_host): try: system("rm -f " + dsc_reportdir + "/*") process = Popen(parameters, stdout = PIPE, stderr = PIPE) stdout, stderr = process.communicate() retval = process.returncode stdout = stdout.decode() if isinstance(stdout, bytes) else stdout stderr = stderr.decode() if isinstance(stderr, bytes) else stderr printVerboseMessage(stdout) if (retval > 0): write_omsconfig_host_log('dsc_host failed with code = ' + str(retval), pathToCurrentScript) exit(retval) # Combine reports together reportFiles = listdir(dsc_reportdir) final_xml_report = '<INSTANCE CLASSNAME="Inventory"><PROPERTY.ARRAY NAME="Instances" TYPE="string" EmbeddedObject="object"><VALUE.ARRAY>' values = [] for reportFileName in reportFiles: reportFilePath = join(dsc_reportdir, reportFileName) if not isfile(reportFilePath): continue report = parse(reportFilePath) for valueNode in report.getElementsByTagName('VALUE'): values.append(valueNode.toxml()) final_xml_report = final_xml_report + "".join(values) + "</VALUE.ARRAY></PROPERTY.ARRAY></INSTANCE>" # Ensure temporary inventory report file permission is set correctly before opening operationStatusUtility.ensure_file_permissions(temp_report_path, '644') tempReportFileHandle = open(temp_report_path, 'w') try: tempReportFileHandle.write(final_xml_report) finally: if (tempReportFileHandle): tempReportFileHandle.close() # Ensure temporary inventory report file permission is set correctly after opening operationStatusUtility.ensure_file_permissions(temp_report_path, '644') system("rm -f " + dsc_reportdir + "/*") move(temp_report_path, report_path) # Ensure inventory report file permission is set correctly operationStatusUtility.ensure_file_permissions(report_path, '644') finally: if (dschostlock_filehandle): # Release inventory file lock flock(inventorylock_filehandle, LOCK_UN) # Release dsc host file lock if isfile(dsc_host_lock_path) and use_omsconfig_host: try: flock(dschostlock_filehandle, LOCK_UN) except: pass finally: if (inventorylock_filehandle): # Close inventory lock file handle inventorylock_filehandle.close() if (dschostlock_filehandle): # Close dsc host lock file handle if use_omsconfig_host: try: dschostlock_filehandle.close() except: pass # Ensure inventory lock file permission is set correctly after opening operationStatusUtility.ensure_file_permissions(inventorylock_path, '644') # Ensure dsc host lock file permission is set correctly after opening if use_omsconfig_host: operationStatusUtility.ensure_file_permissions(dsc_host_lock_path, '644') exit(retval)
def osascript_tell(app, script): p = Popen(['osascript'], stdin=PIPE, stdout=PIPE) stdout, stderr = p.communicate( ('tell application "{}"\n{}\nend tell'.format(app, script) .encode('utf-8'))) return stdout.decode('utf-8').rstrip('\n')
def index(): """ Main WSGI application entry. """ path = normpath(abspath(dirname(__file__))) # Only POST is implemented - same effect as removing 'GET' in methods above if request.method != 'POST': logging.warning("We got a {} request, this isn't supported".format( request.method)) abort(405) # Load config if isfile(join(path, 'config.json')): with open(join(path, 'config.json'), 'r') as cfg: config = loads(cfg.read()) else: # abort(503, 'Configuration file config.json is missing.') config = { "github_ips_only": False, "enforce_secret": "", "return_scripts_info": False, } hooks = config.get('hooks_path', join(path, 'hooks')) logging.info("Config loaded, handling request") # Allow Github IPs only if config.get('github_ips_only', True): src_ip = ip_address(u'{}'.format( request.access_route[0]) # Fix stupid ipaddress issue ) whitelist = requests.get('https://api.github.com/meta').json()['hooks'] for valid_ip in whitelist: if src_ip in ip_network(valid_ip): break else: logging.warning("We got a request from unauthorized IP: %s", src_ip) abort(403) # Enforce secret secret = config.get('enforce_secret', '') if secret: # Only SHA1 is supported header_signature = request.headers.get('X-Hub-Signature') if header_signature is None: logging.warning("No signature found when expecting one") abort(403) sha_name, signature = header_signature.split('=') if sha_name != 'sha1': logging.warning("Unsupported signature mech: %s", sha_name) abort(501) # HMAC requires the key to be bytes, but data is string # Convert key to ascii in unicode environment # https://stackoverflow.com/questions/33455463/python-3-sign-a-message-with-key-sha512 mac = hmac.new(bytearray(secret, "ASCII"), msg=request.data, digestmod=sha1) if not constant_time_compare(str(mac.hexdigest()), str(signature)): abort(403) # Implement ping event = request.headers.get('X-GitHub-Event', 'ping') if event == 'ping': logging.warning("Ping Pong") return dumps({'msg': 'pong'}) # Gather data try: payload = request.get_json() except Exception: logging.warning('Request parsing failed with exception %s', Exception) abort(400) # Determining the branch is tricky, as it only appears for certain event # types an at different levels branch = None try: # Case 1: a ref_type indicates the type of ref. # This true for create and delete events. if 'ref_type' in payload: if payload['ref_type'] == 'branch': branch = payload['ref'] # Case 2: a pull_request object is involved. This is pull_request and # pull_request_review_comment events. elif 'pull_request' in payload: # This is the TARGET branch for the pull-request, not the source # branch branch = payload['pull_request']['base']['ref'] elif event in ['push']: # Push events provide a full Git ref in 'ref' and not a 'ref_type'. branch = payload['ref'].split('/', 2)[2] except KeyError: # If the payload structure isn't what we expect, we'll live without # the branch name pass # All current events have a repository, but some legacy events do not, # so let's be safe name = payload['repository']['name'] if 'repository' in payload else None meta = {'name': name, 'branch': branch, 'event': event} logging.info('Metadata:\n{}'.format(dumps(meta))) # Skip push-delete if event == 'push' and payload['deleted']: logging.info('Skipping push-delete event for {}'.format(dumps(meta))) return jsonify({'status': 'skipped'}) # Possible hooks scripts = [] if branch and name: scripts.append(join(hooks, '{event}-{name}-{branch}'.format(**meta))) scripts.append( join(hooks, '{event}-{name}-{branch}-background'.format(**meta))) if name: scripts.append(join(hooks, '{event}-{name}'.format(**meta))) scripts.append(join(hooks, '{event}'.format(**meta))) scripts.append(join(hooks, '{event}-background'.format(**meta))) scripts.append(join(hooks, 'all')) scripts.append(join(hooks, 'all-background')) # Check permissions scripts = [s for s in scripts if isfile(s) and access(s, X_OK)] if not scripts: return jsonify({'status': 'nop'}) # Save payload to temporal file osfd, tmpfile = mkstemp() with fdopen(osfd, 'w') as pf: pf.write(dumps(payload)) # Run scripts ran = {} for s in scripts: if s.endswith('-background'): # each backgrounded script gets its own tempfile # in this case, the backgrounded script MUST clean up after this!!! # the per-job tempfile will NOT be deleted here! osfd2, tmpfile2 = mkstemp() with fdopen(osfd2, 'w') as pf2: pf2.write(dumps(payload)) proc = Popen([s, tmpfile2, event], stdout=PIPE, stderr=PIPE) ran[basename(s)] = {'backgrounded': 'yes'} else: proc = Popen([s, tmpfile, event], stdout=PIPE, stderr=PIPE) stdout, stderr = proc.communicate() ran[basename(s)] = { 'returncode': proc.returncode, 'stdout': stdout.decode('utf-8'), 'stderr': stderr.decode('utf-8'), } # Log errors if a hook failed if proc.returncode != 0: logging.error('{} : {} \n{}'.format(s, proc.returncode, stderr)) # Remove temporal file remove(tmpfile) info = config.get('return_scripts_info', False) if not info: return jsonify({'status': 'done'}) output = dumps(ran, sort_keys=True, indent=4) logging.info(output) return jsonify(ran)