def install_file(path, **substitutions): '''Install file with path on the host target. The from file is the first of this list which exists: * custom file * custom file.template * common file * common file.template ''' from_head = dirname(dirname(__file__)) from_tail = join('files', path) if path.startswith('~/'): from_tail = join('files', 'home', 'USERNAME', path[2:]) from_common = join(from_head, 'fabfile_data', from_tail) from_custom = join(from_head, 'fabsetup_custom', from_tail) path_dir = dirname(path) if isfile(from_custom): run(flo('mkdir -p {path_dir}')) put(from_custom, path) elif isfile(from_custom + '.template'): _install_file_from_template(from_custom + '.template', to_=path, **substitutions) elif isfile(from_common): run(flo('mkdir -p {path_dir}')) put(from_common, path) else: _install_file_from_template(from_common + '.template', to_=path, **substitutions)
def install_user_command_legacy(command, **substitutions): '''Install command executable file into users bin dir. If a custom executable exists it would be installed instead of the "normal" one. The executable also could exist as a <command>.template file. ''' path = flo('~/bin/{command}') install_file_legacy(path, **substitutions) run(flo('chmod 755 {path}'))
def install_file(path, sudo=False, from_path=None, **substitutions): '''Install file with path on the host target. Args: path(str): Where file has to be installed to sudo(bool): If True install as superuser, eg. when path='/root/foo' from_path([None] or str): Optional path where the file comes from substitutions: kwargs with substitutions when installing a file from a template. If from_path is None, the from file will become the first in this list which exists: * custom_files_basedir/path/to/file * custom_files_basedir/path/to/file.template * default_files_basedir/path/to/file * default_files_basedir/path/to/file.template 'custom_files_basedir' is the dir `files` in the custom addon dir under ~/.fabsetup-custom. 'default_files_basedir' is the dir `files` in the module dir of the addon-package. 'path/to_file' is path as an relative path, a '~' will be expanded to 'home/USERNAME'. ''' if from_path is None: from_custom, from_default = _determine_froms(addon_package, path) from_custom_template = flo('{from_custom}.template') from_default_template = flo('{from_default}.template') if isfile(from_custom): from_path = from_custom elif isfile(from_custom_template): from_path = from_custom_template elif isfile(from_default): from_path = from_default else: from_path = from_default_template to_path = _substituted(path, substitutions) if from_path.endswith('.template'): from_str = filled_out_template(from_path, **substitutions) with tempfile.NamedTemporaryFile( prefix=addon_package.package_name) as tmp_file: print(flo('template: {from_path}')) print(flo('filled out template: {tmp_file.name}')) with open(tmp_file.name, 'w') as fp: fp.write(from_str) _install_file(tmp_file.name, to_path, sudo) else: _install_file(from_path, to_path, sudo)
def checkup_git_repo(url, name=None, base_dir=package.downloads_basedir, verbose=True, prefix='', postfix=''): '''Checkout or update a git repo.''' if not name: # eg. url = 'https://github.com/my-repo-name.git' # => name = 'my-repo-name' match = re.match(r'.*/(.+)\.git', url) assert match, flo("Unable to extract repo name from '{url}'") name = match.group(1) assert name is not None, flo( 'Cannot extract repo name from repo: {url}') assert name != '', flo( 'Cannot extract repo name from repo: {url} (empty)') if verbose: name_blue = blue(name) print_msg(flo('{prefix}Checkout or update {name_blue}{postfix}')) filename_readme = '~/.fabsetup-downloads/README.md' if not exists(filename_readme): install_file_legacy(filename_readme) if not exists(base_dir): run(flo('mkdir -p {base_dir}')) if not exists(flo('{base_dir}/{name}/.git')): run(flo(' && '.join([ 'cd {base_dir}', 'git clone {url} {name}'])), msg='clone repo') else: if verbose: print_msg('update: pull from origin') run(flo('cd {base_dir}/{name} && git pull')) return flo('{base_dir}/{name}')
def checkup_git_repo(url, name=None, base_dir=package.downloads_basedir, verbose=True, prefix='', postfix=''): '''Checkout or update a git repo.''' if not name: # eg. url = 'https://github.com/my-repo-name.git' # => name = 'my-repo-name' match = re.match(r'.*/(.+)\.git', url) assert match, flo("Unable to extract repo name from '{url}'") name = match.group(1) assert name is not None, flo( 'Cannot extract repo name from repo: {url}') assert name != '', flo( 'Cannot extract repo name from repo: {url} (empty)') if verbose: name_blue = blue(name) print_msg(flo('{prefix}Checkout or update {name_blue}{postfix}')) filename_readme = '~/.fabsetup-downloads/README.md' if not exists(filename_readme): install_file_legacy(filename_readme) if not exists(base_dir): run(flo('mkdir -p {base_dir}')) if not exists(flo('{base_dir}/{name}/.git')): run(flo(' && '.join( ['cd {base_dir}', 'git clone {url} {name}'])), msg='clone repo') else: if verbose: print_msg('update: pull from origin') run(flo('cd {base_dir}/{name} && git pull')) return flo('{base_dir}/{name}')
def _install_file(from_path, to_path, sudo): path_dir = dirname(to_path) if sudo: tmp_file = join( os.sep, 'tmp', addon_package.package_name + '_' + os.path.basename(to_path)) print(flo('temporary file: {tmp_file}')) put(from_path, tmp_file) run(flo('mkdir -p {path_dir}')) run(flo('sudo mv --force {tmp_file} {to_path}')) else: run(flo('mkdir -p {path_dir}')) put(from_path, to_path)
def checkup_git_repo_legacy(url, name=None, base_dir='~/repos', verbose=False, prefix='', postfix=''): '''Checkout or update a git repo.''' if not name: match = re.match(r'.*/(.+)\.git', url) assert match, flo("Unable to extract repo name from '{url}'") name = match.group(1) assert name is not None, flo('Cannot extract repo name from repo: {url}') assert name != '', flo('Cannot extract repo name from repo: {url} (empty)') if verbose: name_blue = blue(name) print_msg(flo('{prefix}Checkout or update {name_blue}{postfix}')) if not exists(base_dir): run(flo('mkdir -p {base_dir}')) if not exists(flo('{base_dir}/{name}/.git')): run(flo(' && '.join(['cd {base_dir}', 'git clone {url} {name}'])), msg='clone repo') else: if verbose: print_msg('update: pull from origin') run(flo('cd {base_dir}/{name} && git pull')) return name
def determine_latest_pythons(minors): '''Determine latest stable python versions and return them as a list of str. Args: minors([<str>,..]): List of python minor versions as str, eg. ['2.6', '2.7', '3.3', '3.4', '3.5', '3.6'] Return example: ['2.6.9', '2.7.14', '3.3.7', '3.4.8', '3.5.5', '3.6.4'] ''' # eg: ['2.6.9', '2.7.14', '3.3.7', '3.4.8', '3.5.5', '3.6.4'] latests = [] versions_str = fabric.api.local(flo( 'pyenv install --list | tr -d [:blank:] | ' 'grep -P "^[\d\.]+$"'), capture=True) versions = versions_str.split() for minor in minors: candidates = [ version for version in versions if version.startswith(minor) ] # sort version numbers: https://stackoverflow.com/a/2574090 candidates.sort(key=lambda s: [int(u) for u in s.split('.')]) latest = candidates[-1] latests.append(latest) print(latests) return latests
def _non_installed(packages): non_installed = [] with quiet(): for pkg in packages: if run(flo('dpkg --status {pkg}')).return_code != 0: non_installed.append(pkg) return non_installed
def update_or_append_line(filename, prefix, new_line, keep_backup=True, append=True): '''Search in file 'filename' for a line starting with 'prefix' and replace the line by 'new_line'. If a line starting with 'prefix' not exists 'new_line' will be appended. If the file not exists, it will be created. Return False if new_line was appended, else True (i.e. if the prefix was found within of the file). ''' result = None if env.host_string == 'localhost': result = update_or_append_local(filename, prefix, new_line, keep_backup, append) else: tmp_dir = tempfile.mkdtemp(suffix='', prefix='fabsetup_') # fabric.api.local(flo('chmod 777 {tmp_dir}')) local_path = os.path.join(tmp_dir, os.path.basename(filename)) fabric.operations.get(remote_path=filename, local_path=local_path, use_sudo=True, temp_dir='/tmp') result = update_or_append_local(local_path, prefix, new_line, keep_backup, append) put(local_path, remote_path=filename, use_sudo=True, temp_dir='/tmp') with quiet(): fabric.api.local(flo('rm -rf {tmp_dir}')) return result
def put(*args, **kwargs): func, kwargs = _func(kwargs, func_remote=fabric.operations.put) if func == fabric.api.local: from_, to = [os.path.expanduser(arg) for arg in args] args = [flo('cp {from_} {to}')] kwargs = {} return func(*args, **kwargs)
def determine_latest_pythons(minors): '''Determine latest stable python versions and return them as a list of str. Args: minors([<str>,..]): List of python minor versions as str, eg. ['2.6', '2.7', '3.3', '3.4', '3.5', '3.6'] Return example: ['2.6.9', '2.7.14', '3.3.7', '3.4.8', '3.5.5', '3.6.4'] ''' # eg: ['2.6.9', '2.7.14', '3.3.7', '3.4.8', '3.5.5', '3.6.4'] latests = [] versions_str = fabric.api.local(flo( 'pyenv install --list | tr -d [:blank:] | ' 'grep -P "^[\d\.]+$"'), capture=True) versions = versions_str.split() for minor in minors: candidates = [version for version in versions if version.startswith(minor)] # sort version numbers: https://stackoverflow.com/a/2574090 candidates.sort(key=lambda s: [int(u) for u in s.split('.')]) latest = candidates[-1] latests.append(latest) print(latests) return latests
def wrapper(*args, **kwargs): if not os.path.exists(FABSETUP_CUSTOM_DIR): msg = '''\ Git repository ~/.fabsetup-custom with configurations does not exist. This configs are required to use fabsetup. Clone it if you already have your own fabsetup-custom repository: git clone <user>@<hostname>:/path/to/fabsetup-custom.git ~/.fabetup-custom Else, initialize a new repository. Init a new repository `~/.fabsetup-custom`?''' if not query_yes_no(msg, default='yes'): sys.exit('abort') custom_dir = FABSETUP_CUSTOM_DIR presetting_dir = join(FABFILE_DATA_DIR, 'presetting-fabsetup-custom') if not isdir(custom_dir): print(yellow('\n** ** Init ') + yellow('~/.fabsetup-custom', bold=True) + yellow(' ** **\n')) print(yellow(flo('** Create files in dir {custom_dir} **'))) local(flo('mkdir -p {custom_dir}')) local(flo('cp -r --no-clobber {presetting_dir}/. {custom_dir}')) import_fabsetup_custom(globals()) else: with quiet(): local(flo( 'cp -r --no-clobber {presetting_dir}/. {custom_dir}')) if not isdir(join(custom_dir, '.git')): print(yellow( '\n** Git repo ~/.fabsetup-custom: ' 'init and first commit **')) local(flo('cd {custom_dir} && git init')) local(flo('cd {custom_dir} && git add .')) local(flo('cd {custom_dir} && git commit -am "Initial commit"')) print(yellow("** Done. Don't forget to create a backup of your " '~/.fabsetup-custom repo **\n')) print(yellow("** But do not make it public, it's custom **\n", bold=True)) else: with quiet(): cmd = flo('cd {custom_dir} && git status --porcelain') res = local(cmd, capture=True) if res: print(yellow('\n** git repo ') + magenta('~/.fabsetup-custom ') + yellow('has uncommitted changes: **')) print(cmd) print(yellow(res, bold=True)) print(yellow( "** Don't forget to commit them and make a " "backup of your repo **\n")) return func(*args, **kwargs)
def install_file_legacy(path, sudo=False, from_path=None, **substitutions): '''Install file with path on the host target. The from file is the first of this list which exists: * custom file * custom file.template * common file * common file.template ''' # source paths 'from_custom' and 'from_common' from_path = from_path or path # remove beginning '/' (if any), eg '/foo/bar' -> 'foo/bar' from_tail = join('files', from_path.lstrip(os.sep)) if from_path.startswith('~/'): from_tail = join('files', 'home', 'USERNAME', from_path[2:]) # without beginning '~/' from_common = join(FABFILE_DATA_DIR, from_tail) from_custom = join(FABSETUP_CUSTOM_DIR, from_tail) # target path 'to_' (path or tempfile) for subst in ['SITENAME', 'USER', 'ADDON', 'TASK']: sitename = substitutions.get(subst, False) if sitename: path = path.replace(subst, sitename) to_ = path if sudo: to_ = join(os.sep, 'tmp', 'fabsetup_' + os.path.basename(path)) path_dir = dirname(path) # copy file if isfile(from_custom): run(flo('mkdir -p {path_dir}')) put(from_custom, to_) elif isfile(from_custom + '.template'): _install_file_from_template_legacy(from_custom + '.template', to_=to_, **substitutions) elif isfile(from_common): run(flo('mkdir -p {path_dir}')) put(from_common, to_) else: _install_file_from_template_legacy(from_common + '.template', to_=to_, **substitutions) if sudo: run(flo('sudo mv --force {to_} {path}'))
def _install_file_from_template_legacy(from_template, to_, **substitutions): from_str = filled_out_template(from_template, **substitutions) with tempfile.NamedTemporaryFile(prefix='fabsetup') as tmp_file: with open(tmp_file.name, 'w') as fp: fp.write(from_str) to_dir = dirname(to_) run(flo('mkdir -p {to_dir}')) put(tmp_file.name, to_)
def wrapper(*args, **kwargs): custom_dir = join(dirname(dirname(__file__)), 'fabsetup_custom') presetting_dir = join(dirname(dirname(__file__)), 'fabfile_data', 'presetting_fabsetup_custom') if not isdir(custom_dir): print(yellow('\n** ** Init ') + yellow('fabsetup_custom', bold=True) + yellow(' ** **\n')) print(yellow('** Create files in dir fabsetup_custom **')) local(flo('mkdir -p {custom_dir}')) local(flo('cp -r --no-clobber {presetting_dir}/. {custom_dir}')) else: with quiet(): local(flo('cp -r --no-clobber {presetting_dir}/. {custom_dir}')) if not isdir(join(custom_dir, '.git')): print(yellow('\n** Git repo fabsetup_custom: init and first commit **')) local(flo('cd {custom_dir} && git init')) local(flo('cd {custom_dir} && git add .')) local(flo('cd {custom_dir} && git commit -am "Initial commit"')) print(yellow("** Done. Don't forget to create a backup of your fabsetup_custom repo **\n")) print(yellow("** But do not make it public, it's custom **\n", bold=True)) else: with quiet(): cmd = flo('cd {custom_dir} && git status --porcelain') res = local(cmd, capture=True) if res: print(yellow('\n** git repo fabsetup_custom has uncommitted changes: **')) print(cmd) print(yellow(res, bold=True)) print(yellow("** Don't forget to commit them and make a backup of your repo **\n")) return func(*args, **kwargs)
def install_packages(packages, what_for='for a complete setup to work properly'): '''Try to install .deb packages given by list. Return True, if packages could be installed or are installed already, or if they cannot be installed but the user gives feedback to continue. Else return False. ''' res = True non_installed_packages = _non_installed(packages) packages_str = ' '.join(non_installed_packages) if non_installed_packages: with quiet(): dpkg = _has_dpkg() hint = ' (You may have to install them manually)' do_install = False go_on = True if dpkg: if _is_sudoer('Want to install dpkg packages'): do_install = True else: do_install is False # cannot install anything info = yellow(' '.join([ 'This deb packages are missing to be installed', flo("{what_for}: "), ', '.join(non_installed_packages), ])) question = ' Continue anyway?' go_on = query_yes_no(info + hint + question, default='no') else: # dpkg == False, unable to determine if packages are installed do_install = False # cannot install anything info = yellow(' '.join([ flo('Required {what_for}: '), ', '.join(non_installed_packages), ])) go_on = query_yes_no(info + hint + ' Continue?', default='yes') if not go_on: sys.exit('Abort') if do_install: command = flo('sudo apt-get install {packages_str}') res = run(command).return_code == 0 return res
def checkup_git_repos(repos, base_dir='~/repos'): '''Checkout or update git repos. repos must be a list of dicts each with an url and optional with a name value. ''' run(flo('mkdir -p {base_dir}')) for repo in repos: name = repo.get('name', None) if not name: name = re.match(r'.*/([^.]+)\.git', repo['url']).group(1) assert name is not None assert name != '' if not exists(flo('{base_dir}/{name}/.git')): url = repo['url'] run(flo(' && '.join([ 'cd {base_dir}', 'git clone {url} {name}' ]))) else: run(flo('cd {base_dir}/{name} && git pull'))
def check_reboot(): print(magenta('Reboot required?')) # check file '/var/run/reboot-required', cf. http://askubuntu.com/a/171 res = run('; '.join([ "prefix='\\033['; postfix='\\033[0m'", "if [ -f /var/run/reboot-required ]", 'then echo "${prefix}31mReboot required!${postfix}"', 'else echo "${prefix}32mNope.${postfix}"', 'fi', ])) if res: fabric.operations.local(flo('echo "{res}"')) # interpolate res
def checkup_git_repos_legacy(repos, base_dir='~/repos', verbose=False, prefix='', postfix=''): '''Checkout or update git repos. repos must be a list of dicts each with an url and optional with a name value. ''' run(flo('mkdir -p {base_dir}')) for repo in repos: cur_base_dir = repo.get('base_dir', base_dir) checkup_git_repo_legacy(url=repo['url'], name=repo.get('name', None), base_dir=cur_base_dir, verbose=verbose, prefix=prefix, postfix=postfix)
def setup_fonts_for_powerline_shell(): repo_dir = checkup_git_repo('https://github.com/powerline/fonts.git', name='powerline-fonts') run(flo('cd {repo_dir} && ./install.sh')) # run('fc-cache -vf ~/.local/share/fonts') prefix = 'URxvt*font: ' from config import fontlist line = prefix + fontlist update_or_append_line(filename='~/.Xresources', prefix=prefix, new_line=line) if env.host_string == 'localhost': run('xrdb ~/.Xresources')
def highest_minor(python_versions): '''Return highest minor of a list of stable (semantic) versions. Example: >>> python_versions = [ ... '2.6.9', '2.7.14', '3.3.7', '3.4.8', '3.5.5', '3.6.4'] >>> highest_minor(python_versions) '3.6' ''' highest = python_versions[-1] major, minor, patch = highest.split('.', 2) return flo('{major}.{minor}')
def extract_minors_from_setup_py(filename_setup_py): '''Extract supported python minor versions from setup.py and return them as a list of str. Return example: ['2.6', '2.7', '3.3', '3.4', '3.5', '3.6'] ''' # eg: minors_str = '2.6\n2.7\n3.3\n3.4\n3.5\n3.6' minors_str = fabric.api.local(flo( 'grep --perl-regexp --only-matching ' '"(?<=Programming Language :: Python :: )\\d+\\.\\d+" ' '{filename_setup_py}'), capture=True) # eg: minors = ['2.6', '2.7', '3.3', '3.4', '3.5', '3.6'] minors = minors_str.split() return minors
def extract_minors_from_setup_py(filename_setup_py): '''Extract supported python minor versions from setup.py and return them as a list of str. Return example: ['2.6', '2.7', '3.3', '3.4', '3.5', '3.6'] ''' # eg: minors_str = '2.6\n2.7\n3.3\n3.4\n3.5\n3.6' minors_str = fabric.api.local( flo('grep --perl-regexp --only-matching ' '"(?<=Programming Language :: Python :: )\\d+\\.\\d+" ' '{filename_setup_py}'), capture=True) # eg: minors = ['2.6', '2.7', '3.3', '3.4', '3.5', '3.6'] minors = minors_str.split() return minors
def dn_cn_of_certificate_with_san(domain): '''Return the Common Name (cn) from the Distinguished Name (dn) of the certificate which contains the `domain` in its Subject Alternativ Name (san) list. Needs repo ~/.fabsetup-custom. Return None if no certificate is configured with `domain` in SAN. ''' cn_dn = None from config import domain_groups cns = [domains[0] for domains in domain_groups if domain in domains] if cns: if len(cns) > 1: print_msg( yellow( flo('Several certificates are configured to ' 'contain {domain} ' '(You should clean-up your config.py)\n'))) cn_dn = cns[0] return cn_dn
def dn_cn_of_certificate_with_san(domain): '''Return the Common Name (cn) from the Distinguished Name (dn) of the certificate which contains the `domain` in its Subject Alternativ Name (san) list. Needs repo ~/.fabsetup-custom. Return None if no certificate is configured with `domain` in SAN. ''' cn_dn = None from config import domain_groups cns = [domains[0] for domains in domain_groups if domain in domains] if cns: if len(cns) > 1: print_msg(yellow(flo('Several certificates are configured to ' 'contain {domain} ' '(You should clean-up your config.py)\n'))) cn_dn = cns[0] return cn_dn
def checkup_git_repo_legacy(url, name=None, base_dir='~/repos', verbose=False, prefix='', postfix=''): '''Checkout or update a git repo.''' if not name: match = re.match(r'.*/(.+)\.git', url) assert match, flo("Unable to extract repo name from '{url}'") name = match.group(1) assert name is not None, flo('Cannot extract repo name from repo: {url}') assert name != '', flo('Cannot extract repo name from repo: {url} (empty)') if verbose: name_blue = blue(name) print_msg(flo('{prefix}Checkout or update {name_blue}{postfix}')) if not exists(base_dir): run(flo('mkdir -p {base_dir}')) if not exists(flo('{base_dir}/{name}/.git')): run(flo(' && '.join([ 'cd {base_dir}', 'git clone {url} {name}'])), msg='clone repo') else: if verbose: print_msg('update: pull from origin') run(flo('cd {base_dir}/{name} && git pull')) return name
def print_msg(msg): if msg is not None: print(cyan(flo('{msg}')))
def wrapper(*args, **kwargs): if not os.path.exists(FABSETUP_CUSTOM_DIR): msg = '''\ Git repository ~/.fabsetup-custom with configurations does not exist. This configs are required to use fabsetup. Clone it if you already have your own fabsetup-custom repository: git clone <user>@<hostname>:/path/to/fabsetup-custom.git ~/.fabetup-custom Else, initialize a new repository. Init a new repository `~/.fabsetup-custom`?''' if not query_yes_no(msg, default='yes'): sys.exit('abort') custom_dir = FABSETUP_CUSTOM_DIR presetting_dir = join(FABFILE_DATA_DIR, 'presetting-fabsetup-custom') if not isdir(custom_dir): print( yellow('\n** ** Init ') + yellow('~/.fabsetup-custom', bold=True) + yellow(' ** **\n')) print(yellow(flo('** Create files in dir {custom_dir} **'))) local(flo('mkdir -p {custom_dir}')) local( flo('cp -r --no-clobber {presetting_dir}/. {custom_dir}')) import_fabsetup_custom(globals()) else: with quiet(): local( flo('cp -r --no-clobber {presetting_dir}/. {custom_dir}' )) if not isdir(join(custom_dir, '.git')): print( yellow('\n** Git repo ~/.fabsetup-custom: ' 'init and first commit **')) local(flo('cd {custom_dir} && git init')) local(flo('cd {custom_dir} && git add .')) local( flo('cd {custom_dir} && git commit -am "Initial commit"')) print( yellow("** Done. Don't forget to create a backup of your " '~/.fabsetup-custom repo **\n')) print( yellow("** But do not make it public, it's custom **\n", bold=True)) else: with quiet(): cmd = flo('cd {custom_dir} && git status --porcelain') res = local(cmd, capture=True) if res: print( yellow('\n** git repo ') + magenta('~/.fabsetup-custom ') + yellow('has uncommitted changes: **')) print(cmd) print(yellow(res, bold=True)) print( yellow("** Don't forget to commit them and make a " "backup of your repo **\n")) return func(*args, **kwargs)