Source code for bellatrix.bewitch_ami

#!/usr/bin/python
'''
This script runs an ec2 instance and then applies a configuration to it 
'''

import datetime
import logging
import os
import pkgutil
import re
import sys

import bellatrix
from bellatrix.lib import util
from bellatrix.lib import bellatrix_util

from bellatrix.lib.ec2_lib import Ec2lib

[docs]class Bewitch(): def __init__(self, key, sec, app_name, pk, reports): bellatrix_util.checkPkFile(pk) self._ec2 = Ec2lib(key, sec) #todo: take a look at: http://stackoverflow.com/questions/1389180/python-automatically-initialize-instance-variables self.key = key self.sec = sec self.pk = pk self.app_name = app_name self.CMD_OK = 0 self.define_constants() self.reports = reports self.out_file = bellatrix_util.getOutFile(__file__)
[docs] def define_constants(self): """define class constants to access ami configs""" self.AMIS = "amis" self.CMDS = "commands" self.USER = "user" self.BURN_OR_NOT = "burn_ami_at_the_end" self.SKIP_ME = "skip_me" self.ALL_CONFIGS = "all" self.ACCOUNT_LEN = 12 self.CMD = "command" self.CONFIG_DIR = "./configs" #todo get the path from the script but maybe we will want to deprecate this option self.KEY_NAME = "key_name" self.SECURITY_GROUPS = "security_groups" self.INSTANCE_TYPE = "instance_type"
[docs] def getConfigs(self): """get configurations from 'configs' directory""" #TODO: test this with the new splitted env import configs cfgpath = os.path.dirname(configs.__file__) dir = os.path.basename(cfgpath) return [name for _, name, _ in pkgutil.iter_modules([cfgpath])]
[docs] def executeCommands(self, user, dns, key, commands, config): results = [] errors = [] #use the key in the command only if we get one context = {'key': key, 'user':user, 'dns':dns, 'out_tmp': bellatrix.OUT_TMP} for c in commands: if 'dict' in str(type(c)): context['key'] = key cmd = c[self.CMD] % context else: #todo: check if we can capture the stderr and stdout and seeing the output in real time. Maybe using tee? context['key'] = ' -i ' + key cmd = "ssh -t -t -o StrictHostKeyChecking=no %(key)s %(user)s@%(dns)s '%(command)s 2>&1' " % \ dict(context.items() + {self.CMD: c}.items()) #join the two dictionaries logging.info("executing: " + cmd) res = os.system(cmd) out = "" #open(bellatrix.OUT_TMP).read() cmd_res = [cmd, out, res, config] results.append(cmd_res) logging.info("result: " + str(res) + " output: " + out) #increment errors if necessary if res != 0: errors.append(cmd_res) break logging.info("Commands executions: %s Errors: %s" % (len(commands), len(errors))) return results, errors
[docs] def printErrors(self, errors): if len(errors) < 1: return logging.warning("The following commands failed its execution:") for e in errors: logging.warning("config: %s cmd: %s exit code: %s" % (e[3], e[0], e[2])) logging.warning("last 500 chars output: %s" % e[1][-500:])
[docs] def saveReport(self, results, config): logging.info("Saving report") report_name = self.reports + os.path.sep + config + "-" + datetime.datetime.now().isoformat() + ".txt" report_name_dir = os.path.dirname(report_name) if not os.path.isdir(report_name_dir): os.makedirs(report_name_dir) with open(report_name, "w") as f: for r in results: f.write("res: %s cmd: %s out: %s \n" % (r[2], r[0], r[1])) logging.info("Report saved in: %s" % report_name)
def _processConfig(self, amis, commands, user, burn_at_the_end, key_name, security_groups, instance_type): """execute a configuration, internal method of run""" amis_burned = [] errors = [] for ami in amis: config_name = ami[1] ami = ami[0] inst, dns_name = self._ec2.startInstance(ami, instance_type, key_name, security_groups) instance_name = inst.id self._ec2.waitForConnectionReady(inst, user, self.pk, dns_name) r, e = self.executeCommands(user, inst.dns_name, self.pk, commands, config_name) self.saveReport(r, config_name) errors += e if len(e) > 0: logging.warning("There were errors while executing the commands. Not burning the instance...") else: if burn_at_the_end: try: new_ami = self._ec2.createImage(inst.id, config_name + "-" + datetime.datetime.now().isoformat(), "generated by " + self.app_name) logging.info("ami: %s is being generated for configuration: %s" % (new_ami, config_name)) amis_burned.append([new_ami, config_name]) util.writeFile(self.out_file, new_ami + "," + instance_name + os.linesep) except: logging.exception("Problem burning image: %s with instance: %s" % (config_name, inst.id)) return amis_burned, errors
[docs] def run(self, config): """execute a configuration""" amis_burned = [] errors = [] if config == self.ALL_CONFIGS: configs = self.getConfigs() else: configs = [os.path.splitext(config)[0]] sys.path = [util.getCurDir()] + sys.path for cfg in configs: logging.info("processing: " + cfg + " in: " + os.getcwd()) if config == self.ALL_CONFIGS: c = __import__(os.path.basename(self.CONFIG_DIR) + "." + cfg) mod = "c." + cfg + "." else: c = __import__(cfg) mod = "c." skip_me = eval(mod + self.SKIP_ME) if skip_me: logging.info("skipping execution of config: %s due to its configuration skip_me=true" % cfg) continue amis = eval(mod + self.AMIS) commands = eval(mod + self.CMDS) user = eval(mod + self.USER) burn_at_the_end = eval(mod + self.BURN_OR_NOT) a,e = self._processConfig(amis, commands, user, burn_at_the_end, eval(mod + self.KEY_NAME), eval(mod + self.SECURITY_GROUPS), eval(mod + self.INSTANCE_TYPE)) amis_burned += a errors += e self.printErrors(errors) logging.info("total of ami's burned:%s" % len(amis_burned)) for a in amis_burned: logging.info(str(a)) return 0 if len(errors)==0 else 1
[docs]def run(configuration=None): r = Bewitch(bellatrix_util.getKey(), bellatrix_util.getSecret(), bellatrix.APP, bellatrix_util.getPrivateKey(), bellatrix_util.getReportsDir()) config = r.ALL_CONFIGS if (not configuration) else configuration exit_code = r.run(config) return exit_code
if __name__ == '__main__': sys.exit(run(sys.argv))