ArielGlenn has uploaded a new change for review.
https://gerrit.wikimedia.org/r/276796
Change subject: [WIP] stash a script here for "collect groups of hosts from
labs/beta/prod in order to run something on them, from my laptop because I'm
lazy"
......................................................................
[WIP] stash a script here for "collect groups of hosts from labs/beta/prod
in order to run something on them, from my laptop because I'm lazy"
Change-Id: Ibe515bbf497b7de49327771721e51a397aeae4e5
---
A salt-misc/README.txt
A salt-misc/do_ssh_commands.py
2 files changed, 335 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/operations/software
refs/changes/96/276796/1
diff --git a/salt-misc/README.txt b/salt-misc/README.txt
new file mode 100644
index 0000000..d7d9069
--- /dev/null
+++ b/salt-misc/README.txt
@@ -0,0 +1,21 @@
+These are some crap scripts I steal pieces of in order
+to do e.g. labs minons cleanup or get some random info
+from the prod cluster.
+
+Sticking them here so I can find them again, and because
+they really aren't polished prod ready etc.
+
+gather-minioninfo.sh is meant to be run on one's laptop.
+The output from that can be shoved through parse-minion-output.py
+Once you see what the problems are you can collect lists of
+hosts and use salt-fixups.py on them; it's meant to be run
+via ssh on the instances.
+
+do_ssh_commands.py is meant to be able to tell me which hosts
+are X or have X without having to log into a salt master,
+oh and it's per cluster. Some of the ssh tunnelling doesn't
+work yet. Whatevs. What I really want it for is to copy
+files around to various hosts. Didn't get that done either yet.
+This also needs a variable for the ssh command from the laptop
+e.g. I use a special script with a different name because it
+manages user agents.
diff --git a/salt-misc/do_ssh_commands.py b/salt-misc/do_ssh_commands.py
new file mode 100755
index 0000000..6df8d7d
--- /dev/null
+++ b/salt-misc/do_ssh_commands.py
@@ -0,0 +1,314 @@
+'''
+This script is around primarily for three reasons:
+
+1. I want to see which hosts have X of prod/labs/beta and
+ am too lazy to hop over there to look
+
+2. I want to do an ssh or scp loop on that set of hosts from
+ here (laptop at home).
+
+3. I want to do some action on all salt non-responsive hosts,
+ also ssh from laptop.
+
+Here's an example of the stuff I want to be able to do:
+
+ssh bastion-restricted.wmflabs.org ssh -l root \
+ -o 'StrictHostKeyChecking=no' somehost \
+ "ls -lt /var/log/salt/minion"
+'''
+
+import sys
+import getopt
+from subprocess import Popen, PIPE
+import ast
+
+
+MASTERS = {'prod': 'neodymium.eqiad.wmnet',
+ 'labs': 'labcontrol1001.wikimedia.org',
+ 'beta': 'deployment-saltmaster.deployment-prep.eqiad.wmnet',
+ 'mine': 'salt-jessie.salt.eqiad.wmflabs'}
+
+
+DEBUG = True
+
+
+def get_saltmaster(realm):
+ '''
+ get and return the fqdn of the saltmaster
+ for the specified realm
+ '''
+ if realm in MASTERS:
+ return MASTERS[realm]
+ else:
+ return None
+
+
+def get_target_args(target, target_type):
+ '''
+ turn the target and target type into salt args
+ and return them
+ '''
+ if target_type == 'glob' or target_type == 'mia':
+ return ["'" + target + "'"]
+ elif target_type == 'grain':
+ return ['-G', target]
+ elif target_type == 'list':
+ return ['-L', target]
+ elif target_type == 'facter':
+ return ["'*'"]
+ else:
+ return None
+
+
+def get_target_command(target, target_type):
+ '''
+ assemble salt command to get the returns we
+ want, based on target and target_type
+ '''
+ if target_type == 'facter':
+ command = ['cmd.run', "'facter %s'" % target.split(':')[0]]
+ elif target_type == 'grain':
+ command = ['test.ping']
+ command.extend(target.split(':'))
+ elif (target_type == 'list' or target_type == 'glob'
+ or target_type == 'mia'):
+ command = ['test.ping']
+ if target_type == 'mia':
+ command.append('-v')
+ else:
+ command = None
+ return command
+
+
+def get_salt_responders(output, match_value=None, match_start=None):
+ '''
+ if there is a match_string supplied in the form name:value
+ then only those hostnames responding with that value
+ in the response will be reported
+
+ otherwise all hosts that responded will be reported
+ '''
+ if DEBUG:
+ print output
+ lines = output.splitlines()
+ minions_good = []
+ for line in lines:
+ if not line.startswith('{'):
+ # there might be header lines with the JID
+ continue
+ entry = ast.literal_eval(line)
+ minions = entry.keys()
+ for minion in minions:
+ if (match_value is not None and
+ isinstance(entry[minion], basestring) and
+ entry[minion] == match_value):
+ minions_good.append(minion)
+ elif (match_start is not None and
+ isinstance(entry[minion], basestring) and
+ entry[minion].startswith(match_start)):
+ minions_good.append(minion)
+ elif match_value is None and match_start is None:
+ minions_good.append(minion)
+ return minions_good
+
+
+def get_target_list(target, target_type, realm):
+ '''
+ given target string (host expr), target type (glob, list, etc)
+ and realm (labs, prod, etc) get the list of responsive
+ hosts that match, or in the case where target type is 'mia'
+ get hosts that don't respond
+ '''
+ command = ['ssh', get_saltmaster(realm), "sudo", "salt"]
+ command.extend(get_target_args(target, target_type))
+ command.extend(["--out", "raw"])
+ command.extend(get_target_command(target, target_type))
+
+ if DEBUG:
+ print command
+ proc = Popen(command, stderr=PIPE, stdout=PIPE)
+ output, error = proc.communicate()
+ if proc.returncode:
+ sys.stderr.write("failed to retrieve target list,"
+ " retcode %d\n" % proc.returncode)
+ sys.stderr.write("command run was ")
+ sys.stderr.write(" ".join(command))
+ sys.stderr.write("\n")
+
+ if error is not None:
+ sys.stderr.write(error)
+ sys.stderr.write("\n")
+ if output is not None:
+ sys.stderr.write(output)
+ sys.stderr.write("\n")
+ sys.exit(1)
+ if target_type == 'facter':
+ match_value = target.split(':')[1]
+ results = get_salt_responders(output, match_value=match_value)
+ elif target_type == 'mia':
+ results = get_salt_responders(
+ output, match_start='Minion did not return')
+ else:
+ results = get_salt_responders(output)
+ return results
+
+
+def get_command_prefix(realm, ssh=None):
+ '''
+ depending on realm, get the command prefix that
+ sets up the right ssh proxying
+ '''
+ # FIXME get the right strings in here in the
+ # right place in the command later toooooo
+ prefix = None
+ if ssh == 'scp':
+ if realm == 'prod':
+ prefix = []
+ elif realm == 'labs' or realm == 'mine':
+ prefix = []
+ elif realm == 'beta':
+ prefix = []
+ elif ssh == 'ssh':
+ if realm == 'prod':
+ prefix = ['ssh']
+ elif realm == 'labs' or realm == 'mine':
+ prefix = ["ssh", "-A", "bastion-restricted.wmflabs.org"]
+ elif realm == 'beta':
+ prefix = ["ssh", "-A", "bastion-restricted.wmflabs.org",
+ "ssh", "-A", "deployment-bastion"]
+ return prefix
+
+
+def run_command(hostname, command, realm, dryrun):
+ '''
+ run command on remote host or print what
+ would have been run
+ '''
+ full_command = get_command_prefix(realm, command[0])
+ # ["ssh", "-A", "bastion-restricted.wmflabs.org"]
+
+ command = [field if field != 'minion' else hostname for field in command]
+ full_command.extend(command)
+
+ if dryrun:
+ print("on", hostname, "would run", full_command)
+ return
+
+ proc = Popen(full_command, stderr=PIPE, stdout=PIPE)
+ output, error = proc.communicate()
+ print "retcode:", proc.returncode
+ if error is not None:
+ print "error:", error
+ if output is not None:
+ print "output:", output
+ return
+
+
+def usage(message=None):
+ '''
+ display a helpful usage message with
+ an optional introductory message first
+ '''
+ if message is not None:
+ sys.stderr.write(message)
+ sys.stderr.write("\n")
+ usage_message = """
+Usage: do-ssh-commands.py --target <string>
+ [--type glob|list|facter|grain]
+ [--realm prod|labs|beta] [--dryrun]
+ command arg1 arg2....'minion' ...argn
+OR do-ssh-commands.py --help
+
+The command that follows the regular arguments
+must have the string 'minion' somewhere in it;
+this will be replaced with the name of each host
+as the command is executed.
+
+If no command is specified, the target list will
+be displayed.
+
+Examples:
+
+do_ssh_commands.py --target lsbdistcodename:precise
+ --type facter --realm beta ssh minion uptime
+
+do_ssh_commands.py --target 'bast*'
+ --realm prod scp ~/.bashrc minion:/home/hardworker/.bashrc
+
+do_ssh_commands.py --target 'mw*'
+ --type mia --realm prod
+
+Options:
+
+ --target (-t): string identifying the hosts to be
+ targeted by the command. See --type
+ below.
+ --type (-T): glob: the target is a string with a
+ possible wildcard in it
+ or regexp
+ list: the target is a comma-separated
+ list of hosts
+ grain: the target is of the form grainname:value
+ facter: the target is of the form factername:value
+ mia: hosts that don't respond to test.ping
+ in this case the target must be a
+ string with a possible wildcard in it
+ (default: glob)
+ --realm (-r): which cluster to check. Known values: prod, labs, beta
+ (default: prod)
+ --dryrun (-d): display the commands that would be run to produce
+ the output but don't actually run them
+ --help (-h): display this message
+"""
+ sys.stderr.write(usage_message)
+ sys.exit(1)
+
+
+def main():
+ '''
+ entry point, does all the work
+ '''
+ target = None
+ target_type = 'glob'
+ realm = 'prod'
+ dryrun = False
+ command = None
+
+ try:
+ (options, remainder) = getopt.gnu_getopt(
+ sys.argv[1:], "t:T:r:dh",
+ ["target=", "type=", "realm=", "dryrun", "help"])
+ except getopt.GetoptError as err:
+ usage("Unknown option specified: " + str(err))
+ for (opt, val) in options:
+ if opt in ["-t", "--target"]:
+ target = val
+ elif opt in ["-T", "--type"]:
+ target_type = val
+ elif opt in ["-r", "--realm"]:
+ realm = val
+ elif opt in ["-d", "--dryrun"]:
+ dryrun = True
+ elif opt in ["-h", "--help"]:
+ usage('Help for this script\n')
+ else:
+ usage("Unknown option specified: <%s>" % opt)
+
+ if len(remainder):
+ command = remainder
+
+ if target is None:
+ usage("Mandatory argument 'target' not specified")
+
+ todo = get_target_list(target, target_type, realm)
+
+ if command is None:
+ print todo
+ return
+
+ for hostname in todo:
+ run_command(hostname, command, realm, dryrun)
+
+
+if __name__ == "__main__":
+ main()
--
To view, visit https://gerrit.wikimedia.org/r/276796
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ibe515bbf497b7de49327771721e51a397aeae4e5
Gerrit-PatchSet: 1
Gerrit-Project: operations/software
Gerrit-Branch: master
Gerrit-Owner: ArielGlenn <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits