Example #1
0
class EnvList:
    """
    Can list all the envs and apps.
  """
    logger = Logger("EnvList")

    def __init__(self, script_settings):
        self.script_settings = script_settings

    def list_envs(self):
        self.logger.info('[%s]' % ', '.join(map(str, self.get_list_of_envs())))

    def get_list_of_envs(self):
        envs = os.listdir(self.script_settings.get_env_configs_dir())
        envs.remove("global-configuration.cfg")
        return envs

    def list_projects(self, env):
        projects = os.listdir(
            os.path.join(self.script_settings.get_env_configs_dir(), env))
        return [
            project.replace(".cfg", "") for project in projects
            if project.endswith(".cfg")
        ]

    def print_envs_with_projects(self):
        for env in self.list_envs():
            print env
            for project in self.list_projects(env):
                print "  |_" + project
Example #2
0
class EnvList:
    """ Can list all the envs and apps. """
    logger = Logger("EnvList")

    def __init__(self, global_configs_dir):
        """ :param str global_configs_dir: the environments/ dir """
        self.global_configs_dir = global_configs_dir

    def list_envs(self):
        self.logger.info('[%s]' % ', '.join(map(str, self.get_list_of_envs())))

    def get_list_of_envs(self):
        envs = os.listdir(self.global_configs_dir
                          )  # dir contains folders with envs configuration
        global_configuration_file = 'global-configuration.cfg'
        if global_configuration_file in envs:
            envs.remove(global_configuration_file)
        return envs

    def list_projects(self, env):
        projects = os.listdir(os.path.join(self.global_configs_dir, env))
        if ScriptSettings.ENV_CONFIG_FILE_NAME in projects:
            projects.remove(ScriptSettings.ENV_CONFIG_FILE_NAME)
        return [
            project.replace(".cfg", "") for project in projects
            if project.endswith(".cfg")
        ]

    def print_envs_with_projects(self):
        for env in self.get_list_of_envs():
            print env
            for project in self.list_projects(env):
                print "  |_" + project
Example #3
0
class DeployCommand:
    NEXUS_URL = 'http://repo.jtalks.org/content/repositories/deployment-pipeline/deployment-pipeline/'
    logger = Logger('DeployCommand')

    def __init__(self, jtalks_artifacts, old_nexus, tomcat, sanity_test,
                 backuper, scriptsettings):
        """
        :param jtalks.ScriptSettings.ScriptSettings scriptsettings: config files to be deployed along with the app
        :param jtalks.Nexus.JtalksArtifacts jtalks_artifacts: downloads JTalks artifacts from Nexus
        :param jtalks.OldNexus.Nexus old_nexus: used to download artifacts if it's old and is kept in old repo
        :param jtalks.Tomcat.Tomcat tomcat: manages tomcat
        :param jtalks.sanity.SanityTest.SanityTest sanity_test: runs the tests after an app is deployed
        :param jtalks.backup.Backuper.Backuper backuper: backs up DB, tomcat
        """
        self.scriptsettings = scriptsettings
        self.tomcat = tomcat
        self.jtalks_artifacts = jtalks_artifacts
        self.sanity_test = sanity_test
        self.old_nexus = old_nexus
        self.backuper = backuper

    def deploy(self, project, build, app_final_name, plugins=[]):
        self.__validate_params_and_raise__(project, build)
        plugin_files = []
        try:
            gav, filename = self.jtalks_artifacts.download_war(project, build)
            plugin_files = self.jtalks_artifacts.download_plugins(
                project, gav.version, plugins)
        except BuildNotFoundException:
            if project == 'jcommune' and len(plugins) != 0:
                self.logger.warn(
                    'Looks like a pretty old build is requested, plugins will not be installed for it '
                    'even though they were requested - at those times we did not upload plugins to '
                    'binary storage so they are not saved. To get plugins you would either need to find'
                    ' a required revision and build them from source. Sorry for that. But hey, this is'
                    ' a very old build of JCommune, use a newer version!')
            filename = self.old_nexus.download_war(project)
        self.tomcat.stop()
        self.backuper.back_up_dir(self.tomcat.tomcat_location)
        self.backuper.back_up_db()
        self.tomcat.move_to_webapps(filename, app_final_name)
        if project == 'jcommune':
            self.jtalks_artifacts.deploy_plugins(
                self.scriptsettings.get_plugins_dir(), plugin_files)
        self.scriptsettings.deploy_configs()
        self.tomcat.start()
        self.sanity_test.check_app_started_correctly()

    def __validate_params_and_raise__(self, project, build):
        if build is None:
            self.logger.error(
                'Build number was not specified, see [{0}] to get list of builds',
                self.NEXUS_URL)
            raise RuntimeError
        if project not in ['jcommune', 'poulpe', 'antarcticle']:
            self.logger.error(
                'A correct project should be specified: [poulpe, jcommune, antarcticle]. Actual: [{0}]',
                project)
            raise RuntimeError
Example #4
0
class Main:
    def __init__(self):
        self.logger = Logger("Main")

    def main(self, args, options):
        command = args[0]
        if command == "version":
            print __version__
            exit(0)
        script_settings = ScriptSettings(options)
        app_context = ApplicationContext(script_settings)
        if script_settings.grab_envs == "true":
            app_context.environment_config_grabber().grab_jtalks_configs()
            # recreating them after configs were updated
            script_settings = ScriptSettings(options)
            app_context = ApplicationContext(script_settings)
        try:
            if command == "deploy":
                LibVersion().log_lib_versions()
                app_context.deploy_command().deploy(
                    script_settings.project,
                    script_settings.build,
                    script_settings.get_app_final_name(),
                    script_settings.get_plugins(),
                )
            elif command == "upload-to-nexus":
                LibVersion().log_lib_versions()
                app_context.old_nexus().upload_war("pom.xml")
            elif command == "list-envs":
                app_context.env_list().list_envs()
            elif command == "load-db-from-backup":
                LibVersion().log_lib_versions()
                app_context.load_db_from_backup().load()
            else:
                error = (
                    "Command was not recognized, you can use: deploy, list-envs, load-db-from-backup. "
                    "Also see jtalks -h"
                )
                self.logger.error(error)
                raise RuntimeError(error)
        except:
            self.logger.error("Program finished with errors")
            if options.debug:
                print ("Root cause: %s" % traceback.format_exc())
                sys.exit(1)
Example #5
0
class SanityTest:
    """
      Tests that application was deployed correctly without errors. For these purposes it opens some pages and determines
      whether they return HTML. If let's say they return HTTP 500, then the test failed. It has to break CI builds.
    """
    HOST = "127.0.0.1"
    logger = Logger("SanityTest")
    port = None
    app_name = None

    def __init__(self, tomcat_port, app_name, sanity_test_timeout_sec=120, sleep_sec=30):
        """
         @param tomcat_port - an HTTP port to access the web server where application is
         @param sleep_sec - the amount of time tests ignore error responses as deployment failure. This is needed because
          first tomcat may not start quickly and therefore the response Connection Refused will be immediate. Thus when we
          send requests, first we should treat error messages as possible responses. After this sleep time error responses
          are considered as failed deployment.
        """
        self.port = int(tomcat_port)
        self.app_name = app_name
        self.sanity_test_timeout_sec = sanity_test_timeout_sec
        self.sleep_sec = sleep_sec
        if app_name == "ROOT":
            self.app_name = ""

    def check_app_started_correctly(self):
        request_address = "http://{0}:{1}/{2}".format(self.HOST, self.port, self.app_name)
        tests_sleep_end = datetime.datetime.now() + datetime.timedelta(seconds=self.sleep_sec)
        response = None
        attempt_counter = 0
        while tests_sleep_end > datetime.datetime.now():
            attempt_counter += 1
            self.logger.info(
                "[Attempt #{0}] Running sanity tests to check whether application started correctly and responds back..",
                attempt_counter)
            self.logger.info("[Attempt #{0}] Connecting to {1}", attempt_counter, request_address)
            try:
                response = requests.get(request_address, timeout=self.sanity_test_timeout_sec)
            except ConnectionError:
                self.logger.info("Sleeping for 5 sec..")
                sleep(5)  # so that we don't connect too often
                continue
            if response.status_code in [200, 201]:
                break
            else:
                self.logger.info("Error response, got {0} HTTP status. App server might still be booting.",
                                 response.status_code)
        if not response or response.status_code not in [200, 201]:
            self.logger.error('After {0} no successful response was received from the app. Finishing by timeout {1}',
                              attempt_counter, self.sanity_test_timeout_sec)
            if response:
                self.logger.error("Last time while accessing main page, app answered with error: [{0} {1} {2}]",
                                  response.status_code, response.reason, response.text)
            else:
                self.logger.error("App Server did not even get up!")
            raise SanityCheckFailedException("Sanity check failed")
        self.logger.info("Sanity check passed: [{0} {1}]", response.status_code, response.reason)
class EnvironmentConfigGrabber:
    logger = Logger("EnvironmentConfigGrabber")

    def __init__(self, env_configs_root, temp_dir):
        self.env_configs_root = env_configs_root
        self.temp_dir = temp_dir
        self.clone_repo_to = temp_dir + 'environments'
        self.grabbed_configs_location = self.clone_repo_to + "/configs"

    def grab_jtalks_configs(self):
        try:
            self.__remove_previous_git_folder__()
            self.__create_jtalks_temp_dir__()
            repo.Repo.clone_from('[email protected]:environments',
                                 self.clone_repo_to)
            self.__copy_grabbed_configs_into_work_dir__()
        except GitCommandError:
            self.logger.warn(
                "You don't have access to JTalks repo with environment configs. You may want to use your own "
                +
                "local environment. Create a folder with env name in configs directory. If you think you "
                +
                "need access to JTalks internal envs (including UAT, DEV, PREPROD, PROD envs), "
                + "you should write a request to [email protected].")

    def __remove_previous_git_folder__(self):
        try:
            if os.path.exists(self.clone_repo_to):
                self.logger.info("Removing {0} directory if it was there",
                                 self.clone_repo_to)
                shutil.rmtree(self.clone_repo_to)
        except OSError as e:
            if e.errno is not 2:  #No such file or directory
                self.logger.warn(e.message)

    def __copy_grabbed_configs_into_work_dir__(self):
        grabbed_dirs_and_files = os.listdir(self.grabbed_configs_location)
        for next_grabbed_file_or_dir in grabbed_dirs_and_files:
            destination_file = self.env_configs_root + next_grabbed_file_or_dir
            if os.path.exists(destination_file):
                self.logger.info(
                    "{0} will be overwritten by newer version from git repo",
                    destination_file)
                self.__delete_file_or_dir__(destination_file)
            shutil.move(
                self.grabbed_configs_location + "/" + next_grabbed_file_or_dir,
                destination_file)

    def __delete_file_or_dir__(self, destination_file):
        if os.path.isfile(destination_file):
            os.remove(destination_file)
        else:
            shutil.rmtree(destination_file)

    def __create_jtalks_temp_dir__(self):
        if not os.path.exists(self.temp_dir):
            os.mkdir(self.temp_dir)
Example #7
0
class DbOperations:
    """ Base class for working with database """
    logger = Logger("DbBase")

    def __init__(self, dbsettings):
        """ :param jtalks.ScripSettings.DbSettings dbsettings: settings """
        self.dbsettings = dbsettings

    def connect_to_database(self):
        """ Connects to the specified database """
        self.logger.info("Connecting to [{0}] with user [{1}]",
                         self.dbsettings.host, self.dbsettings.user)
        self.connection = MySQLdb.connect(host=self.dbsettings.host,
                                          user=self.dbsettings.user,
                                          passwd=self.dbsettings.password)
        self.cursor = self.connection.cursor()

    def close_connection(self):
        """ Closes open connection to the database """
        self.connection.close()

    def recreate_database(self):
        """ Deletes the specified database and create it again """
        self.logger.info("Dropping and creating database from scratch: [{0}]",
                         self.dbsettings.name)
        self.cursor.execute('DROP DATABASE IF EXISTS ' + self.dbsettings.name)
        self.cursor.execute('CREATE DATABASE ' + self.dbsettings.name)

    def backup_database(self, backup_path):
        """ Backups database to given backupPath """
        self.logger.info("Backing up database [{0}] to [{1}] using user [{2}]",
                         self.dbsettings.name, backup_path,
                         self.dbsettings.user)
        dump_command = "mysqldump -u'{0}'".format(self.dbsettings.user)
        if self.dbsettings.password:
            dump_command += " -p'{0}'".format(self.dbsettings.password)
        dump_command += " '{0}' > '{1}/{0}.sql'".format(
            self.dbsettings.name, backup_path)
        self.logger.info("Dumping DB: [{0}]",
                         dump_command.replace(self.dbsettings.password, "***"))
        os.popen(
            dump_command.format(self.dbsettings.user, self.dbsettings.password,
                                self.dbsettings.name, backup_path)).read()
        self.logger.info("Database backed up [{0}]".format(
            self.dbsettings.name))

    def restore_database_from_file(self, backupPath):
        """ Restores database from file specified in backupPath """
        self.logger.info("Loading a db dump from [{0}/{1}.sql]", backupPath,
                         self.dbsettings.name)
        self.recreate_database()
        os.popen("mysql -u'{0}' -p'{1}' '{2}' < '{3}/{2}.sql'".format(
            self.dbsettings.user, self.dbsettings.password,
            self.dbsettings.name, backupPath)).read()
        self.logger.info("Database restored [{0}]".format(
            self.dbsettings.name))
Example #8
0
class SSH:
    env = None
    config = None
    sftpTransport = None
    sftpClient = None
    sftpHost = None
    sftpPort = None
    sftpUser = None
    sftpPass = None
    sftpBackupArchive = None
    sftpBackupFileName = None
    logger = Logger("SSH")

    def __init__(self, script_settings):
        self.env = script_settings.env
        self.config = ConfigParser.ConfigParser()
        self.config.read(script_settings.get_env_configs_dir() + self.env +
                         "/ssh.cfg")
        self.sftpHost = self.config.get('sftp', 'sftp_host')
        self.sftpPort = self.config.get('sftp', 'sftp_port')
        self.sftpUser = self.config.get('sftp', 'sftp_user')
        self.sftpPass = self.config.get('sftp', 'sftp_pass')
        self.sftpBackupArchive = self.config.get('sftp', 'sftp_backup_archive')
        self.sftpBackupFileName = self.config.get('sftp',
                                                  'sftp_backup_filename')

    def download_backup_prod_db(self):
        self.sftp_connection()
        attrList = self.sftpClient.listdir_attr('.')
        attrTimeTmp = 0
        dirWithLastUpdate = None
        for attr in attrList:
            if attrTimeTmp < attr.st_mtime:
                attrTimeTmp = attr.st_mtime
                dirWithLastUpdate = attr.filename
        self.sftpClient.get(dirWithLastUpdate + '/' + self.sftpBackupArchive,
                            './' + self.sftpBackupArchive)
        os.popen('bunzip2 -d ' + self.sftpBackupArchive)
        self.sftp_connection_close()

    def sftp_connection_close(self):
        self.sftpClient.close()
        self.sftpTransport.close()

    def sftp_connection(self):
        self.logger.info("Getting a DB backup from {0}", self.sftpHost)
        self.sftpTransport = paramiko.Transport(
            (self.sftpHost, int(self.sftpPort)))
        self.sftpTransport.connect(username=self.sftpUser,
                                   password=self.sftpPass)
        self.sftpClient = paramiko.SFTPClient.from_transport(
            self.sftpTransport)

    def remove_backup_prod_db_file(self):
        os.popen('rm -rf ' + self.sftpBackupFileName)
Example #9
0
class Main:
    def __init__(self):
        self.logger = Logger('Main')

    def main(self, args, options):
        command = args[0]
        if command == 'version':
            print __version__
            exit(0)
        script_settings = ScriptSettings(options)
        app_context = ApplicationContext(script_settings)
        if script_settings.grab_envs == "true":
            app_context.environment_config_grabber().grab_jtalks_configs()
            # recreating them after configs were updated
            script_settings = ScriptSettings(options)
            app_context = ApplicationContext(script_settings)
        try:
            if command == 'deploy':
                LibVersion().log_lib_versions()
                app_context.deploy_command().deploy(
                    script_settings.project, script_settings.build,
                    script_settings.get_app_final_name(),
                    script_settings.get_plugins())
            elif command == "upload-to-nexus":
                LibVersion().log_lib_versions()
                app_context.old_nexus().upload_war('pom.xml')
            elif command == "list-envs":
                app_context.env_list().list_envs()
            elif command == 'load-db-from-backup':
                LibVersion().log_lib_versions()
                app_context.load_db_from_backup().load()
            else:
                error = 'Command was not recognized, you can use: deploy, list-envs, load-db-from-backup. ' \
                        'Also see jtalks -h'
                self.logger.error(error)
                raise RuntimeError(error)
        except:
            self.logger.error("Program finished with errors")
            if options.debug:
                print("Root cause: %s" % traceback.format_exc())
                sys.exit(1)
Example #10
0
class LibVersion:
    logger = Logger("LibVersions")

    def log_lib_versions(self):
        self.logger.info("python={0}", sys.version_info)
        self.__log_lib_version("requests")
        self.__log_lib_version("GitPython")
        self.__log_lib_version("mock")

    def __log_lib_version(self, libname):
        self.logger.info("{0}={1}", libname,
                         pkg_resources.get_distribution(libname).version)
Example #11
0
class Nexus:
    """
    A class to work with Nexus (upload, download, search). Note, that for all the operations we need a build number
    to be passed into the constructor. Other properties may or may not be required (some of them can be determined,
    like project name and version by pom.xml).
    """
    base_url = "http://repo.jtalks.org/content/repositories/deployment-pipeline/"
    logger = Logger("OldNexus")

    def __init__(self, build_number):
        self.build_number = build_number

    def upload_war(self, pom_file_location):
        """
          Uploads a war to the Nexus. The path to pom.xml is acceptaced as an argument, by this path
          we also determean where war file is placed.
        """
        pom = PomFile(pom_file_location)
        artifact_version = pom.version()
        artifact_id = pom.artifact_id()

        maven_deploy_command = (
            "mvn deploy:deploy-file -Durl={2} " +
            "-DrepositoryId=deployment-pipeline -DgroupId=deployment-pipeline -DartifactId={0} -Dpackaging=war "
            + "-Dfile={0}-view/{0}-web-view/target/{0}.war -Dversion={1}"
        ).format(artifact_id, artifact_version, self.base_url)
        print maven_deploy_command

        return_code = os.system(maven_deploy_command)
        if return_code != 0:
            self.logger.error("Maven returned error code: " + str(return_code))
            raise Exception("Maven returned error code: " + str(return_code))

    def download_war(self, project):
        self.logger.info("Looking up build #{0} for {1} project",
                         self.build_number, project)
        war_url = self.get_war_url(project, self.build_number)
        self.logger.info("Downloading artifact: [{0}]", war_url)
        urllib.urlretrieve(war_url, project + ".war")
        return project + '.war'

    def get_war_url(self, project, build_number):
        group_id = "deployment-pipeline/"
        artifact_version_url = NexusPageWithVersions().parse(
            self.base_url + group_id + project).version(build_number)
        # get version by URL (last part is something like /jcommune/12.3.123/)
        artifact_version = artifact_version_url.rpartition(project +
                                                           "/")[2].replace(
                                                               "/", "")
        return artifact_version_url + '{0}-{1}.war'.format(
            project, artifact_version)
Example #12
0
class TomcatServerXml:
  """
    Parses $TOMCAT_HOME/conf/server.xml file and returns attributes and tag values.
  """
  logger = Logger("TomcatServerXml")
  HTTP_PROTOCOL = "HTTP/1.1"
  tree = None

  def __init__(self, xml_element):
    """
      Creates a file from xml element object
      @param xml_element server.xml already parsed into xml.etree.Element
    """
    self.tree = xml_element

  @staticmethod
  def fromfile(filename):
    """
      Parses the specified server.xml
    """
    Logger("TomcatServerXml").info("Parsing [{0}] to obtain information about Tomcat", filename)
    return TomcatServerXml(ElementTree.parse(filename))

  @staticmethod
  def fromstring(file_content):
    """
      Creates object from string instead of from file
    """
    return TomcatServerXml(ElementTree.fromstring(file_content))

  def http_port(self):
    """
      Parses server.xml and obtains a Connector with protocol="HTTP/1.1". Returns its port.
    """
    http_connectors = self.tree.findall("./Service/Connector")
    http_connector = None
    for connector in http_connectors:
      if connector.get("protocol") == self.HTTP_PROTOCOL:
        http_connector = connector
        break
    if http_connector is None:
      error_message = "$TOMCAT_HOME/conf/server.xml didn't contain Connector with HTTP/1.1 protocol"
      self.logger.error(error_message)
      raise WrongConfigException(error_message)
    return http_connector.get("port")
Example #13
0
class Backuper:
    """
      Responsible for backing up artifacts like war files, config files, as well as DB backups.
      Stores backups in the folder and allows to restore those backups from it.
    """
    logger = Logger("Backuper")
    BACKUP_FOLDER_DATE_FORMAT = "%Y_%m_%dT%H_%M_%S"

    def __init__(self, root_backup_folder, db_operations):
        """
        :param str root_backup_folder: a directory to put backups to, it may contain backups from different envs,
            thus a folder for each env will be created
        :param jtalks.db.DbOperations.DbOperations db_operations: instance of DbOperations
        """
        self.backup_folder = self.create_folder_to_backup(root_backup_folder)
        self.db_operations = db_operations

    def create_folder_to_backup(self, backup_folder):
        """
          We don't just put backups into a backup folder, we're creating a new folder there
          with current date so that our previous backups are kept there. E.g.:
          '/var/backups/prod/20130302T195924'
          :param str backup_folder: the root folder to create folders with datetime names for each backup
        """
        now = datetime.now().strftime(self.BACKUP_FOLDER_DATE_FORMAT)
        final_backup_folder = os.path.join(backup_folder, now)
        if not os.path.exists(final_backup_folder):
            self.logger.info("Creating a folder to store back ups: [{0}]", final_backup_folder)
            os.makedirs(final_backup_folder)
        return final_backup_folder

    def back_up_dir(self, folder_path):
        if os.path.exists(folder_path):
            head, folder_name = os.path.split(folder_path)
            backup_dst = os.path.join(self.backup_folder, folder_name)
            self.logger.info('Backing up [{0}] to [{1}]', folder_path, backup_dst)
            shutil.copytree(folder_path, backup_dst)
        else:
            backup_dst = None
            self.logger.info("There was no previous folder [{0}], so nothing to backup", folder_path)
        return backup_dst

    def back_up_db(self):
        self.db_operations.backup_database(self.backup_folder)
Example #14
0
class DbSettings:
    """
  Class keeping connection settings to the database
  """
    logger = Logger("DbSettings")

    def __init__(self, project, config_file_location):
        """
    Creates connection settings object with given project name
    """
        self.dbHost = None
        self.dbUser = None
        self.dbPass = None
        self.dbName = None
        self.dbPort = None
        self.project = project.upper()
        self.parse_config(config_file_location)

    def parse_config(self, config_file_path):
        """
    Parses given config file and gets information about connection settings.
    """
        if not os.path.exists(config_file_path):
            self.logger.error("Config file not found: [{0}]", config_file_path)
            raise ValueError("Config file not found: " + config_file_path)
        configs_doc = parse(config_file_path)
        env_elements = configs_doc.getElementsByTagName('Environment')

        for element in env_elements:
            name = element.getAttribute("name")
            value = element.getAttribute("value")

            if name == (self.project + "_DB_USER"):
                self.dbUser = value
            elif name == (self.project + "_DB_PASSWORD"):
                self.dbPass = value
            elif name == (self.project + "_DB_URL"):
                jdbc_pattern = 'mysql://(.*?):?(\d*)/(.*?)\?'
                (host, port,
                 database) = re.compile(jdbc_pattern).findall(value)[0]
                self.dbHost = host
                self.dbName = database
                self.dbPort = port
Example #15
0
class Nexus:
    """
    A class to work with Nexus (upload, download, search). Note, that for all the operations we need a build number
    to be passed into the constructor. Other properties may or may not be required (some of them can be determined,
    like project name and version by pom.xml).
    """
    logger = Logger("Nexus")

    def __init__(self, nexus='http://repo.jtalks.org/content/repositories/'):
        self.nexus_url = nexus

    def download(self, repo, gav, tofile_path):
        """
        :param repo - str
        :param gav - Gav
        :param tofile_path str
        """
        url = self.nexus_url + repo + '/' + gav.to_repo_path()
        self.logger.info('Downloading artifact: [{0}]', url)
        urllib.urlretrieve(url, tofile_path)
Example #16
0
class DeployToTomcatFacade:
  NEXUS_URL = "http://repo.jtalks.org/content/repositories/deployment-pipeline/deployment-pipeline/"
  logger = Logger("DeployToTomcatFacade")

  def __init__(self, application_context):
    self.app_context = application_context

  def deploy(self):
    script_settings = self.app_context.script_settings
    self.__raise_if_settings_not_specified__(script_settings)

    if script_settings.grab_envs == "true":
      self.app_context.environment_config_grabber().grab_jtalks_configs()
    self.app_context.nexus().download_war(project=self.app_context.script_settings.project)
    self.app_context.tomcat().deploy_war()
    self.app_context.sanity_test().check_app_started_correctly()

  def __raise_if_settings_not_specified__(self, script_settings):
    if script_settings.build is None:
      self.logger.error("Build number was not specified, see [{0}] to get list of builds", self.NEXUS_URL)
      raise RuntimeError
    if script_settings.project not in ["jcommune", "poulpe", "antarcticle"]:
      self.logger.error("A correct project should be specified: [poulpe, jcommune, antarcticle]")
      raise RuntimeError
Example #17
0
 def fromfile(filename):
   """
     Parses the specified server.xml
   """
   Logger("TomcatServerXml").info("Parsing [{0}] to obtain information about Tomcat", filename)
   return TomcatServerXml(ElementTree.parse(filename))
Example #18
0
class Tomcat:
    """ Class for deploying and backing up Tomcat applications """

    logger = Logger("Tomcat")

    def __init__(self, tomcat_location):
        """ :param str tomcat_location: location of the tomcat root dir """
        self.tomcat_location = tomcat_location
        if self.tomcat_location and os.path.exists(tomcat_location):
            self.logger.info('Tomcat location: [{0}]', tomcat_location)
        else:
            self.logger.warn(
                'Tomcat location was not set or it does not exist: [{0}]',
                tomcat_location)

    def stop(self):
        """ Stops the Tomcat server if it is running """
        if not os.path.exists(self.tomcat_location):
            raise TomcatNotFoundException(
                'Could not found tomcat: [{0}]'.format(self.tomcat_location))
        stop_command = 'pkill -9 -f {0}'.format(
            os.path.abspath(self.tomcat_location))
        self.logger.info('Killing tomcat [{0}]', stop_command)
        # dunno why but return code always equals to SIGNAL (-9 in this case), didn't figure out how to
        # distinguish errors from this
        subprocess.call([stop_command], shell=True, stdout=PIPE, stderr=PIPE)

    def start(self):
        """ Starts the Tomcat server """
        if not os.path.exists(self.tomcat_location):
            raise TomcatNotFoundException(
                'Could not found tomcat: [{0}]'.format(self.tomcat_location))
        startup_file = self.tomcat_location + "/bin/startup.sh"
        self.logger.info("Starting Tomcat [{0}]", startup_file)
        pipe = subprocess.Popen(['/bin/bash', startup_file],
                                shell=False,
                                stdout=PIPE,
                                stderr=PIPE)
        out, err = pipe.communicate()
        if pipe.returncode != 0:
            error = 'Could not start Tomcat, return code: {0}'.format(
                pipe.returncode)
            self.logger.error(error)
            self.logger.error(out)
            self.logger.error(err)
            raise CouldNotStartTomcatException(error)

    def move_to_webapps(self, src_filepath, appname):
        """
        Moves application war-file to 'webapps' Tomcat sub-folder
        :param str src_filepath: to get artifact from
        :param str appname: the name of the webapp to be deployed
        """
        webapps_location = os.path.join(self.tomcat_location, 'webapps')
        final_app_location = os.path.join(webapps_location, appname)
        self.logger.info('Putting new war file to Tomcat: [{0}]',
                         final_app_location)
        if not os.path.exists(webapps_location):
            error = 'Tomcat webapps folder was not found in [{0}], configuration must have been wrong. ' \
                    'Please configure correct Tomcat location. Current location contains: {1}' \
                .format(self.tomcat_location, os.listdir(self.tomcat_location))
            self.logger.error(error)
            raise TomcatNotFoundException(error)
        self._remove_previous_app(final_app_location)
        shutil.move(src_filepath, final_app_location + '.war')
        return final_app_location + '.war'

    def _remove_previous_app(self, app_location):
        if os.path.exists(app_location):
            self.logger.info("Removing previous app: [{0}]", app_location)
            shutil.rmtree(app_location)
        else:
            self.logger.info(
                "Previous application was not found in [{0}], thus nothing to remove",
                app_location)

        war_location = app_location + ".war"
        if os.path.exists(war_location):
            self.logger.info("Removing previous war file: [{0}]", war_location)
            os.remove(war_location)
Example #19
0
class ScriptSettings:
    logger = Logger("ScriptSettings")
    TEMP_DIR_NAME = 'temp'
    BACKUPS_DIR_NAME = 'backups'
    ENVS_DIR_NAME = 'environments'
    GLOBAL_ENV_CONFIG_FILE_NAME = 'global-configuration.cfg'
    ENV_CONFIG_FILE_NAME = 'environment-configuration.cfg'

    def __init__(self,
                 options_passed_to_script,
                 workdir=os.path.expanduser('~/.jtalks')):
        """ :param optparse.Values options_passed_to_script: thins that are passed with -f or --flags """
        self.env = options_passed_to_script.env
        self.build = options_passed_to_script.build
        self.project = options_passed_to_script.project
        self.grab_envs = options_passed_to_script.grab_envs
        self.sanity_test_timeout_sec = int(
            options_passed_to_script.sanity_test_timeout_sec)
        self.package_version = __version__

        self.work_dir = workdir
        self.temp_dir = os.path.join(self.work_dir, self.TEMP_DIR_NAME)
        self.backups_dir = os.path.join(self.work_dir, self.BACKUPS_DIR_NAME)
        self.global_configs_dir = os.path.join(self.work_dir,
                                               self.ENVS_DIR_NAME)
        self.env_configs_dir = os.path.join(self.global_configs_dir, self.env)

        self.global_config_path = os.path.join(
            self.global_configs_dir, self.GLOBAL_ENV_CONFIG_FILE_NAME)
        self.env_config_path = os.path.join(self.env_configs_dir,
                                            self.ENV_CONFIG_FILE_NAME)
        self.project_config_path = os.path.join(self.env_configs_dir,
                                                self.project + '.cfg')

        self.props = self._read_properties()

    def log_settings(self):
        self.logger.info(
            'Script Settings: project=[{0}], env=[{1}], build number=[{2}], sanity test timeout=[{3}], '
            'package version=[{4}]', self.project, self.env, self.build,
            self.sanity_test_timeout_sec, self.package_version)
        self.logger.info("Environment configuration: [{0}]",
                         self.env_configs_dir)

    def create_work_dir_if_absent(self):
        self._create_dir_if_absent(self.work_dir)
        self._create_dir_if_absent(self.env_configs_dir)
        self._create_dir_if_absent(self.backups_dir)

    def _create_dir_if_absent(self, directory):
        if not os.path.exists(directory):
            self.logger.info("Creating directory [{0}]", directory)
            os.makedirs(directory)

    def get_tomcat_port(self):
        return int(self.props.get('tomcat_http_port', 0))

    def get_tomcat_location(self):
        """ Gets value of the tomcat home from [project].cfg file related to particular env and project """
        return self.props.get('tomcat_location', None)

    def get_app_final_name(self):
        """ Gets the name of the application to be deployed (even if it's Poulpe, it can be deployed as ROOT.war). """
        return self.props.get('app_final_name', self.project)

    def get_plugins(self):
        if 'app_plugins' in self.props and self.props['app_plugins']:
            return self.props['app_plugins'].split(',')
        else:
            return []

    def get_plugins_dir(self):
        if 'app_plugins_dir' in self.props and self.props['app_plugins_dir']:
            return self.props['app_plugins_dir']
        return None

    def get_app_file_mapping(self):
        """
        :return dict: mapping of the src file name (located either in environments/ or in environments/env folder)
            without folder part and the destination file path (where to put those files on the server)
        """
        file_mapping = {}
        app_files_section = 'app-files_'
        for key in self.props.keys():
            if key.startswith(app_files_section):
                src_filename = key.replace(app_files_section, '')
                dst_filepath = self.props[key]
                file_mapping[src_filename] = dst_filepath
        return file_mapping

    def get_db_settings(self):
        """ :return DbSettings: db settings """
        return DbSettings.build(self.props)

    def deploy_configs(self):
        mapping = self.get_app_file_mapping()
        for key in mapping:
            src_filepath = os.path.join(self.env_configs_dir, key)
            if not os.path.exists(src_filepath):
                src_filepath = os.path.join(self.global_configs_dir, key)
            if not os.path.exists(src_filepath):
                self.logger.info(
                    'File {0} did not exist, skipping its deployment', key)
                continue
            dst_filepath = mapping[key]
            self.logger.info('Putting file [{0}]', dst_filepath)
            if not os.path.exists(os.path.dirname(dst_filepath)):
                self.logger.info('Creating dir [{0}] to place the file',
                                 os.path.dirname(dst_filepath))
                os.makedirs(os.path.dirname(dst_filepath))
            dst_file = file(dst_filepath, 'w')
            for line in open(src_filepath).readlines():
                dst_file.write(self._resolve_placeholder(self.props, line))
            dst_file.close()

    def _read_properties(self):
        """
        Reads props from global, env and project configs, then returns them as map with `section_option=value`.
        Values may contain placeholders referencing other options and special options like `${project}` & `${env}`
        """
        configs = [
            os.path.abspath(self.global_config_path),
            os.path.abspath(self.env_config_path),
            os.path.abspath(self.project_config_path)
        ]
        props = {}
        config = ConfigParser()
        for config_file in configs:
            abs_path = os.path.abspath(config_file)
            if os.path.exists(abs_path):
                self.logger.info('Reading config file [{0}]', abs_path)
                config.read(abs_path)
            else:
                self.logger.info(
                    'Could not read config file as it does not exist: [{0}]',
                    abs_path)
        for section in config.sections():
            for option in config.options(section):
                props[section + '_' + option] = config.get(section, option)
        with_replaced_placeholders = {}
        for key in props.keys():
            with_replaced_placeholders[key] = self._resolve_placeholder(
                props, props[key])
        return with_replaced_placeholders

    def _resolve_placeholder(self, props, value, n_of_trial=0):
        if n_of_trial > 10:
            self.logger.warn(
                'Could not resolve all placeholders in property value: {0}. Leaving it as is.',
                value)
            return value
        value = value.replace("${env}",
                              self.env).replace("${project}", self.project)
        if value.find('${') != -1:
            for key in props.keys():
                value = value.replace('${' + key + '}', props[key])
        if value.find('${') != -1:
            value = self._resolve_placeholder(props, value, n_of_trial + 1)
        return value
 def test_deploy_plugins_must_not_clean_prev_plugins_if_not_jc_is_deployed(self):
     logger = Logger("plugins_must_not_clean_prev_plugins_if_not_jc_is_deployed")
     jcsettings = self._scriptsettings(build=2843)
     antsettings = self._scriptsettings(build=574, project="antarcticle")
     try:
         logger.info("Given JCommune was deployed with one of its plugins")
         self._deploy(jcsettings)
         logger.info("When deploying Antarcticle")
         self._deploy(antsettings)
     except:
         logger.error("Oops, error happened during deployment")
         self._read_log_if_available("/home/jtalks/tomcat/logs/catalina.out")
         self._read_log_if_available("/home/jtalks/tomcat/logs/jcommune.log")
         raise
     plugins = os.listdir("/home/jtalks/.jtalks/plugins/system-test")
     logger.info("Then plugins stayed from prev jcommune deployment: [{0}]", ", ".join(plugins))
     self.assertNotEqual(0, len(plugins), "Actual plugins: " + ", ".join(plugins))
     logger.info("And precisely the same plugin was left (QnA)")
     self.assertEqual(["questions-n-answers-plugin.jar"], plugins)
Example #21
0
class ScriptSettings:
    SCRIPT_TEMD_DIR = '/tmp/jtalks-cicd/'
    script_work_dir = "" + os.path.expanduser("~/.jtalks/")
    backups_dir = script_work_dir + "backups/"
    ENV_CONFIGS_DIR = script_work_dir + "environments/"
    GLOBAL_CONFIG_LOCATION = ENV_CONFIGS_DIR + "global-configuration.cfg"
    logger = Logger("ScriptSettings")

    def __init__(self,
                 build,
                 project=None,
                 env=None,
                 grab_envs=None,
                 work_dir=None,
                 sanity_test_timeout_sec=120,
                 package_version=None):
        """
     @param grab_envs - whether or not we should clone JTalks predefined environment configuration from private git
     repo
     @param work_dir - standard is ~/.jtalks, but it may be useful to override this value, e.g. during tests
     @param sanity_test_timeout_sec - how much time do sanity tests wait for the application to respond until they
            consider deployment as failed
    """
        self.env = env
        self.build = build
        self.project = project
        self.grab_envs = grab_envs
        self.script_work_dir = work_dir
        self.sanity_test_timeout_sec = sanity_test_timeout_sec
        self.package_version = package_version

    def log_settings(self):
        self.logger.info(
            "Script Settings: project=[{0}], env=[{1}], build number=[{2}], sanity test timeout=[{3}], package version=[{4}]",
            self.project, self.env, self.build, self.sanity_test_timeout_sec,
            self.package_version)
        self.logger.info("Environment configuration: [{0}]",
                         self.ENV_CONFIGS_DIR)

    def create_work_dir_if_absent(self):
        self.__create_dir_if_absent__(self.script_work_dir)
        self.__create_dir_if_absent__(self.get_env_configs_dir())
        self.__create_dir_if_absent__(self.get_backup_folder())

    def __create_dir_if_absent__(self, directory):
        if not os.path.exists(directory):
            self.logger.info("Creating directory [{0}]", directory)
            os.mkdir(directory)

    def get_tomcat_location(self):
        """
      Gets value of the tomcat home from [project].cfg file related to particular env and project
    """
        return self.__get_property('tomcat', 'location')

    def get_app_final_name(self):
        """
      Gets the name of the application to be deployed (even if it's Poulpe, it can be deployed as ROOT.war).
    """
        return self.__get_property('app', 'final_name')

    def get_temp_dir(self):
        """
     Script can save there some temp files.
    """
        return self.SCRIPT_TEMD_DIR

    def get_tomcat_port(self):
        """
      This is not actually a pre-configured parameter, it parses $TOMCAT_HOME/conf/server.xml to find out the HTTP
      port it's going to be listening. This is needed e.g. for sanity tests to.
    """
        server_xml = os.path.join(self.get_tomcat_location(), "conf",
                                  "server.xml")
        raise RuntimeError("tomcat port parsing is not implemented yet")

    def get_backup_folder(self):
        return self.backups_dir

    def get_env_configs_dir(self):
        return self.ENV_CONFIGS_DIR

    def get_global_config_location(self):
        return self.GLOBAL_CONFIG_LOCATION

    def __get_property(self, section, prop_name):
        """
      Finds property first in project configuration, then environment configuration and if there is no such property
      there, then it looks is it up in global configuration.
      @param section - a section joins several properties under it
      @param prop_name - a particular property name from specified section
      @returns None if there is no such property found in any config
    """
        value = self.__get_project_property(section, prop_name)
        if value == None:
            value = self.__get_env_property(section, prop_name)
        if value == None:
            value = self.__get_global_prop(section, prop_name)
        if value == None:
            self.logger.error("Property [{0}] was not found in any configs",
                              prop_name)
            raise ValueError
        return self.__replace_placeholders(value)

    def __get_project_property(self, section, prop_name):
        """
      Finds property value in project configuration. This overrides env and global configuration.
    """
        config = ConfigParser()
        config.read(
            os.path.join(self.ENV_CONFIGS_DIR, self.env,
                         self.project + ".cfg"))
        return self.__get_value_from_config(config, section, prop_name)

    def __get_env_property(self, section, prop_name):
        """
      Finds property value in configs/${env}/${env}.cfg configuration file. This overrides global configuration, but
      still can be overriden by project configs. These configs are shared between apps of the same environment. E.g.
      if we have Poulpe and JCommune on UAT env, and there is a file configs/uat/uat.cfg, then these properties are
      shared between those Poulpe and JCommune.
    """
        config = ConfigParser()
        config.read(
            os.path.join(self.ENV_CONFIGS_DIR, self.env,
                         "environment-configuration.cfg"))
        return self.__get_value_from_config(config, section, prop_name)

    def __get_global_prop(self, section, prop_name):
        """
      Finds a property value in configs/global-configuration.cfg.
    """
        config = ConfigParser()
        config.read(self.GLOBAL_CONFIG_LOCATION)
        return self.__get_value_from_config(config, section, prop_name)

    def __get_value_from_config(self, config, section, prop_name):
        try:
            return config.get(section, prop_name)
        except NoSectionError:
            return None

    def __replace_placeholders(self, prop_value):
        """
      Replaces placeholder for env and project that were possibly set in config files.
    """
        return prop_value.replace("${env}",
                                  self.env).replace("${project}", self.project)

    def get_sanity_test_timeout_sec(self):
        """
      How much time do sanity tests wait for the application response until they consider that the app didn't start
    """
        self.sanity_test_timeout_sec
Example #22
0
class DbOperations:
    """
    Base class for working with database
  """
    logger = Logger("DbBase")
    env = None
    db_settings = None

    def __init__(self, env, db_settings):
        """
    Args:
    env: environment name e.g. dev, uat
    db_settings: database connection settings of type DbSettings, which will be used for all
                 database related operations
    """
        self.env = env
        self.db_settings = db_settings

    def connect_to_database(self):
        """
    Connects to the specified database
    """
        self.logger.info("Connecting to [{0}] with user [{1}]",
                         self.db_settings.dbHost, self.db_settings.dbUser)
        self.connection = MySQLdb.connect(host=self.db_settings.dbHost,
                                          user=self.db_settings.dbUser,
                                          passwd=self.db_settings.dbPass)
        self.cursor = self.connection.cursor()

    def close_connection(self):
        """
    Closes open connection to the database
    """
        self.connection.close()

    def recreate_database(self):
        """
    Deletes the specified database and create it again
    """
        self.logger.info("Dropping and creating database from scratch: [{0}]",
                         self.db_settings.dbName)
        self.cursor.execute('DROP DATABASE IF EXISTS ' +
                            self.db_settings.dbName)
        self.cursor.execute('CREATE DATABASE ' + self.db_settings.dbName)

    def backup_database(self, backupPath):
        """
    Backups database to given backupPath
    """
        self.logger.info("Backing up database [{0}] to [{1}] using user [{2}]",
                         self.db_settings.dbName, backupPath,
                         self.db_settings.dbUser)
        os.popen("mysqldump -u{0} -p{1} {2} > {3}/{2}.sql"
          .format(self.db_settings.dbUser, self.db_settings.dbPass, self.db_settings.dbName, backupPath)) \
          .read()
        self.logger.info("Database backed up: environment=[{0}]".format(
            self.env))

    def restore_database_from_file(self, backupPath):
        """
    Restores database from file specified in backupPath
    """
        self.logger.info("Loading a db dump from [{0}/{1}.sql]", backupPath,
                         self.db_settings.dbName)
        self.recreate_database()
        os.popen('mysql -u{0} -p{1} {2} < {3}/{2}.sql'
          .format(self.db_settings.dbUser,self.db_settings.dbPass, self.db_settings.dbName, backupPath))\
          .read()
        self.logger.info("Database restored: environment=[{0}]".format(
            self.env))
Example #23
0
class DB:
    """
    A class to work with DB (upload, download, search). Note, that for all the operations we need a connection to
   database which created by connect_to_database method. To connection need name of environments, which contained config file with properties to database.
  """
    env = None
    config = None
    dbHost = None
    dbUser = None
    dbPass = None
    dbName = None
    dbDefiner = None
    cursor = None
    connection = None
    jcName = None
    jcDescription = None
    jcUrl = None
    jcNotify = None
    poulpeAdminPass = None
    logger = Logger("DB")

    def __init__(self, script_settings):
        self.env = script_settings.env
        self.config = ConfigParser.ConfigParser()
        self.config.read(script_settings.get_env_configs_dir() + self.env +
                         "/db.cfg")
        self.dbHost = self.config.get('db', 'host')
        self.dbUser = self.config.get('db', 'user')
        self.dbPass = self.config.get('db', 'pass')
        self.dbName = self.config.get('db', 'name')
        self.dbDefiner = self.config.get('db', 'definer')
        self.jcName = self.config.get('properties', 'jc_name')
        self.jcDescription = self.config.get('properties', 'jc_description')
        self.jcUrl = self.config.get('properties', 'jc_url')
        self.jcNotify = self.config.get('properties', 'jc_notify')
        self.poulpeAdminPass = self.config.get('properties',
                                               'poulpe_admin_pass')

    def connect_to_database(self):
        """
      Create connection to database.
    """
        self.connection = MySQLdb.connect(host=self.dbHost,
                                          user=self.dbUser,
                                          passwd=self.dbPass)
        self.cursor = self.connection.cursor()

    def close_connection(self):
        """
      Close connection to database.
    """
        self.connection.close()

    def recreate_database(self):
        """
      Method removed database (whith name, which contains in dbName), and create new(empty)
      database whita same name.
    """
        self.logger.info("Recreating database {0}", self.dbName)
        self.cursor.execute('DROP DATABASE IF EXISTS ' + self.dbName)
        self.cursor.execute('CREATE DATABASE ' + self.dbName)

    def restore_database_from_file(self, backupPath):
        """
      This method get path to filename (.sql) with backup of database. And restoring backup to database whith name,
      which contains in dbName.
    """
        self.logger.info("Loading a db dump from [{0}]", backupPath)
        self.recreate_database()
        self.fix_definer_in_backup(backupPath)
        os.popen('mysql -u ' + self.dbUser + ' --password='******' ' + self.dbName + ' < ' + backupPath).read()
        self.logger.info("Database restored: environment=[{0}]".format(
            self.env))

    def fix_definer_in_backup(self, backupPath):
        """
     For MySQL.
     If database contains VIEWS then created backup of database (by mysqldump) file contains lines with permissions (to
    user from origin database). When we restoring database to another server(it server don't have it user) we get a ERROR. To fix this problem, before restore we need replace all entries  (with DEFINER='{username}') to MySQL constant CURRENT_USER. This constant indicates that the law should give to the user on whose behalf is restored.
    """
        os.popen('sed -i \'s/DEFINER=' + self.dbDefiner +
                 '/DEFINER=CURRENT_USER/g\' ' + backupPath).read()
        self.logger.info("Database definer fixed: environment=[{0}]".format(
            self.env))

    def update_properties_to_preprod(self):
        """
     This method update application properties in database. Need to call after restore.
    """
        self.cursor.execute('USE ' + self.dbName)
        self.logger.info("Changing forum name to [{0}]", self.jcName)
        self.cursor.execute('UPDATE COMPONENTS SET NAME="' + self.jcName +
                            '", DESCRIPTION="' + self.jcDescription +
                            '" where COMPONENT_TYPE="FORUM"')
        self.logger.info("Switching off mail notifications")
        self.cursor.execute(
            'UPDATE PROPERTIES SET VALUE="' + self.jcNotify +
            '" where NAME="jcommune.sending_notifications_enabled"')
        self.cursor.execute('UPDATE PROPERTIES SET VALUE="' + self.jcUrl +
                            '" where NAME="jcommune.url_address"')
        self.cursor.execute('UPDATE USERS SET PASSWORD=MD5(' +
                            self.poulpeAdminPass + ') WHERE USERNAME="******"')
        self.connection.commit()
Example #24
0
 def __init__(self):
     self.logger = Logger('Main')
Example #25
0
class DB:
    """
      A class to work with DB (upload, download, search). Note, that for all the operations we need a connection to
     database which created by connect_to_database method. To connection need name of environments, which contained config file with properties to database.
    """
    env = None
    config = None
    dbHost = None
    dbUser = None
    dbPass = None
    dbName = None
    cursor = None
    connection = None
    jcName = None
    jcDescription = None
    jcUrl = None
    jcNotify = None
    poulpeAdminPass = None
    logger = Logger("DB")

    def __init__(self, dbsettings, scriptsettings):
        self.dbHost = dbsettings.host
        self.dbUser = dbsettings.user
        self.dbPass = dbsettings.password
        self.dbName = dbsettings.name
        self.jcName = scriptsettings.props('forum_name')
        self.jcDescription = scriptsettings.props('forum_description')
        self.jcUrl = scriptsettings.props('forum_url')
        self.jcNotify = scriptsettings.props('forum_notify')
        self.poulpeAdminPass = scriptsettings.props('forum_poulpe_admin_pass')

    def connect_to_database(self):
        """
          Create connection to database.
        """
        self.connection = MySQLdb.connect(host=self.dbHost,
                                          user=self.dbUser,
                                          passwd=self.dbPass)
        self.cursor = self.connection.cursor()

    def close_connection(self):
        """
          Close connection to database.
        """
        self.connection.close()

    def recreate_database(self):
        """
          Method removed database (whith name, which contains in dbName), and create new(empty)
          database whita same name.
        """
        self.logger.info("Recreating database {0}", self.dbName)
        self.cursor.execute('DROP DATABASE IF EXISTS ' + self.dbName)
        self.cursor.execute('CREATE DATABASE ' + self.dbName)

    def restore_database_from_file(self, backupPath):
        """
          This method get path to filename (.sql) with backup of database. And restoring backup to database whith name,
          which contains in dbName.
        """
        self.logger.info("Loading a db dump from [{0}]", backupPath)
        self.recreate_database()
        self.fix_definer_in_backup(backupPath)
        os.popen('mysql -u ' + self.dbUser + ' --password='******' ' + self.dbName + ' < ' + backupPath).read()
        self.logger.info("Database restored: environment=[{0}]".format(
            self.env))

    def fix_definer_in_backup(self, backupPath):
        """
         For MySQL.
         If database contains VIEWS then created backup of database (by mysqldump) file contains lines with permissions
         (to user from original database). When we restoring database on another server (the server that doesn't have
         that user) we get an ERROR. To fix this problem, before restore we need remove all entries
         (with DEFINER='{username}') and the view will be created and will be invoked by the user that created the DB.
        """
        os.popen("sed -i 's/DEFINER=[^*]*\*/\*/g' " + backupPath).read()
        self.logger.info("Database definer fixed: environment=[{0}]".format(
            self.env))

    def update_properties_to_preprod(self):
        """
         This method update application properties in database. Need to call after restore.
        """
        self.cursor.execute('USE ' + self.dbName)
        self.logger.info("Changing forum name to [{0}]", self.jcName)
        self.cursor.execute('UPDATE COMPONENTS SET NAME="' + self.jcName +
                            '", DESCRIPTION="' + self.jcDescription +
                            '" where COMPONENT_TYPE="FORUM"')
        self.logger.info("Switching off mail notifications")
        self.cursor.execute('UPDATE PROPERTIES SET VALUE="' + self.jcUrl +
                            '" where NAME="jcommune.url_address"')
        self.cursor.execute('UPDATE USERS SET PASSWORD=MD5(' +
                            self.poulpeAdminPass + ') WHERE USERNAME="******"')
        self.connection.commit()
Example #26
0
class Backuper:
    """
    Responsible for backing up artifacts like war files, config files, as well as DB backups.
    Stores backups in the folder and allows to restore those backups from it.
    @author stanislav bashkirtsev
  """
    logger = Logger("Backuper")
    BACKUP_FOLDER_DATE_FORMAT = "%Y_%m_%dT%H_%M_%S"

    def __init__(self, backup_folder, script_settings, db_operations):
        """
      @param backup_folder a directory to put backups to, it may contain backups from different envs,
        thus a folder for each env will be created
      @param script_settings is instance of ScriptSettings class
      @param db_operations instance of DbOperations
    """
        if not backup_folder.endswith("/"):
            raise ValueError(
                "Folder name should finish with '/'. Actual value: " +
                backup_folder)
        self.root_backup_folder = backup_folder
        self.script_settings = script_settings
        self.db_operations = db_operations

    def backup(self):
        """
    Does all operations for project backup:
     - backup the application database
     - backup application war-file
     - backup tomcat and ehcache configuration files
    """
        folder_to_put_backups = self.create_folder_to_backup(
            self.root_backup_folder, self.script_settings)
        self.backup_tomcat(folder_to_put_backups)
        self.backup_db(folder_to_put_backups)

    def clean_old_backups(self, backups_to_keep):
        """
    In order not to keep a lot of useless backups that take space, we're purging old ones.
    @param backups_to_keep amount of backups to keep in backup folder without removal
    """
        all_backups = self.get_list_of_backups()

    def create_folder_to_backup(self, backup_folder, script_settings):
        """
      We don't just put backups into a backup folder, we're creating a new folder there
      with current date so that our previous backups are kept there. E.g.:
      '/var/backups/prod/20130302T195924'

      @param backups_folder - basic folder to put backups there, it will be concatenated with env and current time
      @param script_settings to figure out what's the env and what's the project we're going to backup
    """
        now = datetime.now().strftime(self.BACKUP_FOLDER_DATE_FORMAT)

        final_backup_folder = "{0}/{1}".format(
            self.get_project_backup_folder(), now)
        os.makedirs(final_backup_folder)
        self.logger.info("Backing up old resources to [{0}]",
                         final_backup_folder)
        return final_backup_folder

    def backup_tomcat(self, backup_folder):
        """
      To be safe that we didn't forget anything, we're doing a backup for the whole tomcat directory.
      @param backup_folder a directory to put Tomcat to, should be created by the time this method is invoked
    """
        tomcat_location = self.script_settings.get_tomcat_location()
        if os.path.exists(tomcat_location):
            shutil.copytree(tomcat_location, backup_folder + "/tomcat")
        else:
            self.logger.info(
                "There was no previous tomcat folder [{0}], so nothing to backup",
                tomcat_location)

    def backup_db(self, backup_folder):
        self.db_operations.backup_database(backup_folder)

    def get_list_of_backups(self):
        """
      Gets the list of backups saved for
    """
        return os.listdir(self.root_backup_folder)

    def get_project_backup_folder(self):
        return "{0}{1}/{2}".format(self.root_backup_folder,
                                   self.script_settings.env,
                                   self.script_settings.project)
Example #27
0
class JtalksArtifacts:
    logger = Logger('JtalksArtifacts')

    def __init__(self, repo='builds'):
        self.repo = repo

    def download_war(self, project, build):
        gav = Gav(project + '-web-view',
                  'org.jtalks.' + project,
                  version='',
                  extension='')
        nexus = Nexus()
        version_page_url = gav.to_url(nexus.nexus_url, self.repo)
        version = NexusPageWithVersions().parse(version_page_url).version(
            build)
        gav.version = version
        gav.extension = 'war'
        nexus.download(self.repo, gav, project + '.war')
        return gav, project + '.war'

    def download_plugins(self, project, version, artifact_ids=()):
        """ -> [str] """
        files = []
        for plugin in artifact_ids:
            gav, filename = self.download_plugin(project, version, plugin)
            files.append(filename)
        return files

    def download_plugin(self, project, version, artifact_id):
        gav = Gav(artifact_id, 'org.jtalks.' + project, version)
        tofile_path = artifact_id + '.' + gav.extension
        Nexus().download(self.repo, gav, tofile_path)
        return gav, tofile_path

    def deploy_plugins(self, to_dir, plugin_files=[]):
        """
        Puts plugins to the config to the specified folder on the FS. Cleans previous plugins of there are any.
        :param str to_dir: directory to put the plugins to. Will be created if it's absent.
        :param [str] plugin_files: file names to put to the target dir
        """
        if to_dir:
            if not os.path.exists(to_dir):
                self.logger.info('Plugin dir did not exist, creating: [{0}]',
                                 to_dir)
                os.makedirs(to_dir)
            for filename in os.listdir(to_dir):  # rm previous plugins
                if filename.endswith('.jar'):
                    plugin_path = os.path.join(to_dir, filename)
                    self.logger.info('Removing previous plugins: [{0}]',
                                     plugin_path)
                    os.remove(plugin_path)
            for plugin in plugin_files:
                self.logger.info('Adding plugin [{0}] to [{1}]', plugin,
                                 to_dir)
                shutil.move(plugin, to_dir)
        elif len(plugin_files) != 0:
            self.logger.warn(
                'Plugin dir was not specified in env configs while there are plugins specified '
                'to be deployed: [{0}]. Skipping plugin deployment',
                ','.join(plugin_files))
            return
Example #28
0
 def __init__(self):
     self.logger = Logger("Main")
Example #29
0
class Tomcat:
    """
  Class for deploying and backing up Tomcat applications
  """

    logger = Logger("Tomcat")

    def __init__(self, backuper, script_settings):
        self.backuper = backuper
        self.script_settings = script_settings

    def deploy_war(self):
        """
    Stops the Tomcat server, backups all necessary application data, copies
    new application data to Tomcat directories
    """
        self.logger.info("Deploying {0} to {1}", self.script_settings.project,
                         self.script_settings.get_tomcat_location())
        self.stop()
        self.backuper.backup()
        self.move_war_to_webapps()
        self.put_configs_to_conf()
        self.start()

    def stop(self):
        """
    Stops the Tomcat server if it is running
    """
        stop_command = "pkill -9 -f {0}".format(
            self.script_settings.get_tomcat_location())
        self.logger.info("Killing tomcat [{0}]", stop_command)
        #dunno why but retcode always equals to SIGNAL (-9 in this case), didn't figure out how to
        #distinguish errors from this
        retcode = subprocess.call([stop_command],
                                  shell=True,
                                  stdout=PIPE,
                                  stderr=PIPE)

    def move_war_to_webapps(self):
        """
    Moves application war-file to 'webapps' Tomcat subfolder
    """
        final_app_location = self.get_web_apps_location(
        ) + "/" + self.script_settings.get_app_final_name()
        self.remove_previous_app(final_app_location)
        self.logger.info("Putting new war file to Tomcat: [{0}]",
                         final_app_location)
        shutil.move(self.script_settings.project + ".war",
                    final_app_location + ".war")

    def remove_previous_app(self, app_location):
        if os.path.exists(app_location):
            self.logger.info("Removing previous app: [{0}]", app_location)
            shutil.rmtree(app_location)
        else:
            self.logger.info(
                "Previous application was not found in [{0}], thus nothing to remove",
                app_location)

        war_location = app_location + ".war"
        if os.path.exists(war_location):
            self.logger.info("Removing previous war file: [{0}]", war_location)
            os.remove(war_location)

    def get_config_file_location(self):
        return os.path.join(self.script_settings.get_env_configs_dir(),
                            self.script_settings.env, self.get_config_name())

    def put_configs_to_conf(self):
        """
    Copies configuration files for application and ehcache to Tomcat directories
    """
        final_conf_location = self.get_config_folder_location(
        ) + "/" + self.script_settings.get_app_final_name() + ".xml"
        conf_file_location = self.get_config_file_location()
        ehcache_config_file_location = os.path.join(
            self.script_settings.get_env_configs_dir(),
            self.script_settings.env, self.get_ehcache_config_name())

        self.logger.info("Putting [{0}] into [{1}]", conf_file_location,
                         final_conf_location)
        shutil.copyfile(conf_file_location, final_conf_location)
        if os.path.exists(ehcache_config_file_location):
            shutil.copy(ehcache_config_file_location,
                        self.script_settings.get_tomcat_location() + "/conf")

    def start(self):
        """
    Starts the Tomcat server
    """
        startup_file = self.script_settings.get_tomcat_location(
        ) + "/bin/startup.sh"
        self.logger.info("Starting Tomcat [{0}]", startup_file)
        subprocess.call(startup_file, shell=True, stdout=PIPE, stderr=PIPE)

    def get_ehcache_config_name(self):
        """
    Returns name of the Ehcache configuration file
    """
        return self.script_settings.project + ".ehcache.xml"

    def get_config_name(self):
        """
    Returns name of the Tomcat configuration file
    """
        return self.script_settings.project + ".xml"

    def get_config_folder_location(self):
        """
    Returns configuration folder for Tomcat
    """
        return os.path.join(self.script_settings.get_tomcat_location(), "conf",
                            "Catalina", "localhost")

    def get_web_apps_location(self):
        """
    Returns path to web applications directory of Tomcat
    """
        return self.script_settings.get_tomcat_location() + "/webapps"
Example #30
0
 def test_deploy_plugins_must_not_clean_prev_plugins_if_not_jc_is_deployed(
         self):
     logger = Logger(
         'plugins_must_not_clean_prev_plugins_if_not_jc_is_deployed')
     jcsettings = self._scriptsettings(build=2843)
     antsettings = self._scriptsettings(build=574, project='antarcticle')
     try:
         logger.info('Given JCommune was deployed with one of its plugins')
         self._deploy(jcsettings)
         logger.info('When deploying Antarcticle')
         self._deploy(antsettings)
     except:
         logger.error('Oops, error happened during deployment')
         self._read_log_if_available(
             '/home/jtalks/tomcat/logs/catalina.out')
         self._read_log_if_available(
             '/home/jtalks/tomcat/logs/jcommune.log')
         raise
     plugins = os.listdir('/home/jtalks/.jtalks/plugins/system-test')
     logger.info('Then plugins stayed from prev jcommune deployment: [{0}]',
                 ', '.join(plugins))
     self.assertNotEqual(0, len(plugins),
                         'Actual plugins: ' + ', '.join(plugins))
     logger.info('And precisely the same plugin was left (QnA)')
     self.assertEqual(['questions-n-answers-plugin.jar'], plugins)