This commit is contained in:
Richard Pappalardo 2016-02-19 09:48:07 -08:00
Родитель 7baebeec7d
Коммит e59354f13b
8 изменённых файлов: 543 добавлений и 0 удалений

Просмотреть файл

@ -1,2 +1,4 @@
# ff-tool
Python CLI tool for downloading desktop Firefox version, managing profiles and test prefs
# Work in progress... DO NOT USE

52
firefox_download.py Normal file
Просмотреть файл

@ -0,0 +1,52 @@
"""
Module to download OS-specific versions of Firefox:
1. General Release (gr)
2. Beta (beta)
3. Developer Edition (aurora)
4. Nightly (nightly)
"""
from firefox_env_handler import IniHandler
from mozdownload import FactoryScraper
CONFIG_CHANNELS = 'configs/channels.ini'
try:
import configparser # Python 3
except:
import ConfigParser as configparser # Python 2
config = configparser.ConfigParser()
config.read(CONFIG_CHANNELS)
env = IniHandler()
env.load_os_config('configs')
def set_channel(channel):
ch_version = config.get(channel, 'version')
ch_type = config.get(channel, 'type')
ch_branch = config.get(channel, 'branch')
return ch_version, ch_type, ch_branch
def download(channel):
print('USING CHANNEL: {0}'.format(channel))
v, t, b = set_channel(channel)
download_filename = env.get(channel, 'DOWNLOAD_FILENAME')
scraper = FactoryScraper(
t,
version=v,
branch=b,
destination='_temp/{0}'.format(download_filename)
)
scraper.download()
def main():
for channel in config.sections():
download(channel)
if __name__ == '__main__':
main()

174
firefox_env_handler.py Executable file
Просмотреть файл

@ -0,0 +1,174 @@
#!/usr/bin/env python
import os
import platform
import re
import shutil
import sys
import configparser
class FirefoxEnvHandler():
LINUX = 'linux'
MAC = 'darwin'
WINDOWS = 'cygwin'
def __init__(self):
pass
@staticmethod
def get_os():
"""
Do our best to determine the user's current operating system.
"""
system = platform.system().lower()
return re.split('[-_]', system, maxsplit=1).pop(0)
@classmethod
def is_linux(cls):
"""
Am I running on Linux?
"""
return cls.get_os() == cls.LINUX
@classmethod
def is_mac(cls):
"""
Am I running on Mac/Darwin?
"""
return cls.get_os() == cls.MAC
@classmethod
def is_other(cls):
"""
I 'literally' have no idea who you are.
"""
return not (cls.is_linux() or cls.is_mac() or cls.is_windows())
@classmethod
def is_windows(cls):
"""
Am I running on Windows/Cygwin?
"""
return cls.get_os() == cls.WINDOWS
@staticmethod
def banner(str, length=79, delimiter='='):
"""
Throws up a debug header to make output subjectively "easier" to read
in stdout.
"""
if length <= 0:
length = len(str)
divider = delimiter * length
output = '\n'.join(['', divider, str, divider])
print(output)
@staticmethod
def clean_folder(path, foot_gun=True):
"""
Recursively delete the specified path.
"""
if os.path.isdir(path):
print(('Deleting {0}'.format(path)))
shutil.rmtree(path)
else:
print(('{0} is not a directory'.format(path)))
class IniHandler(FirefoxEnvHandler):
def __init__(self, ini_path=None):
"""
Creates a new config parser object, and optionally loads a config file
if `ini_path` was specified.
"""
self.config = configparser.SafeConfigParser(os.environ)
if ini_path is not None:
self.load_config(ini_path)
def load_config(self, ini_path):
"""
Load an INI config based on the specified `ini_path`.
"""
IniHandler.banner('LOADING {0}'.format(ini_path), 79, '-')
# Make sure the specified config file exists, fail hard if missing.
if not os.path.isfile(ini_path):
sys.exit('Config file not found: {0}'.format(ini_path))
self.config.read(ini_path)
def load_os_config(self, config_path):
"""
Load an INI file based on the current operating system. This method
will attempt to load a "darwin.ini", "cygwin.ini", or "linux-gnu.ini"
file from the specified `config_path` based on the current OS.
"""
os_config = os.path.join(config_path, IniHandler.get_os() + '.ini')
self.load_config(os_config)
def create_env_file(self, out_file='.env'):
"""
Generate and save the output environment file so we can source it from
something like .bashrc or .bashprofile.
"""
IniHandler.banner('CREATING ENV FILE ({0})'.format(out_file))
env_fmt = "export %s=\"%s\""
env_vars = []
# Generic paths to Sikuli and Firefox profile directories.
for key in ['PATH_SIKULIX_BIN', 'PATH_FIREFOX_PROFILES']:
env_key = self.get_default(key + '_ENV')
env_vars.append(env_fmt % (key, env_key))
# Channel specific Firefox binary paths.
for channel in self.sections():
export_name = 'PATH_FIREFOX_APP_' + channel.upper()
firefox_bin = self.get(channel, 'PATH_FIREFOX_BIN_ENV')
env_vars.append(env_fmt % (export_name, firefox_bin))
output = '\n'.join(env_vars) + '\n'
print(output)
with open(out_file, 'w') as env_file:
env_file.write(output)
env_file.close()
def sections(self):
"""
Shortcut for getting a config's `sections()` array without needing to
do the visually horrifying `self.config.config.sections()`.
"""
return self.config.sections()
def get(self, section, option):
"""
Shortcut for calling a config's `get()` method without needing to
do the visually horrifying `self.config.config.get()`.
"""
return self.config.get(section, option)
def set(self, section, option, value):
"""
Shortcut for calling a config's `set()` method without needing to
do the visually horrifying `self.config.config.set()`.
"""
return self.config.set(section, option, str(value))
def get_default(self, option):
"""
Shortcut to get a value from the "DEFAULTS" section of a ConfigParser
INI file. Doesn't save much time, but reads a bit easier.
"""
return self.get('DEFAULT', option)
if __name__ == '__main__':
i = IniHandler()
i.load_os_config('configs')
i.create_env_file()

72
firefox_install.py Executable file
Просмотреть файл

@ -0,0 +1,72 @@
#!/usr/bin/env python
from firefox_env_handler import IniHandler
from fabric.api import local
import os
import sys
class FirefoxInstall(object):
def __init__(self, config, archive_dir='temp'):
self.CACHE_FILE = 'cache.ini'
self.out_dir = archive_dir
self.cache_path = os.path.join(self.out_dir, self.CACHE_FILE)
self.cache = IniHandler(self.cache_path)
# Do some basic type checking on the `config` attribute.
if isinstance(config, IniHandler):
self.config = config
elif isinstance(config, str):
self.config = IniHandler()
self.config.load_os_config(config)
else:
sys.exit('FirefoxInstall: Unexpected config data type')
def install_all(self, force=False):
IniHandler.banner('INSTALLING FIREFOXES')
for channel in self.config.sections():
self.install_channel(channel, force)
def install_channel(self, channel, force=False):
was_cached = self.cache.config.getboolean('cached', channel)
filename = self.config.get(channel, 'DOWNLOAD_FILENAME')
install_dir = self.config.get(channel, 'PATH_FIREFOX_APP')
installer = os.path.join('.', self.out_dir, filename)
if force or not was_cached:
print(('Installing {0}'.format(channel)))
if IniHandler.is_linux():
# TODO: Move to /opt/* and chmod file?
# `tar -jxf firefox-beta.tar.gz -C ./beta --strip-components=1`?
local('tar -jxf {0} && mv firefox {1}'.format(installer, install_dir))
elif IniHandler.is_windows():
local('{0} -ms'.format(installer))
if channel == 'beta':
# Since Beta and General Release channels install to the same directory,
# install Beta first then rename the directory.
gr_install_dir = self.config.get('gr', 'PATH_FIREFOX_APP')
local('mv "{0}" "{1}"'.format(gr_install_dir, install_dir))
elif IniHandler.is_mac():
# TODO: Mount the DMG to /Volumes and copy to /Applications?
print('Do something...')
else:
print(('[{0}] was cached, skipping install.'.format(channel)))
local('"{0}" --version # {1}'.format(self.config.get(channel, 'PATH_FIREFOX_BIN_ENV'), channel))
def main():
ff_install = FirefoxInstall('./configs/')
ff_install.install_all(True)
if __name__ == '__main__':
main()

96
firefox_profile.py Normal file
Просмотреть файл

@ -0,0 +1,96 @@
"""
This module uses Fabric API to generate a Firefox Profile by concatenating the
following preferences files:
- ./_utils/prefs.ini
- ./<application>/prefs.ini
- ./<application>/<test_type>/prefs.ini
The profile is then created using the specified name and saved to the ../_temp/
directory.
"""
try:
import configparser # Python 3
except:
import ConfigParser as configparser # Python 2
import os
import shutil
import configargparse
from fabric.api import local # It looks like Fabric may only support Python 2.
PATH_PROJECT = os.path.abspath('../')
PATH_TEMP = os.path.join(PATH_PROJECT, '_temp')
FILE_PREFS = 'prefs.ini'
config = configparser.ConfigParser()
def _parse_args():
"""Parses out args for CLI"""
parser = configargparse.ArgumentParser(
description='CLI tool for creating Firefox profiles via mozprofile CLI')
parser.add_argument('-a', '--application',
required=True,
help='Application to test. Example: "loop-server"')
parser.add_argument('-t', '--test-type',
required=True,
help='Application test type. Example: "stack-check"')
parser.add_argument('-e', '--env',
help='Test environment. Example: "dev", "stage", ...')
parser.add_argument('-p', '--profile',
required=True,
help='Profile name.')
args = parser.parse_args()
return args, parser
def prefs_paths(application, test_type, env='stage'):
path_global = os.path.join(PATH_PROJECT, '_utils', FILE_PREFS)
path_app_dir = os.path.join(PATH_PROJECT, application)
path_app = os.path.join(path_app_dir, FILE_PREFS)
path_app_test_type = os.path.join(path_app_dir, test_type, FILE_PREFS)
valid_paths = [path_global]
if os.path.exists(path_app):
config.read(path_app)
# Make sure the specified INI file has the specified section.
if config.has_section(env):
valid_paths.append(path_app + ":" + env)
if os.path.exists(path_app_test_type):
config.read(path_app_test_type)
if config.has_section(env):
valid_paths.append(path_app_test_type + ":" + env)
return valid_paths
def create_mozprofile(application, test_type, env, profile_dir):
full_profile_dir = os.path.join(PATH_TEMP, profile_dir)
# If temp profile already exists, kill it so it doesn't merge unexpectedly.
if os.path.exists(full_profile_dir):
print("Deleting existing profile... {0}".format(full_profile_dir))
shutil.rmtree(full_profile_dir)
cmd = [
'mozprofile',
'--profile={0}'.format(full_profile_dir)
]
for path in prefs_paths(application, test_type, env):
cmd.append("--preferences=" + path)
local(" ".join(cmd))
def main():
args, parser = _parse_args()
create_mozprofile(args.application, args.test_type, args.env, args.profile)
if __name__ == '__main__':
main()

39
firefox_profile_handler.py Executable file
Просмотреть файл

@ -0,0 +1,39 @@
#!/usr/bin/env python
from firefox_env_handler import FirefoxEnvHandler, IniHandler
import sys
class FirefoxProfileHandler(object):
def __init__(self, config):
# Do some basic type checking on the `config` attribute.
if isinstance(config, IniHandler):
self.config = config
elif isinstance(config, str):
self.config = IniHandler()
self.config.load_os_config(config)
else:
sys.exit('FirefoxProfileHandler: Unexpected config data type')
self.profile_dir = self.config.get_default('PATH_FIREFOX_PROFILES_ENV')
def switch_prefs(self, profile_name, user_prefs, channel='nightly'):
channel_firefox_bin = self.config.get(channel, 'PATH_FIREFOX_BIN_ENV')
print(('{0} -CreateProfile {1}'.format(channel_firefox_bin, profile_name)))
print('copy prefs.js to <new profile name> dir')
def delete_all_profiles(self):
FirefoxEnvHandler.clean_folder(self.profile_dir)
def main():
config_path = './configs/'
FirefoxProfileHandler(config_path)
if __name__ == '__main__':
main()

45
firefox_tool.py Normal file
Просмотреть файл

@ -0,0 +1,45 @@
"""firefox test tool helper script"""
import glob
import configargparse
def _parse_args():
"""Parses out args for CLI"""
parser = configargparse.ArgumentParser(
description='cross-platform CLI tool for installing firefox and managing profiles')
parser.add_argument('-i', '--install',
help='install firefox version (release, beta, aurora, nightly',
default='nightly',
type=str)
parser.add_argument('-u', '--uninstall',
help='install firefox version (release, beta, aurora, nightly, ALL',
type=str)
parser.add_argument('-p', '--create-profile',
help='create new profile (indicate name)',
type=str)
parser.add_argument('-d', '--delete-profile',
help='delete profile (indicate name)',
type=str)
parser.add_argument('-s', '--set-profile-path',
help='-s <path to profile>',
default='<put OS-specific default path here??>',
type=str)
args = parser.parse_args()
return args, parser
def main():
"""Main entrypoint for CLI"""
args, parser = _parse_args()
print('INSTALL: {0}'.format(args.install))
print('UNINSTALL: {0}'.format(args.uninstall))
print('CREATE PROFILE: {0}'.format(args.create_profile))
print('DELETE PROFILE: {0}'.format(args.delete_profile))
print('SET PROFILE PATH: {0}'.format(args.set_profile_path))
if __name__ == '__main__':
main()

63
firefox_uninstall.py Executable file
Просмотреть файл

@ -0,0 +1,63 @@
#!/usr/bin/env python
from firefox_env_handler import IniHandler
from fabric.api import local
import os
import sys
class FirefoxUninstall(object):
def __init__(self, config, archive_dir="temp"):
self.CACHE_FILE = "cache.ini"
self.out_dir = archive_dir
self.cache_path = os.path.join(self.out_dir, self.CACHE_FILE)
self.cache = IniHandler(self.cache_path)
# Do some basic type checking on the `config` attribute.
if isinstance(config, IniHandler):
self.config = config
elif isinstance(config, str):
self.config = IniHandler()
self.config.load_os_config(config)
else:
sys.exit("FirefoxUninstall: Unexpected config data type")
def uninstall_all(self, force=False):
"""
Delete all the Firefox apps (nightly, aurora, beta, general release),
and then delete the shared profiles directory.
"""
IniHandler.banner("UNINSTALLING FIREFOXES")
for channel in self.config.sections():
self.uninstall_channel(channel, force)
def uninstall_channel(self, channel, force=False):
was_cached = self.cache.config.getboolean("cached", channel)
if force or not was_cached:
path_firefox_app = self.config.get(channel, "PATH_FIREFOX_APP")
if not os.path.isdir(path_firefox_app):
print(('Firefox not found: {0}'.format(path_firefox_app)))
return
# If we're on Windows/Cygwin, use the uninstaller.
if self.config.is_windows():
local("\"{0}/uninstall/helper.exe\" -ms".format(path_firefox_app))
# Otherwise just rimraf the Firefox folder.
else:
IniHandler.clean_folder(path_firefox_app, False)
else:
print(("[%s] was cached, skipping uninstall." % (channel)))
def main():
config_path = "./configs/"
ff_uninstall = FirefoxUninstall(config_path)
ff_uninstall.uninstall_all()
if __name__ == '__main__':
main()