Example #1
0
def site_profile_update(site, original, updates):
    print ("Site Profile Update\n{0}".format(site))
    code_directory_sid = "{0}/{1}/{1}".format(sites_code_root, site["sid"])
    old_profile = utilities.get_single_eve("code", original["code"]["profile"])
    new_profile = utilities.get_single_eve("code", site["code"]["profile"])
    new_profile_full_string = _get_code_name_version(site["code"]["profile"])

    with cd(code_directory_sid + "/profiles"):
        run(
            "rm {0}; ln -s {1}/profiles/{2}/{3} {2}".format(
                old_profile["meta"]["name"], code_root, new_profile["meta"]["name"], new_profile_full_string
            )
        )
        print ("Rebuild registry.")
        run("drush rr")
Example #2
0
def pre_delete_code_callback(request, lookup):
    """
    Make sure no sites are using the code.

    :param request: flask.request object
    :param lookup:
    """
    code = utilities.get_single_eve('code', lookup['_id'])
    app.logger.debug(code)
    if code['meta']['code_type'] in ['module', 'theme', 'library']:
        code_type = 'package'
    else:
        code_type = code['meta']['code_type']
    app.logger.debug(code_type)
    site_query = 'where={{"code.{0}":"{1}"}}'.format(code_type, code['_id'])
    sites = utilities.get_eve('sites', site_query)
    app.logger.debug(sites)
    if not sites['_meta']['total'] == 0:
        for site in sites['_items']:
            # Create a list of sites that use this code item.
            # If 'sid' is a key in the site dict use it, otherwise use '_id'.
            site_list = []
            if site.get('sid'):
                site_list.append(site['sid'])
            else:
                site_list.append(site['_id'])
        site_list_full = ', '.join(site_list)
        app.logger.error('Code item is in use by one or more sites:\n{0}'.format(site_list_full))
        abort(409, 'A conflict happened while processing the request. Code item is in use by one or more sites.')
Example #3
0
def switch_packages(instance):
    """Switch Package symlinks, if no package symlinks are present add them.

    Arguments:
        instance {dict} -- full instance record
    """
    log.info('Instance | Switch package | Instance - %s', instance['sid'])
    log.debug('Instance | Switch package | Instance - %s', instance)
    instance_code_path_sid = '{0}/{1}/{1}'.format(INSTANCE_ROOT,
                                                  instance['sid'])
    # Get rid of old symlinks
    # List sites/all/{modules|themes|libraries} and remove all symlinks
    for package_type_path in ['modules', 'themes', 'libraries']:
        package_path = instance_code_path_sid + '/sites/all/' + package_type_path
        log.debug('Instance | Switch Packages | listdir - %s',
                  os.listdir(package_path))
        for item in os.listdir(package_path):
            # Get full path of item
            path = package_path + '/' + item
            log.debug('Instance | Switch Packages | Item to unlink - %s', path)
            if os.path.islink(path):
                os.remove(path)
    if 'package' in instance['code']:
        for item in instance['code']['package']:
            package = utilities.get_single_eve('code', item)
            package_path = utilities.code_path(package)
            package_type_path = utilities.code_type_directory_name(
                package['meta']['code_type'])
            destination_path = instance_code_path_sid + '/sites/all/' + \
                package_type_path + '/' + package['meta']['name']
            # Add new relative symlink
            if not os.access(destination_path, os.F_OK):
                utilities.relative_symlink(package_path, destination_path)
Example #4
0
def pre_delete_code(request, lookup):
    """
    Make sure no sites are using the code.

    :param request: flask.request object
    :param lookup:
    """
    code = utilities.get_single_eve('code', lookup['_id'])
    log.debug('code | Delete | code - %s', code)

    # Check for sites using this piece of code.
    if code['meta']['code_type'] in ['module', 'theme', 'library']:
        code_type = 'package'
    else:
        code_type = code['meta']['code_type']
    log.debug('code | Delete | code - %s | code_type - %s', code['_id'], code_type)
    site_query = 'where={{"code.{0}":"{1}"}}'.format(code_type, code['_id'])
    sites = utilities.get_eve('sites', site_query)
    log.debug('code | Delete | code - %s | sites result - %s', code['_id'], sites)
    if not sites['_meta']['total'] == 0:
        site_list = []
        for site in sites['_items']:
            # Create a list of sites that use this code item.
            # If 'sid' is a key in the site dict use it, otherwise use '_id'.
            if site.get('sid'):
                site_list.append(site['sid'])
            else:
                site_list.append(site['_id'])
        site_list_full = ', '.join(site_list)
        log.error('code | Delete | code - %s | Code item is in use by one or more sites - %s',
                  code['_id'], site_list_full)
        abort(409, 'A conflict happened while processing the request. Code item is in use by one or more sites.')
Example #5
0
def correct_file_permissions(site_id):
    """
    Correct file permissions for an instance's NFS files.
    :param machine_name: id of instance to fix
    """
    app.logger.debug('Site | Correct file permissions | Site ID - %s', site_id)
    instance = utilities.get_single_eve('sites', site_id)
    tasks.correct_file_permissions.delay(instance)
    return make_response('Fixing permissions for NFS mounted files.')
Example #6
0
def update_settings_file(site):
    print ("Update Settings Files\n{0}".format(site))
    code_directory = "{0}/{1}".format(sites_code_root, site["sid"])
    code_directory_current = "{0}/current".format(code_directory)
    profile = utilities.get_single_eve("code", site["code"]["profile"])
    profile_name = profile["meta"]["name"]

    _create_settings_files(site, profile_name)
    _push_settings_files(site, code_directory_current)
Example #7
0
def _get_code_name_version(code_id):
    """
    Get the label and version for a code item.
    :param code_id: string '_id' for a code item
    :return: string 'label'-'version'
    """
    code = utilities.get_single_eve("code", code_id)
    code_name = code["meta"]["name"]
    code_version = code["meta"]["version"]
    return "{0}-{1}".format(code_name, code_version)
Example #8
0
def create_backup(site_id):
    """
    Create a backup of an instance.
    :param machine_name: id of instance to restore
    """
    app.logger.debug('Backup | Create | Site ID - %s', site_id)
    site = utilities.get_single_eve('sites', site_id)
    app.logger.debug('Backup | Create | Site Response - %s', site)
    tasks.backup_create.delay(site=site, backup_type='on_demand')
    response = make_response('Backup started')
    return response
Example #9
0
def restore_backup(backup_id):
    """
    Restore a backup to a new instance.
    :param machine_name: id of backup to restore
    """
    app.logger.debug('Backup | Restore | %s', backup_id)
    backup_record = utilities.get_single_eve('backup', backup_id)
    original_instance = utilities.get_single_eve('sites',
                                                 backup_record['site'],
                                                 backup_record['site_version'])
    # If packages are still active, add them; if not, find a current version
    # and add it; if none, error
    try:
        package_list = utilities.package_import(original_instance)
    except Exception as error:
        abort(409, error)

    tasks.backup_restore.delay(backup_record, original_instance, package_list)
    response = make_response('Restore started')
    return response
Example #10
0
def site_provision(site, install=True):
    """
    Responds to POSTs to provision a site to the right places on the server.

    :param site: The flask.request object, JSON encoded
    :param install: Boolean. Indicates if the install command will run.
    :return:
    """
    print ("Site Provision - Install: {0}\n{1}".format(install, site))

    code_directory = "{0}/{1}".format(sites_code_root, site["sid"])
    code_directory_sid = "{0}/{1}".format(code_directory, site["sid"])
    code_directory_current = "{0}/current".format(code_directory)
    web_directory = "{0}/{1}/{2}".format(sites_web_root, site["type"], site["sid"])
    profile = utilities.get_single_eve("code", site["code"]["profile"])
    profile_name = profile["meta"]["name"]

    _create_database(site)

    _create_settings_files(site, profile_name)

    _create_directory_structure(code_directory)

    with cd(code_directory):
        core = _get_code_name_version(site["code"]["core"])
        run("drush dslm-new {0} {1}".format(site["sid"], core))

    _update_symlink(code_directory_sid, code_directory_current)

    with cd(code_directory_current):
        profile = _get_code_name_version(site["code"]["profile"])
        run("drush dslm-add-profile {0}".format(profile))

    if nfs_mount_files_dir:
        nfs_dir = nfs_mount_location[environment]
        nfs_files_dir = "{0}/sitefiles/{1}/files".format(nfs_dir, site["sid"])
        nfs_tmp_dir = "{0}/sitefiles/{1}/tmp".format(nfs_dir, site["sid"])
        _create_directory_structure(nfs_files_dir)
        _create_directory_structure(nfs_tmp_dir)
        # Replace default files dir with this one
        site_files_dir = code_directory_current + "/sites/default/files"
        _replace_files_directory(nfs_files_dir, site_files_dir)

    _push_settings_files(site, code_directory_current)

    _update_symlink(code_directory_current, web_directory)
    correct_file_directory_permissions(site)

    if install:
        _install_site(profile_name, code_directory_current)
        correct_file_directory_permissions(site)
Example #11
0
def site_install(site):
    """Run Drupal install
    """
    code_directory = '{0}/{1}'.format(INSTANCE_ROOT, site['sid'])
    code_directory_current = '{0}/current'.format(code_directory)
    profile = utilities.get_single_eve('code', site['code']['profile'])
    profile_name = profile['meta']['name']

    try:
        with cd(code_directory_current):
            run('drush site-install -y {0}'.format(profile_name))
    except FabricException as error:
        log.error('Site | Install | Instance install failed | Error - %s', error)
        return error
Example #12
0
def backup_restore(backup_record, original_instance, package_list):
    """
    Restore database and files to a new instance.
    """
    log.info('Instance | Restore Backup | %s | %s', backup_record, original_instance)
    start_time = time()
    file_date = datetime.strptime(backup_record['backup_date'], "%Y-%m-%d %H:%M:%S %Z")
    pretty_filename = '{0}_{1}'.format(
        original_instance['sid'], file_date.strftime("%Y-%m-%d-%H-%M-%S"))
    pretty_database_filename = '{0}.sql'.format(pretty_filename)
    database_path = '{0}/backups/{1}'.format(BACKUP_PATH, pretty_database_filename)
    pretty_files_filename = '{0}.tar.gz'.format(pretty_filename)
    files_path = '{0}/backups/{1}'.format(BACKUP_PATH, pretty_files_filename)

    # Grab available instance and add packages if needed
    available_instances = utilities.get_eve('sites', 'where={"status":"available"}')
    log.debug('Instance | Restore Backup | Avaiable Instances - %s', available_instances)
    new_instance = next(iter(available_instances['_items']), None)
    # TODO: Don't switch if the code is the same
    if new_instance is not None:
        payload = {'status': 'installing'}
        if package_list:
            packages = {'code': {'package': package_list}}
            payload.update(packages)
        utilities.patch_eve('sites', new_instance['_id'], payload)
    else:
        exit('No available instances.')

    # Wait for code and status to update.
    attempts = 18  # Tries every 10 seconds to a max of 18 (or 3 minutes).
    while attempts:
        try:
            new_instance_refresh = utilities.get_single_eve('sites', new_instance['_id'])
            if new_instance_refresh['status'] != 'installed':
                log.info('Instance | Restore Backup | New instance is not ready | %s', new_instance['_id'])
                raise ValueError('Status has not yet updated.')
            break
        except ValueError, e:
            # If the status is not updated and we have attempts left,
            # remove an attempt and wait 10 seconds.
            attempts -= 1
            if attempts is not 0:
                sleep(10)
            else:
                exit(str(e))
Example #13
0
def pre_delete_sites(request, lookup):
    """
    Remove site from servers right before the item is removed.

    :param request: flask.request object
    :param lookup:
    """
    log.debug('Instances | Pre Delete | lookup - %s', lookup)
    instance = utilities.get_single_eve('sites', lookup['_id'])
    log.debug('Instances | Pre Delete | instance - %s', instance)

    # Check if instance is launched.
    if not instance['status'] in ['launched', 'launching']:
        log.debug('Instances | Pre Delete | instance - %s | Instance state - %s | Okay to delete', instance, instance['status'])
        tasks.site_remove.delay(instance)
    else:
        log.error('Instances | Delete | instance - %s | Instance is launched or launching',
                  instance['_id'])
        abort(409, 'Instance is launched or launching. To delete, take instance down first.')
Example #14
0
def switch_profile(instance):
    """Switch non-core Profile symlinks, if no appropriate symlinks are present add them.

    Arguments:
        instance {dict} -- full instance record
    """
    log.info('Instance | Switch profile | Instance - %s', instance['sid'])
    log.debug('Instance | Switch profile | Instance - %s', instance)
    # Lookup the profile we want to use.
    profile = utilities.get_single_eve('code', instance['code']['profile'])
    # Setup variables
    profile_path = utilities.code_path(profile)
    instance_code_path_sid = '{0}/{1}/{1}'.format(INSTANCE_ROOT,
                                                  instance['sid'])
    destination_path = instance_code_path_sid + '/profiles/' + profile['meta'][
        'name']
    # Remove old symlink
    if os.path.islink(destination_path):
        os.remove(destination_path)
    # Add new relative symlink
    if not os.access(destination_path, os.F_OK):
        utilities.relative_symlink(profile_path, destination_path)
Example #15
0
def backup_delete(item):
    """Remove backup files from servers

    Arguments:
        item {string} -- Backup item to remove
    """
    log.debug('Backup | Delete | Item - %s', item)
    log.info('Backup | Delete | Item - %s ', item['_id'])

    instance = utilities.get_single_eve('sites', item['site'],
                                        item['site_version'])
    pretty_filename = '{0}_{1}'.format(
        instance['sid'], item['backup_date'].strftime("%Y-%m-%d-%H-%M-%S"))
    pretty_database_filename = '{0}.sql'.format(pretty_filename)
    database_path = '{0}/backups/{1}'.format(BACKUP_PATH,
                                             pretty_database_filename)
    pretty_files_filename = '{0}.tar.gz'.format(pretty_filename)
    files_path = '{0}/backups/{1}'.format(BACKUP_PATH, pretty_files_filename)

    os.remove(files_path)
    os.remove(database_path)

    log.info('Backup | Delete | Complete | Item - %s', item['_id'])
Example #16
0
def switch_settings_files(instance):
    """Create settings.php from template and render the resulting file onto the server.

    Arguments:
        instance {dict} -- full instance record
    """
    log.info('Instance | Settings file | Instance ID - %s', instance['_id'])

    # If the settings file exists, change permissions to allow us to update the template.
    file_destination = "{0}/{1}/{1}/sites/default/settings.php".format(
        INSTANCE_ROOT, instance['sid'])
    # Check to see if file exists and is writable.
    utilities.file_accessable_and_writable(file_destination)

    # Setup variables
    if instance['settings'].get('siteimprove_site'):
        siteimprove_site = instance['settings']['siteimprove_site']
    else:
        siteimprove_site = None
    if instance['settings'].get('siteimprove_group'):
        siteimprove_group = instance['settings']['siteimprove_group']
    else:
        siteimprove_group = None

    profile = utilities.get_single_eve('code', instance['code']['profile'])

    if ('cse_creator' in instance['settings']) and ('cse_id'
                                                    in instance['settings']):
        google_cse_csx = instance['settings']['cse_creator'] + ':' + instance[
            'settings']['cse_id']
    else:
        google_cse_csx = None

    if NFS_MOUNT_FILES_DIR:
        tmp_path = '{0}/{1}/tmp'.format(NFS_MOUNT_LOCATION[ENVIRONMENT],
                                        instance['sid'])
    else:
        tmp_path = '/tmp'

    if 'google_tag_client_container_id' in instance['settings']:
        google_tag_client_container_id = instance['settings'][
            'google_tag_client_container_id']
    else:
        google_tag_client_container_id = None

    domain = BASE_URLS[ENVIRONMENT].split('://')[1]

    settings_variables = {
        'profile': profile['meta']['name'],
        'sid': instance['sid'],
        'atlas_id': instance['_id'],
        'atlas_url': API_URLS[ENVIRONMENT] + '/',
        'atlas_logging_url': ATLAS_LOGGING_URLS[ENVIRONMENT],
        'atlas_username': SERVICE_ACCOUNT_USERNAME,
        'atlas_password': SERVICE_ACCOUNT_PASSWORD,
        'path': instance['path'],
        'status': instance['status'],
        'atlas_statistics_id': instance['statistics'],
        'siteimprove_site': siteimprove_site,
        'siteimprove_group': siteimprove_group,
        'google_cse_csx': google_cse_csx,
        'google_tag_client_container_id': google_tag_client_container_id,
        'reverse_proxies': SERVERDEFS[ENVIRONMENT]['varnish_servers'],
        'varnish_control': VARNISH_CONTROL_TERMINALS[ENVIRONMENT],
        'varnish_control_key': VARNISH_CONTROL_KEY,
        'pw': utilities.decrypt_string(instance['db_key']),
        'page_cache_maximum_age':
        instance['settings']['page_cache_maximum_age'],
        'database_servers': SERVERDEFS[ENVIRONMENT]['database_servers'],
        'environment': ENVIRONMENT,
        'tmp_path': tmp_path,
        'saml_pw': SAML_AUTH,
        'smtp_client_hostname': BASE_URLS[ENVIRONMENT],
        'smtp_password': SMTP_PASSWORD,
        'base_url': BASE_URLS[ENVIRONMENT],
        'domain': domain,
        'servicenow_key': SERVICENOW_KEY
    }

    log.info(
        'Instance | Settings file | Render settings file | Instance ID - %s',
        instance['_id'])
    # Create a template environment with the default settings and a loader that looks up the
    # templates in the templates folder inside the Atlas python package.
    # We don't do autoescaping, because there is no PHP support.
    jinja_env = Environment(loader=PackageLoader('atlas', 'templates'))
    template = jinja_env.get_template('settings.php')
    render = template.render(settings_variables)
    # Remove the existing file.
    if os.access(file_destination, os.F_OK):
        os.remove(file_destination)
    # Write the render to a file.
    with open(file_destination, "wb") as open_file:
        open_file.write(render)
    # Set file permissions
    # Octet mode, Python 3 compatible
    os.chmod(file_destination, 0o444)
Example #17
0
def import_backup():
    """
    Import a backup to a new instance on the current version of core, profile, and any packages
    that are present. If a current version of a package is not available, the import will abort.
    """
    backup_request = request.get_json()
    app.logger.debug('Backup | Import | %s', backup_request)
    # Get the backup and then the site records.
    # TODO Get the list of env from the config files.
    # TODO Verify import is from different env, recommend restore if it is the same env.
    if not (backup_request.get('env') and backup_request.get('id')):
        abort(409, 'Error: Missing env (local, dev, test, prod) and id.')
    elif not backup_request.get('env'):
        abort(409, 'Error: Missing env (local, dev, test, prod).')
    elif not backup_request.get('id'):
        abort(409, 'Error: Missing id.')
    elif backup_request['env'] not in ['local', 'dev', 'test', 'prod']:
        abort(409, 'Error: Invalid env choose from [local, dev, test, prod]')

    backup_record = utilities.get_single_eve('backup',
                                             backup_request['id'],
                                             env=backup_request['env'])
    app.logger.debug('Backup | Import | Backup record - %s', backup_record)
    remote_site_record = utilities.get_single_eve(
        'sites',
        backup_record['site'],
        backup_record['site_version'],
        env=backup_request['env'])
    app.logger.debug('Backup | Import | Site record - %s', remote_site_record)

    # Get a list of packages to include
    try:
        package_list = utilities.package_import_cross_env(
            remote_site_record, env=backup_request['env'])
    except Exception as error:
        abort(500, error)

    app.logger.info('Backup | Import | Package list - %s', package_list)

    # Try to get the p1 record.
    local_p1_instance_record = utilities.get_single_eve(
        'sites', remote_site_record['sid'])
    app.logger.debug('Backup | Import | Local instance record - %s',
                     local_p1_instance_record)
    # Try to get the path record if the site is launched.
    local_path_instance_record = False
    if remote_site_record['path'] != remote_site_record['sid']:
        query_string = 'where={{"path":"{0}"}}'.format(
            remote_site_record['path'])
        local_path_instance_records = utilities.get_eve('sites', query_string)
        app.logger.info('Backup | Import | Local path instance record - %s',
                        local_path_instance_records)
        if local_path_instance_records['_meta']['total'] == 1:
            local_path_instance_record = True
    if local_p1_instance_record['_error'] and local_p1_instance_record[
            '_error']['code'] == 404 and not local_path_instance_record:
        # Create an instance with the same sid
        payload = {
            "status": remote_site_record['status'],
            "sid": remote_site_record['sid'],
            "path": remote_site_record['path']
        }
        response_string = 'the same'
    else:
        app.logger.info('Backup | Import | Instance sid or path exists')
        payload = {"status": "installed"}
        response_string = 'a new'

    # Add package list to payload if it exists
    if package_list:
        payload['code'] = {"package": package_list}
    # Set install
    payload['install'] = False

    new_instance = utilities.post_eve('sites', payload)
    app.logger.debug('Backup | Import | New instance record - %s',
                     new_instance)

    env = backup_request['env']
    backup_id = backup_request['id']
    target_instance = new_instance['_id']

    tasks.import_backup.apply_async([env, backup_id, target_instance],
                                    countdown=30)

    return make_response(
        'Attempting to import backup to {0} sid'.format(response_string))
Example #18
0
def switch_core(instance):
    """Switch Drupal core symlinks, if no core symlinks are present add them.

    Arguments:
        instance {dict} -- full instance record
    """
    # Lookup the core we want to use.
    core = utilities.get_single_eve('code', instance['code']['core'])
    # Setup variables
    core_path = utilities.code_path(core)
    instance_code_path_sid = '{0}/{1}/{1}'.format(INSTANCE_ROOT,
                                                  instance['sid'])
    # Get a list of files in the Core source directory
    core_files = os.listdir(core_path)
    # Get a list of files in the Instance target directory
    instance_files = os.listdir(instance_code_path_sid)
    # Remove any existing symlinks to a core.
    for instance_file in instance_files:
        full_path = instance_code_path_sid + '/' + instance_file
        # Check if path is a symlink.
        if os.path.islink(full_path):
            # Get the target of the symlink.
            symlink_target = os.readlink(full_path)
            # Get the name of the directory that contains the symlink target
            code_dir = os.path.dirname(symlink_target)
            # Check to see if the directory is a Drupal core, if so remove the symlink.
            regex = '((drupal)\-([\d\.x]+\-*[dev|alph|beta|rc|pl]*[\d]*))$i'
            if re.match(regex, code_dir):
                os.remove(full_path)
    # Iterate through the source files and symlink when applicable.
    for core_file in core_files:
        if core_file in ['sites', 'profiles']:
            continue
        if utilities.ignore_code_file(core_file):
            continue
        source_path = core_path + '/' + core_file
        destination_path = instance_code_path_sid + '/' + core_file
        # Remove existing symlink and add new one.
        if os.path.islink(destination_path):
            os.remove(destination_path)
            # F_OK to test the existence of path
        if not os.access(destination_path, os.F_OK):
            utilities.relative_symlink(source_path, destination_path)
    # Create Instance specific directory structure
    directories_to_create = [
        'sites', 'sites/all', 'sites/all/modules', 'sites/all/libraries',
        'sites/all/themes', 'sites/default', 'sites/default/files', 'profiles'
    ]
    for directory in directories_to_create:
        target_dir = instance_code_path_sid + '/' + directory
        # If the directoey does not already exist, create it.
        if not os.access(target_dir, os.F_OK):
            os.mkdir(target_dir)
    # Copy over default settings file so that this instance is like a default install.
    source_path = core_path + '/sites/default/default.settings.php'
    destination_path = instance_code_path_sid + '/sites/default/default.settings.php'
    if os.access(destination_path, os.F_OK):
        os.remove(destination_path)
    copyfile(source_path, destination_path)
    # Include links to the profiles that we are not using so that the site doesn't white screen if
    # the deployed profile gets disabled.
    core_profiles = os.listdir(core_path + '/profiles')
    for core_profile in core_profiles:
        source_path = core_path + '/profiles/' + core_profile
        destination_path = instance_code_path_sid + '/profiles/' + core_profile
        if os.path.islink(destination_path):
            os.remove(destination_path)
            # F_OK to test the existence of path
        if not os.access(destination_path, os.F_OK):
            utilities.relative_symlink(source_path, destination_path)