| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # coding: utf-8 | 2 # coding: utf-8 |
| 3 | 3 |
| 4 import argparse | 4 import argparse |
| 5 import sys | 5 import sys |
| 6 import os | 6 import os |
| 7 import posixpath | 7 import posixpath |
| 8 import re | 8 import re |
| 9 import subprocess | 9 import subprocess |
| 10 import yaml | 10 import yaml |
| 11 | 11 |
| 12 def createArgumentParser(**kwargs): |
| 13 parser = argparse.ArgumentParser(**kwargs) |
| 14 parser.add_argument( |
| 15 '-u', '--user', metavar='user', dest='user', type=str, |
| 16 help='user name for use with SSH, must exist on all hosts' |
| 17 ) |
| 12 | 18 |
| 13 def createArgumentParser(**kwargs): | 19 parser.add_argument( |
| 14 parser = argparse.ArgumentParser(**kwargs) | 20 '-l', '--local', action='store_false', dest='remote', default=None, |
| 15 parser.add_argument( | 21 help='use the local version of hosts.yaml' |
| 16 '-u', '--user', metavar='user', dest='user', type=str, | 22 ) |
| 17 help='user name for use with SSH, must exist on all hosts' | |
| 18 ) | |
| 19 | 23 |
| 20 parser.add_argument( | 24 parser.add_argument( |
| 21 '-l', '--local', action='store_false', dest='remote', default=None, | 25 '-r', '--remote', metavar='master', dest='remote', type=str, |
| 22 help='use the local version of hosts.yaml' | 26 help='use a remote (puppet-master) version of hosts.yaml' |
| 23 ) | 27 ) |
| 24 | 28 |
| 25 parser.add_argument( | 29 return parser |
| 26 '-r', '--remote', metavar='master', dest='remote', type=str, | |
| 27 help='use a remote (puppet-master) version of hosts.yaml' | |
| 28 ) | |
| 29 | |
| 30 return parser | |
| 31 | |
| 32 | 30 |
| 33 def parseOptions(args): | 31 def parseOptions(args): |
| 34 description = 'Run a command on the given hosts or groups of hosts' | 32 description = 'Run a command on the given hosts or groups of hosts' |
| 35 parser = createArgumentParser(description=description) | 33 parser = createArgumentParser(description=description) |
| 36 parser.add_argument( | 34 parser.add_argument( |
| 37 '-i', '--ignore-errors', action='store_true', dest='ignore_errors', | 35 '-i', '--ignore-errors', action='store_true', dest='ignore_errors', |
| 38 help='continue execution on next host in case of an error' | 36 help='continue execution on next host in case of an error' |
| 39 ) | 37 ) |
| 40 | 38 |
| 41 hosts = set() | 39 hosts = set() |
| 42 parser.add_argument( | 40 parser.add_argument( |
| 43 '-t', '--target', metavar='host|group', | 41 '-t', '--target', metavar='host|group', |
| 44 help='target host or group, can be specified multiple times', | 42 help='target host or group, can be specified multiple times', |
| 45 type=lambda value: hosts.update([value]) | 43 type=lambda value: hosts.update([value]) |
| 46 ) | 44 ) |
| 47 | 45 |
| 48 parser.add_argument( | 46 parser.add_argument( |
| 49 'args', metavar='command', type=str, nargs='+', | 47 'args', metavar='command', type=str, nargs='+', |
| 50 help='the command to run on the specified hosts' | 48 help='the command to run on the specified hosts' |
| 51 ) | 49 ) |
| 52 | 50 |
| 53 options = parser.parse_args(args) | 51 options = parser.parse_args(args) |
| 54 options.hosts = hosts | 52 options.hosts = hosts |
| 55 return options | 53 return options |
| 56 | |
| 57 | 54 |
| 58 def getValidHosts(options): | 55 def getValidHosts(options): |
| 59 path_canonical = ('modules', 'private', 'hiera', 'hosts.yaml') | 56 path_canonical = ('modules', 'private', 'hiera', 'hosts.yaml') |
| 60 | 57 |
| 61 if options.remote: | 58 if options.remote: |
| 62 login = ['-l', options.user] if options.user else [] | 59 login = ['-l', options.user] if options.user else [] |
| 63 path_name = posixpath.join('/etc/puppet/infrastructure', *path_canonical
) | 60 path_name = posixpath.join('/etc/puppet/infrastructure', *path_canonical) |
| 64 command = ['ssh'] + login + [options.remote, '--', 'sudo', 'cat', path_n
ame] | 61 command = ['ssh'] + login + [options.remote, '--', 'sudo', 'cat', path_name] |
| 65 child = subprocess.Popen(command, stderr=sys.stderr, stdout=subprocess.P
IPE) | 62 child = subprocess.Popen(command, stderr=sys.stderr, stdout=subprocess.PIPE) |
| 66 try: | 63 try: |
| 67 config = yaml.load(child.stdout) | 64 config = yaml.load(child.stdout) |
| 68 finally: | 65 finally: |
| 69 child.stdout.close() | 66 child.stdout.close() |
| 70 child.wait() | 67 child.wait() |
| 71 elif options.remote is False: | 68 elif options.remote is False: |
| 72 dirname = os.path.dirname(sys.argv[0]) | 69 dirname = os.path.dirname(sys.argv[0]) |
| 73 path_name = os.path.join(dirname, *path_canonical) | 70 path_name = os.path.join(dirname, *path_canonical) |
| 74 with open(path_name, 'rb') as handle: | 71 with open(path_name, 'rb') as handle: |
| 75 config = yaml.load(handle) | 72 config = yaml.load(handle) |
| 76 else: | 73 else: |
| 77 sys.exit('Please either specify a --remote host or use --local') | 74 sys.exit('Please either specify a --remote host or use --local') |
| 78 | 75 |
| 79 servers = config.get('servers', {}) | 76 servers = config.get('servers', {}) |
| 80 return servers | 77 return servers |
| 81 | |
| 82 | 78 |
| 83 def resolveHostList(options): | 79 def resolveHostList(options): |
| 84 | 80 |
| 85 result = set() | 81 result = set() |
| 86 | 82 |
| 87 try: | 83 try: |
| 88 valid_hosts = getValidHosts(options) | 84 valid_hosts = getValidHosts(options) |
| 89 except Warning as error: | 85 except Warning as error: |
| 90 print >>sys.stderr, 'Warning: failed to determine valid hosts:', error | 86 print >>sys.stderr, 'Warning: failed to determine valid hosts:', error |
| 91 result.update(options.hosts) | 87 result.update(options.hosts) |
| 92 else: | 88 else: |
| 93 for name in options.hosts: | 89 for name in options.hosts: |
| 94 chunk = [ | 90 chunk = [ |
| 95 value.get('dns', key) for (key, value) in valid_hosts.items() | 91 value.get('dns', key) for (key, value) in valid_hosts.items() |
| 96 | 92 |
| 97 if name == key | 93 if name == key |
| 98 or name == '*' | 94 or name == '*' |
| 99 or name == value.get('dns', None) | 95 or name == value.get('dns', None) |
| 100 or name in value.get('groups', ()) | 96 or name in value.get('groups', ()) |
| 101 ] | 97 ] |
| 102 | 98 |
| 103 if len(chunk) == 0: | 99 if len(chunk) == 0: |
| 104 print >>sys.stderr, 'Warning: failed to recognize host or group'
, name | 100 print >>sys.stderr, 'Warning: failed to recognize host or group', name |
| 105 else: | 101 else: |
| 106 result.update(chunk) | 102 result.update(chunk) |
| 107 | 103 |
| 108 return result | 104 return result |
| 109 | |
| 110 | 105 |
| 111 def runCommand(user, host, command, ignore_errors=False): | 106 def runCommand(user, host, command, ignore_errors=False): |
| 112 if not isinstance(command, list): | 107 if not isinstance(command, list): |
| 113 command = [command] | 108 command = [command] |
| 114 command = ['ssh'] + (['-l', user] if user else []) + [host] + command | 109 command = ['ssh'] + (['-l', user] if user else []) + [host] + command |
| 115 if ignore_errors: | 110 if ignore_errors: |
| 116 subprocess.call(command) | 111 subprocess.call(command) |
| 117 else: | 112 else: |
| 118 subprocess.check_call(command) | 113 subprocess.check_call(command) |
| 119 | 114 |
| 120 if __name__ == '__main__': | 115 if __name__ == '__main__': |
| 121 options = parseOptions(sys.argv[1:]) | 116 options = parseOptions(sys.argv[1:]) |
| 122 selectedHosts = resolveHostList(options) | 117 selectedHosts = resolveHostList(options) |
| 123 if len(selectedHosts) == 0: | 118 if len(selectedHosts) == 0: |
| 124 print >>sys.stderr, 'No valid hosts or groups specified, nothing to do' | 119 print >>sys.stderr, 'No valid hosts or groups specified, nothing to do' |
| 125 sys.exit(0) | 120 sys.exit(0) |
| 126 for host in selectedHosts: | 121 for host in selectedHosts: |
| 127 print >>sys.stderr, 'Running on %s...' % host | 122 print >>sys.stderr, 'Running on %s...' % host |
| 128 runCommand(options.user, host, options.args, options.ignore_errors) | 123 runCommand(options.user, host, options.args, options.ignore_errors) |
| OLD | NEW |