class InfoUrlHandler(ShowSomeUrlHandler): """Tells you what the bot's information url is.""" handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('info url', takes_args=False), trigger.Trigger('information url', takes_args=False), ], } def _get_what_host_port_url(self): try: status_port = self.config.status.port except AttributeError: status_port = None try: ssl_on = bool(self.config.status.ssl) except AttributeError: ssl_on = False return munch.Munch({ 'name': 'information', 'enabled': bool(status_port), 'port': status_port, 'hostname': self.bot.hostname, 'ssl_on': ssl_on, })
class Handler(handler.TriggeredHandler): """Shows the version of the bot that is running.""" what = 'padre' handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('version', takes_args=False), ], } def _run(self, **kwargs): me = pkg_resources.get_distribution(self.what) replier = self.message.reply_text if not me: replier("I am not really sure what version I am.", threaded=True, prefixed=False) else: replier("I am %s version `%s`." % (self.what, me.version), threaded=True, prefixed=False)
class AraUrlHandler(ShowSomeUrlHandler): """Tells you what the bot's ara url is.""" handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('ara url', takes_args=False), ], } def _get_what_host_port_url(self): try: ara_enabled = self.config.ara_enabled except AttributeError: ara_enabled = False ara_hostname = self.config.get("ara_hostname") if not ara_hostname: ara_hostname = self.bot.hostname return munch.Munch({ 'name': 'ara', 'enabled': ara_enabled, 'port': self.config.get("ara_port"), 'hostname': ara_hostname, })
class Handler(handler.TriggeredHandler): """Welcomes you to the future!""" hellos = [ 'Hallo', 'Bonjour', 'Guten tag', u'Shalóm', 'Konnichiwa', u'Namastē', 'Hola', u'Nǐ hǎo', ] handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('hello', takes_args=False), trigger.Trigger('hi', takes_args=False), trigger.Trigger('howdy', takes_args=False), ], } def _run(self): hi_there = random.choice(self.hellos) replier = self.message.reply_text replier(hi_there, threaded=True, prefixed=False)
class Handler(handler.TriggeredHandler): """Shows you what this bots current config is.""" handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('show config', takes_args=False), trigger.Trigger('config show', takes_args=False), ], } def _run(self, **kwargs): tmp_config = copy.deepcopy(self.bot.config) tmp_config = munch.unmunchify(tmp_config) tmp_config = utils.mask_dict_password(tmp_config) tmp_config = utils.prettify_yaml(tmp_config, explicit_end=False, explicit_start=False) lines = [] lines.append("I am running with configuration:") lines.extend([ "```", tmp_config, "```", ]) replier = self.message.reply_text replier("\n".join(lines), threaded=True, prefixed=True)
class ShowHandler(handler.TriggeredHandler): """Shows the internal time schedule (for periodic jobs).""" handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('periodics show', takes_args=False), ], } def _run(self): sched_state, jobs = peu.format_scheduler(self.bot.scheduler) text = "Scheduler is in `%s` state" % sched_state if jobs: text += " with the following jobs:" self.message.reply_attachments( text=text, attachments=[peu.format_job(job) for job in jobs], log=LOG, link_names=True, as_user=True, thread_ts=self.message.body.ts, channel=self.message.body.channel) else: text += "." self.message.reply_text(text, threaded=True, prefixed=False)
class Handler(handler.TriggeredHandler): """Show's how many seconds the bot has been alive for.""" handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('uptime', takes_args=False), ], } def _run(self, **kwargs): started_at = self.bot.started_at replier = self.message.reply_text if started_at is None: replier( "I am not alive, how are you sending this?", threaded=True, prefixed=False) else: now = self.date_wrangler.get_now() diff = now - started_at replier( "I have been alive" " for %s." % utils.format_seconds(diff.total_seconds()), threaded=True, prefixed=False)
class Handler(handler.TriggeredHandler): """Finds a hostnames ip address.""" handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('dns lookup', takes_args=True), ], 'args': { 'order': ['hostname'], 'help': { 'hostname': 'hostname to lookup', }, 'schema': Schema({ Required("hostname"): All(su.string_types(), Length(min=1)), }), }, } def _run(self, hostname): replier = functools.partial(self.message.reply_text, threaded=True, prefixed=False) hostname_ip = socket.gethostbyname(hostname) replier("The ip address for `%s` is `%s`" % (hostname, hostname_ip))
class SpinHandler(handler.TriggeredHandler): """Handler that never stops (for testing).""" update_frequency = 5.0 handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('spin', takes_args=False), trigger.Trigger('spinner', takes_args=False), ], } def _run(self): replier = self.message.reply_text m = self.message.make_manual_progress_bar() with timeutils.StopWatch() as w: replier("Spinner initiated.", threaded=True, prefixed=False) while not self.dead.is_set(): self.dead.wait(self.update_frequency) if self.dead.is_set(): break else: m.update("%0.2f seconds" % w.elapsed()) replier("Spinner stopped.", threaded=True, prefixed=False)
class Handler(handler.TriggeredHandler): """Helps you (the user) with some useful buzzwords.""" buzz_url = "http://www.buzzwordipsum.com/buzzwords/" handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('help me', takes_args=False), ], } def _run(self): bullshit_response_from_buzzwordipsum = requests.get(self.buzz_url, params={ "format": "text", "paragraphs": "1", "type": "sentences" }) bullshit_response_from_buzzwordipsum.raise_for_status() replier = self.message.reply_text replier(bullshit_response_from_buzzwordipsum.text.strip("\n"), threaded=True, prefixed=False)
class ClearHandler(handler.TriggeredHandler): """Remove all aliases (for the calling user).""" handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('alias clear', takes_args=False), ], } def _run(self): from_who = self.message.body.user_id if not from_who: return from_who = "user:%s" % from_who with self.bot.locks.brain: try: user_info = self.bot.brain[from_who] except KeyError: user_info = {} if 'aliases' in user_info: num_aliases = len(user_info['aliases']) user_info['aliases'] = {} self.bot.brain[from_who] = user_info self.bot.brain.sync() else: num_aliases = 0 replier = self.message.reply_text replier("Removed %s aliases." % num_aliases, threaded=True, prefixed=False)
class Handler(handler.TriggeredHandler): """Hands out random jokes.""" joke_url = 'http://icanhazdadjoke.com' handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('tell me a joke', takes_args=False), ], } def _run(self): resp = requests.get(self.joke_url, headers=OUT_HEADERS) resp.raise_for_status() joke = resp.json().get("joke") if not joke: joke_text = "No joke found when calling `%s`." % self.joke_url else: joke_text = joke replier = self.message.reply_text replier(joke_text, threaded=True, prefixed=False)
class Handler(handler.TriggeredHandler): """Causes the bot to restart itself.""" ack_prefix = 'Restart acknowledged.' ack_messages = [ "Be back in a bit!", ] handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('restart', takes_args=False), ], 'authorizer': auth.user_in_ldap_groups('admins_cloud'), } def _run(self, **kwargs): ack_msg = self.ack_prefix ack_msg += " " ack_msg += random.choice(self.ack_messages) replier = self.message.reply_text replier(ack_msg, threaded=True, prefixed=False) if not self.bot.dead.is_set(): self.bot.dead.set(self.bot.dead.RESTART)
class Handler(handler.TriggeredHandler): """Causes the bot to turn itself off/shutdown.""" ack_prefix = 'Shutdown acknowledged.' ack_messages = [ "Goodbye!", "I am out of here.", "I am so out of here.", "Live long and prosper.", "Peace out y'all!", ] handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('shutdown', takes_args=False), ], 'authorizer': auth.user_in_ldap_groups('admins_cloud'), } def _run(self, **kwargs): ack_msg = self.ack_prefix ack_msg += " " ack_msg += random.choice(self.ack_messages) replier = self.message.reply_text replier(ack_msg, threaded=True, prefixed=False) if not self.bot.dead.is_set(): self.bot.dead.set()
class Handler(handler.TriggeredHandler): """Shows what this bot is.""" what = 'padre' handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('what are you', takes_args=False), trigger.Trigger('what are you?', takes_args=False), ], } pkg_info_attrs = tuple([ 'author_email', 'author', 'classifiers', 'description', 'home_page', 'license', 'name', 'platform', 'summary', 'version', ]) def _run(self, **kwargs): replier = self.message.reply_text me = pkginfo.get_metadata(self.what) if not me: replier("I am not really sure what I am.", threaded=True, prefixed=False) else: lines = _format_pkg(me, self.pkg_info_attrs) if lines: replier = self.message.reply_attachments attachment = { 'pretext': "I am the following:", 'text': "\n".join(lines), 'mrkdwn_in': ['text'], } replier(text=' ', log=LOG, attachments=[attachment], link_names=True, as_user=True, channel=self.message.body.channel, thread_ts=self.message.body.ts) else: replier("I am not really sure what I am.", threaded=True, prefixed=False)
class RemoveHandler(handler.TriggeredHandler): """Remove a alias to a long command (for the calling user).""" handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('alias remove', True), ], 'schema': Schema({ Required("short"): All(su.string_types(), Length(min=1)), }), 'args': { 'order': [ 'short', ], 'help': { 'short': 'alias of full command to remove', }, }, } def _run(self, short): from_who = self.message.body.user_id if not from_who: return from_who = "user:%s" % from_who lines = [] with self.bot.locks.brain: try: user_info = self.bot.brain[from_who] except KeyError: user_info = {} user_aliases = user_info.get('aliases', {}) try: long = user_aliases.pop(short) self.bot.brain[from_who] = user_info self.bot.brain.sync() lines = [ ("Alias of `%s` to `%s` has" " been removed.") % (short, long), ] except KeyError: lines = [ "No alias found for `%s`" % short, ] if lines: replier = self.message.reply_text replier("\n".join(lines), threaded=True, prefixed=False)
class DescribeUserHandler(handler.TriggeredHandler): """Lists the details of some ldap user.""" required_clients = ("ldap", ) handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('ldap describe user', takes_args=True), ], 'args': { 'order': [ 'user', ], 'help': { 'user': '******', }, 'schema': Schema({ Required("user"): All(scu.string_types(), Length(min=1)), }), }, } def _run(self, user): ldap_client = self.bot.clients.ldap_client tmp_user = ldap_client.describe_user(user) replier = self.message.reply_text if not tmp_user: replier("No user with name `%s` found." % (user), threaded=True, prefixed=False) else: tbl_headers = [] row = [] for k in sorted(tmp_user.keys()): v = tmp_user.get(k) if v is not None: h_k = k.replace("_", ' ') h_k = h_k[0].upper() + h_k[1:] tbl_headers.append(h_k) row.append(v) rows = [row] lines = [ "```", tabulate.tabulate(rows, headers=tbl_headers), "```", ] replier("\n".join(lines), threaded=True, prefixed=False)
class RunAllHandler(handler.TriggeredHandler): """Explicitly runs all periodic jobs.""" handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('periodics run all', takes_args=True), ], 'args': { 'order': [ 'skip_paused', ], 'help': { 'skip_paused': ('skip over paused jobs (ie do not' ' unpause them)'), }, 'defaults': { 'skip_paused': True, }, 'converters': { 'skip_paused': hu.strict_bool_from_string, }, 'schema': Schema({ Required("skip_paused"): bool, }), }, 'authorizer': auth.user_in_ldap_groups('admins_cloud'), } def _run(self, skip_paused): kicked = 0 seen_jobs = set() skipped = 0 for job in self.bot.scheduler.get_jobs(): if job.id in seen_jobs: continue seen_jobs.add(job.id) if skip_paused and job.next_run_time is None: skipped += 1 continue job.modify(next_run_time=self.date_wrangler.get_now()) kicked += 1 if kicked: self.bot.scheduler.wakeup() text = ("Kicked %s jobs" " and skipped %s jobs.") % (kicked, skipped) self.message.reply_text(text, threaded=True, prefixed=False)
class AddHandler(handler.TriggeredHandler): """Alias a long command to a short(er) one (for the calling user).""" handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('alias add', True), ], 'schema': Schema({ Required("long"): All(su.string_types(), Length(min=1)), Required("short"): All(su.string_types(), Length(min=1)), }), 'args': { 'order': [ 'long', 'short', ], 'help': { 'long': "full command", 'short': 'shorter alias of full command', }, }, } def _run(self, long, short): from_who = self.message.body.user_id if not from_who: return from_who = "user:%s" % from_who with self.bot.locks.brain: try: user_info = self.bot.brain[from_who] except KeyError: user_info = {} user_aliases = user_info.setdefault('aliases', {}) user_aliases[short] = long self.bot.brain[from_who] = user_info self.bot.brain.sync() lines = [ "Alias of `%s` to `%s` has been recorded." % (short, long), ] replier = self.message.reply_text replier("\n".join(lines), threaded=True, prefixed=False)
class JenkinsRestartHandler(handler.TriggeredHandler): """Triggers the jenkins the bot is connected to, to restart.""" handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('jenkins restart', takes_args=True), ], 'args': { 'order': [ 'safe', ], 'schema': Schema({ Required("safe"): bool, }), 'converters': { 'safe': hu.strict_bool_from_string, }, 'help': { 'safe': "perform a safe restart (letting active jobs finish)", }, 'defaults': { 'safe': True, }, }, 'authorizer': auth.user_in_ldap_groups('admins_cloud'), } required_clients = ('jenkins', ) def _run(self, safe): jenkins_client = self.bot.clients.jenkins_client replier = self.message.reply_text replier = functools.partial(replier, threaded=True, prefixed=False) if safe: replier("Engaging *safe* jenkins restart, please wait...") else: replier("Engaging *unsafe* (ie forceful)" " jenkins restart, please wait...") if jenkins_client.perform_restart(safe=safe): replier("Restart acknowledged.") else: replier("Restart failed.")
class Handler(handler.TriggeredHandler): """Emit some message to some set of slack channels.""" required_clients = ('slack',) requires_slack_sender = True handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('emit message', takes_args=True), ], 'args': { 'order': ['channels', 'message'], 'schema': Schema({ Required("channels"): All(scu.string_types(), Length(min=1)), Required("message"): All(scu.string_types(), Length(min=1)), }), 'help': { 'channels': ('comma separated list of channels' ' to broadcast to'), 'message': 'what to broadcast', }, }, } def _run(self, channels, message): slack_sender = self.bot.slack_sender slack_server = self.bot.clients.slack_client.server ok_channels = [] seen = set() for maybe_c in channels.split(","): maybe_c = maybe_c.strip() if maybe_c and maybe_c not in seen: tmp_c = slack_server.channels.find(maybe_c) if tmp_c is None: raise RuntimeError("Could not find channel '%s'" % maybe_c) else: if tmp_c.id not in seen: seen.add(maybe_c) seen.add(tmp_c.id) ok_channels.append(tmp_c) for ch in ok_channels: slack_sender.rtm_send(message, channel=ch.id)
class WatchHandler(JobWatcher): """Watches a jenkins jobs build.""" build_info_delay = 10 handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'followers': [ ConsoleFollower, AbortFollower, ], 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('jenkins watch', takes_args=True), ], 'args': { 'order': [ 'job_name', 'build', ], 'schema': Schema({ Required("job_name"): All(scu.string_types(), Length(min=1)), Required("build"): int, }), 'converters': { 'build': int, }, 'help': { 'job_name': "job name to watch", "build": "build number to watch", }, }, 'authorizer': auth.user_in_ldap_groups('admins_cloud'), } required_clients = ('jenkins', ) def _run(self, job_name, build): clients = self.bot.clients return self._watch(job_name, build, clients.jenkins_client)
class RunOneHandler(handler.TriggeredHandler): """Explicitly runs one periodic jobs.""" handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('periodics run one', takes_args=True), ], 'args': { 'order': [ 'job_id', ], 'help': { 'job_id': 'job id to run', }, 'schema': Schema({ Required("job_id"): All(scu.string_types(), Length(min=1)), }), }, 'authorizer': auth.user_in_ldap_groups('admins_cloud'), } def _run(self, job_id): job = self.bot.scheduler.get_job(job_id) if job is None: raise excp.NotFound("Could not find job id '%s'" % job_id) elif job.next_run_time is None: raise RuntimeError("Paused job '%s' can not be explicitly" " ran (please resume it first)" % job_id) else: job.modify(next_run_time=self.date_wrangler.get_now()) self.bot.scheduler.wakeup() self.message.reply_text("Job `%s` has had" " its next run time" " updated to be now (hopefully it" " runs soon)." % job_id, threaded=True, prefixed=False)
class ResumeHandler(handler.TriggeredHandler): """Resumes a previously paused periodic job.""" handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('periodics resume', takes_args=True), ], 'args': { 'order': [ 'job_id', ], 'help': { 'job_id': 'job id to resume', }, 'schema': Schema({ Required("job_id"): All(scu.string_types(), Length(min=1)), }), }, 'authorizer': auth.user_in_ldap_groups('admins_cloud'), } def _run(self, job_id): job = self.bot.scheduler.get_job(job_id) if job is None: raise excp.NotFound("Could not find job id '%s'" % job_id) if job.next_run_time is None: job.resume() self.bot.scheduler.wakeup() self.message.reply_text("Job `%s` has" " been resumed." % job_id, threaded=True, prefixed=False) else: self.message.reply_text("Job `%s` is not paused (so it can" " not be resumed)." % job_id, threaded=True, prefixed=False)
class Handler(handler.TriggeredHandler): """Say various trump phrases.""" trump_url = 'https://api.whatdoestrumpthink.com/api/v1/quotes' handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('what would trump say', takes_args=False), trigger.Trigger('what would trump say?', takes_args=False), trigger.Trigger('trump say something', takes_args=False), ], } def _run(self): trump_messages = requests.get(self.trump_url) trump_messages.raise_for_status() message = random.choice( trump_messages.json()["messages"]["non_personalized"]) replier = self.message.reply_text replier(message, threaded=True, prefixed=False)
class CalcSizeHandler(handler.TriggeredHandler): """Determines size of docker artifactory repositories.""" config_section = 'artifactory' handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('artifactory calculate size', takes_args=True), ], 'authorizer': auth.user_in_ldap_groups('admins_cloud'), 'args': { 'order': ['project'], 'help': { 'project': 'project to scan', }, 'schema': Schema({ Required("project"): All(su.string_types(), Length(min=1)), }), }, } required_secrets = ( 'ci.artifactory.ro_account', ) required_configurations = ('base_url',) def _run(self, project): ro_account = self.bot.secrets.ci.artifactory.ro_account path = _find_path(self.config, project, ro_account) if not path: raise excp.NotFound("Could not find project '%s'" % project) replier = self.message.reply_text replier = functools.partial(replier, threaded=True, prefixed=False) replier("Determining current size of `%s`, please" " wait..." % project) all_sizes = [ path.stat().size, ] child_paths = list(path.iterdir()) child_paths = sorted(child_paths, key=lambda p: p.name) if child_paths: c_pbar = self.message.make_progress_bar( len(child_paths), update_period=_calc_emit_every(child_paths)) for child_path in c_pbar.wrap_iter(child_paths): if self.dead.is_set(): break all_sizes.append(child_path.stat().size) replier("Determining total size" " of top-level child `%s`, please" " wait..." % child_path.name) sub_child_paths = list(child_path.iterdir()) if sub_child_paths: sc_pbar = self.message.make_progress_bar( len(sub_child_paths), update_period=_calc_emit_every(sub_child_paths)) for sub_child_path in sc_pbar.wrap_iter(sub_child_paths): if self.dead.is_set(): break try: sub_child_size = _calc_docker_size( sub_child_path, sub_child_path.stat().size) except excp.NotFound: sub_child_size = 0 for size in _iter_sizes_deep(sub_child_path): if self.dead.is_set(): break sub_child_size += size all_sizes.append(sub_child_size) if self.dead.is_set(): replier("Died during scanning, please" " try again next time...") else: replier( "Size of `%s` is %s" % (project, utils.format_bytes( sum(all_sizes), quote=True)))
class ListHandler(handler.TriggeredHandler): """Lists the members of a ldap group.""" required_clients = ("ldap", "github") max_before_gist = 100 handles_what = { 'message_matcher': matchers.match_or(matchers.match_slack("message"), matchers.match_telnet("message")), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('ldap list', takes_args=True), ], 'args': { 'order': [ 'group', ], 'help': { 'group': 'ldap group to list', }, 'schema': Schema({ Required("group"): All(scu.string_types(), Length(min=1)), }), }, } def _run(self, group): replier = self.message.reply_text ldap_client = self.bot.clients.ldap_client group_members = [ ldap_utils.explode_member(member) for member in ldap_client.list_ldap_group(group) ] group_members = sorted(group_members, key=lambda m: m.get("CN")) tbl_headers = ['CN', 'DC', 'OU'] rows = [] for member in group_members: row = [] for k in tbl_headers: v = member.get(k) if isinstance(v, list): v = ", ".join(v) row.append(v) rows.append(row) if len(group_members) <= self.max_before_gist: lines = [ "```", tabulate.tabulate(rows, headers=tbl_headers), "```", ] replier("\n".join(lines), threaded=True, prefixed=False) else: github_client = self.bot.clients.github_client me = github_client.get_user() to_send = {} upload_what = [ ('listing', tabulate.tabulate(rows, headers=tbl_headers)), ] for what_name, contents in upload_what: # Github has upper limit on postings to 1MB contents = _chop(contents, units.Mi) contents = contents.strip() name = what_name + ".txt" to_send[name] = github.InputFileContent(contents) if to_send: try: gist = me.create_gist(True, to_send) except Exception: LOG.warning( "Failed uploading gist for listing" " of '%s' ldap group", group) else: lines = [ "Gist url at: %s" % gist.html_url, ] replier("\n".join(lines), threaded=True, prefixed=False)
class Handler(handler.TriggeredHandler): """Triggers a workflow to downgrade/upgrade the version of this bot.""" wait_jenkins_queue_item = 0.1 config_section = 'updater' handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('upgrade', takes_args=True), trigger.Trigger('update', takes_args=True), trigger.Trigger('upgrayedd', takes_args=True), ], 'authorizer': auth.user_in_ldap_groups('admins_cloud'), 'args': { 'order': [ 'version', ], 'help': { 'version': ('version of padre container to deploy' ' (must exist in artifactory), if' ' not provided then the lastest will' ' be found'), }, } } required_clients = ('jenkins',) required_secrets = ( 'ci.artifactory.ro_account', ) def _await_confirm(self, old_version, version, changelog_lines): def _show_others_active(): active_handlers = len(self.bot.active_handlers) return ("There are %s other active" # Remove one since thats the upgrade handler itself... " handlers.") % (max(0, active_handlers - 1)) pretext_lines = [ "Newer version `%s` found!" % version, "I am older version `%s`." % old_version, ] text_lines = [] if changelog_lines: text_lines.append("Last `%s` changes:" % len(changelog_lines)) text_lines.extend(changelog_lines) attachments = [{ 'pretext': "\n".join(pretext_lines), 'mrkdwn_in': ['pretext', 'text'], "text": "\n".join(text_lines), }] self.message.reply_attachments( text="Good %s." % self.date_wrangler.get_when(), attachments=attachments, link_names=True, as_user=True, channel=self.message.body.channel, log=LOG, thread_ts=self.message.body.get("ts")) replier = functools.partial(self.message.reply_text, threaded=True, prefixed=False, thread_ts=self.message.body.ts) f = followers.ConfirmMe(confirms_what='upgrading', confirm_self_ok=True, check_func=_show_others_active) replier(f.generate_who_satisifies_message(self)) self.wait_for_transition(follower=f, wait_timeout=300, wait_start_state='CONFIRMING') if self.state == 'CONFIRMED_CANCELLED': raise excp.Cancelled def _run(self, **kwargs): replier = functools.partial(self.message.reply_text, threaded=True, prefixed=False) me = pkg_resources.get_distribution('padre') ro_account = self.bot.secrets.ci.artifactory.ro_account version = kwargs.get("version") version_provided = bool(version) project_url = self.bot.config.updater.project_url path = None if not version_provided: replier("Scanning artifactory, please wait...") newer_paths_it = uu.iter_updates(me.version, ro_account, project_url) newer_paths = sorted(newer_paths_it, key=lambda v: v.version) if newer_paths: path = newer_paths[-1].path version = str(newer_paths[-1].version) if not version: replier("No potentially upgradeable versions" " found under '%s'" % project_url) return if me.version == version: replier("Nothing to upgrade, version desired is equivalent" " to active version.") return if path is None: tmp_path = uu.check_fetch_version(version, ro_account, project_url) path = tmp_path.path self._await_confirm( me.version, version, uu.extract_changelog(path)) self.change_state("UPGRADING") jenkins_job = self.config.jenkins_job jenkins_client = self.bot.clients.jenkins_client job = jenkins_client.get_job(jenkins_job) if job is not None: replier( "Triggering upgrade to" " version `%s` by kicking job `%s`." % (version, jenkins_job)) qi = job.invoke(build_params={ 'image_tag': version, 'bot': self.bot.name or "", }) replier("Your upgrade to `%s` job" " has been queued." % version) build = None while build is None: if self.dead.is_set(): # Oh well, someone else killed us... raise excp.Dying qi.poll() build = qi.get_build() if build is None: self.dead.wait(self.wait_jenkins_queue_item) replier( "Your upgrade to `%s` job has" " started at %s. I am going into stealth/quiet" " mode until then (resurrection expected in %0.2f" " seconds), goodbye..." % (version, build.url, build.get_eta())) self.bot.quiescing = True self.bot.scheduler.shutdown(wait=False) else: raise excp.NotFound( "Jenkins upgrade job '%s' was not" " found" % jenkins_job)
class Handler(handler.TriggeredHandler): """Get stock information.""" stock_url = 'https://www.alphavantage.co/query' # NOTE: If more than 100 symbols are included, the API will # return quotes for the first 100 symbols. # # In order to fix that just split into 100 size chunks... max_per_call = 100 handles_what = { 'message_matcher': matchers.match_or( matchers.match_slack("message"), matchers.match_telnet("message") ), 'channel_matcher': matchers.match_channel(c.TARGETED), 'triggers': [ trigger.Trigger('stock', takes_args=True), ], 'args': { 'order': ['symbols'], 'converters': {}, 'schema': Schema({ Required("symbols"): All(scu.string_types(), Length(min=1)), }), 'help': { 'symbols': 'symbol(s) to lookup (comma separated)', }, 'defaults': { 'symbols': 'gddy', }, }, } def _run(self, **kwargs): symbols = kwargs.get('symbols', "") symbols = symbols.split(",") symbols = [s.strip() for s in symbols if s.strip()] seen_symbols = set() headers = ["Symbol", "Price", "Volume"] rows = [] uniq_symbols = [] for s in symbols: tmp_s = s.upper() if tmp_s in seen_symbols: continue else: uniq_symbols.append(tmp_s) seen_symbols.add(tmp_s) for batch in utils.iter_chunks(uniq_symbols, self.max_per_call): url = self.stock_url + "?" url += urllib.urlencode({ 'function': 'BATCH_STOCK_QUOTES', 'symbols': ",".join(batch), 'datatype': 'csv', 'apikey': self.config.stock.apikey, }) resp = requests.get(url) resp.raise_for_status() for row in csv.DictReader( six.StringIO(resp.content.decode('utf-8'))): rows.append([ row['symbol'], row['price'], row['volume'], ]) lines = [ "```", tabulate.tabulate(rows, headers=headers), "```", ] replier = self.message.reply_text replier("\n".join(lines), threaded=True, prefixed=False)
class Unfurler(handler.TriggeredHandler): handles_what = { 'channel_matcher': matchers.match_channel(c.BROADCAST), 'message_matcher': matchers.match_slack("message"), } template_subdir = 'gerrit' config_section = 'gerrit' config_on_off = ("unfurl.enabled", False) change_url_tpl = ("%(base)s://%(host)s/changes/%(change_id)s" "?o=CURRENT_COMMIT&o=CURRENT_REVISION") change_msg_tpl = ("`{{ change.subject }}` in" " project `{{ change.project }}`" " ({{ change.insertions }}|{{ change.deletions }}).") @classmethod def _find_matches(cls, message_text, config): matches = [] expand_for = [] try: expand_for = list(config.unfurl.expand_for) except AttributeError: pass for tmp_host in expand_for: pats = [ r"(https://|http://)" + tmp_host + r"/#/c/(\d+)[/]?", r"(https://|http://)" + tmp_host + r"/(\d+)[/]?", ] for pat in pats: for m in re.finditer(pat, message_text): match = munch.Munch({ 'host': tmp_host, 'change_id': int(m.group(2)), 'url': m.group(0), }) if m.group(1) == "https://": match.is_secure = True else: match.is_secure = False matches.append(match) return matches def _fetch_change(self, match, call_timeout): base = "http" if match.is_secure: base += "s" change_url = self.change_url_tpl % { 'base': base, 'host': match.host, 'change_id': match.change_id, } change = None try: req = requests.get(change_url, timeout=call_timeout) req.raise_for_status() except requests.RequestException: LOG.warning("Failed fetch of change %s from '%s'", match.change_id, change_url, exc_info=True) else: # Rip off the header gerrit responses start with. body_lines = req.text.split("\n")[1:] body = "\n".join(body_lines) try: change = json.loads(body) if not isinstance(change, dict): raise TypeError("%s is not a dict" % reflection.get_class_name(change)) except (ValueError, TypeError): LOG.warning( "Received invalid json content from result" " of call to %s", change_url, exc_info=True) else: LOG.debug("Received %s", change) change = munch.munchify(change) return change @classmethod def handles(cls, message, channel, config): channel_matcher = cls.handles_what['channel_matcher'] if not channel_matcher(channel): return None message_matcher = cls.handles_what['message_matcher'] if (not message_matcher(message, cls, only_to_me=False) or message.body.thread_ts): return None message_text = message.body.text_no_links matches = cls._find_matches(message_text, config) if not matches: return None return handler.ExplicitHandlerMatch(arguments={ 'matches': matches, }) @staticmethod def _find_author(change): maybe_author = [] if hasattr(change, 'owner') and change.owner: maybe_author.extend([ change.owner.get("name"), change.owner.get("email"), change.owner.get("username"), ]) rev = change.revisions[change.current_revision] if hasattr(rev, "commit") and rev.commit: committer = rev.commit.get("committer", {}) maybe_author.extend([ committer.get("name"), committer.get("email"), committer.get("username"), ]) author = None for a in maybe_author: if a: author = a break return author def _run(self, matches=None): if not matches: matches = [] seen_changes = set() replier = self.message.reply_attachments for m in matches: if self.dead.is_set(): break if m.change_id <= 0: continue m_ident = (m.host, m.change_id) if m_ident in seen_changes: continue seen_changes.add(m_ident) LOG.debug("Trying to unfurl '%s'", m.url) change = self._fetch_change(m, self.config.unfurl.call_timeout) if change is not None: attachment = { 'fallback': change.subject, 'pretext': utils.render_template(self.change_msg_tpl, {'change': change}), 'link': m.url, 'footer': "Gerrit", 'mrkdwn_in': ["pretext"], 'footer_icon': ("https://upload.wikimedia.org/" "wikipedia/commons/thumb/4/4d/" "Gerrit_icon.svg/" "52px-Gerrit_icon.svg.png"), } author = self._find_author(change) if author: attachment['author_name'] = author rev = change.revisions[change.current_revision] if rev.commit and rev.commit.message: attachment['text'] = rev.commit.message.strip() replier(channel=self.message.body.channel, log=LOG, thread_ts=self.message.body.ts, attachments=[attachment], link_names=False, as_user=True, unfurl_links=False)