| Index: run.py | 
| =================================================================== | 
| new file mode 100755 | 
| --- /dev/null | 
| +++ b/run.py | 
| @@ -0,0 +1,135 @@ | 
| +#!/usr/bin/env python | 
| +# coding: utf-8 | 
| + | 
| +import sys | 
| +import os | 
| +import re | 
| +import subprocess | 
| +import getopt | 
| +import yaml | 
| + | 
| +def usage(): | 
| +  print >>sys.stderr, ''' | 
| +Usage: %s [-u <user>] [-h <host>|<group>] [-i] ... <command> | 
| + | 
| +Runs a command on the given hosts or groups of hosts. | 
| + | 
| +Options: | 
| +  -u <user>       User name to use with the SSH command | 
| +  -h <host|group> Host or group to run the command on (can be specified multiple times) | 
| +  -i              If specified, command will be executed on all hosts despite errors | 
| +''' % sys.argv[0] | 
| + | 
| +def parseOptions(args): | 
| +  try: | 
| +    options, args = getopt.getopt(args, 'u:h:i') | 
| +  except getopt.GetoptError, e: | 
| +    print >>sys.stderr, e | 
| +    usage() | 
| +    sys.exit(1) | 
| + | 
| +  user = None | 
| +  hosts = [] | 
| +  ignore_errors = False | 
| +  for option, value in options: | 
| +    if option == '-u': | 
| +      user = value | 
| +    elif option == '-h': | 
| +      hosts.append(value) | 
| +    elif option == '-i': | 
| +      ignore_errors = True | 
| + | 
| +  return user, hosts, ignore_errors, args | 
| + | 
| +def readMonitoringConfig(): | 
| +  # Use Puppet's parser to convert monitoringserver.pp into YAML | 
| +  manifest = os.path.join(os.path.dirname(__file__), 'manifests', 'monitoringserver.pp') | 
| +  parseScript = ''' | 
| +    require 'puppet' | 
| +    require 'puppet/parser' | 
| +    parser = Puppet::Parser::Parser.new(Puppet[:environment]) | 
| +    Puppet.settings[:ignoreimport] = true | 
| +    parser.file = ARGV[0] | 
| +    print ZAML.dump(parser.parse) | 
| +  ''' | 
| +  data, dummy = subprocess.Popen(['ruby', '', manifest], | 
| +                  stdin=subprocess.PIPE, | 
| +                  stdout=subprocess.PIPE).communicate(parseScript) | 
| + | 
| +  # See http://stackoverflow.com/q/8357650/785541 on parsing Puppet's YAML | 
| +  yaml.add_multi_constructor(u"!ruby/object:", lambda loader, suffix, node: loader.construct_yaml_map(node)) | 
| +  yaml.add_constructor(u"!ruby/sym", lambda loader, node: loader.construct_yaml_str(node)) | 
| +  return yaml.load(data) | 
| + | 
| +def getValidHosts(): | 
| +  def processNode(node, hosts=None, groups=None): | 
| +    if hosts == None: | 
| +      hosts = set() | 
| +    if groups == None: | 
| +      groups = {} | 
| + | 
| +    if 'context' in node and 'code' in node['context']: | 
| +      node = node['context']['code'] | 
| + | 
| +    if node.get('type', None) == 'nagios_hostgroup': | 
| +      data = node['instances']['children'][0] | 
| +      title = data['title']['value'] | 
| +      members = filter(lambda c: c['param'] == 'members', data['parameters']['children'])[0]['value']['value'] | 
| +      members = re.split(r'\s*,\s*', members) | 
| +      groups[title] = members | 
| +    elif node.get('type', None) == 'nagios_host': | 
| +      data = node['instances']['children'][0] | 
| +      title = data['title']['value'] | 
| +      hosts.add(title) | 
| + | 
| +    for child in node['children']: | 
| +      processNode(child, hosts, groups) | 
| +    return hosts, groups | 
| + | 
| +  monitoringConfig = readMonitoringConfig() | 
| +  if not monitoringConfig: | 
| +    print >>sys.stderr, "Failed to parse monitoring configuration" | 
| +    return [[], []] | 
| +  # Extract hosts and groups from monitoring config | 
| +  return processNode(monitoringConfig) | 
| + | 
| +def resolveHostList(hosts): | 
| +  validHosts, validGroups = getValidHosts() | 
| +  if not validHosts: | 
| +    print >>sys.stderr, "Warning: No valid hosts found, not validating" | 
| +    return hosts | 
| + | 
| +  result = set() | 
| +  for param in hosts: | 
| +    if param in validGroups: | 
| +      for host in validGroups[param]: | 
| +        if host == '*': | 
| +          result = result | validHosts | 
| +        else: | 
| +          result.add(host) | 
| +    elif param in validHosts: | 
| +      result.add(param) | 
| +    elif '%s.adblockplus.org' % param in validHosts: | 
| +      result.add('%s.adblockplus.org' % param) | 
| +    else: | 
| +      print >>sys.stderr, 'Warning: failed to recognize host or group %s' %param | 
| +  return result | 
| + | 
| +def runCommand(user, host, command, ignore_errors=False): | 
| +  if not isinstance(command, list): | 
| +    command = [command] | 
| +  command = ["ssh"] + (["-l", user] if user else []) + [host] + command | 
| +  if ignore_errors: | 
| +    subprocess.call(command) | 
| +  else: | 
| +    subprocess.check_call(command) | 
| + | 
| +if __name__ == "__main__": | 
| +  user, hosts, ignore_errors, args = parseOptions(sys.argv[1:]) | 
| +  selectedHosts = resolveHostList(hosts) | 
| +  if len(selectedHosts) == 0: | 
| +    print >>sys.stderr, 'No valid hosts or groups specified, nothing to do' | 
| +    sys.exit(0) | 
| +  for host in selectedHosts: | 
| +    print >>sys.stderr, 'Running on %s...' % host | 
| +    runCommand(user, host, args, ignore_errors=ignore_errors) | 
|  |