def install_project_user(): """ Create project user and groups. Create user home dir. Disable ssh host checking. Create log dir. """ from .project import project_home with sudo(): info('Install application user') username = blueprint.get('project') home_path = project_home() # Setup groups for project user project_user_groups = ['app-data', 'www-data'] for group in project_user_groups: debian.groupadd(group, gid_min=10000) # Get UID for project user user.create_system_user(username, groups=project_user_groups, home=home_path) # Create application log path application_log_path = os.path.join('/var', 'log', username) debian.mkdir(application_log_path, group='app-data', mode=1775) # Configure ssh for github user.set_strict_host_checking(username, 'github.com')
def deploy(auto_reload=True): """ Reset source to configured branch and install requirements, if needed :return: Got new source? """ # Reset git repo previous_commit, current_commit = update_source() code_changed = previous_commit != current_commit if code_changed: # Check if requirements has changed requirements = blueprint.get('requirements', 'requirements.txt') commit_range = '{}..{}'.format(previous_commit, current_commit) requirements_changed, _, _ = git.diff_stat(git_repository_path(), commit_range, requirements) # Install repo requirements.txt info('Install requirements {}', requirements) if requirements_changed: install_requirements() else: info(indent('(requirements not changed in {}...skipping)'), commit_range) if auto_reload: reload() return code_changed
def add_fstab(filesystem=None, mount_point=None, type='auto', options='rw', dump='0', pazz='0'): """ Add mount point configuration to /etc/fstab. If mount point already mounted on different file system then unmount. :param str filesystem: The partition or storage device to be mounted :param str mount_point: The mount point where <filesystem> is mounted to :param str type: The file system type (Default: auto) :param str options: Mount options of the filesystem (Default: rw) :param str dump: Used by the dump utility to decide when to make a backup, 0|1 (Default: 0) :param str pazz: Used by fsck to decide which order filesystems are to be checked (Default: 0) """ with sudo(): fstab_line = '{fs} {mount} {type} {options} {dump} {pazz}'.format( fs=filesystem, mount=mount_point, type=type, options=options, dump=dump, pazz=pazz ) validate_boot_options(options) # Add mount to /etc/fstab if not already there (?) with silent(): output = run('cat /etc/fstab') fstab = output.stdout if fstab_line not in fstab.split('\n'): # TODO: Handle comments info('Adding fstab: {} on {}', filesystem, mount_point) fabric.contrib.files.append('/etc/fstab', fstab_line, use_sudo=True) # Unmount any previous mismatching mount point mounted_file_system = get_mount(mount_point) if mounted_file_system and mounted_file_system != filesystem: unmount(mount_point)
def maybe_install_requirements(previous_commit, current_commit, force=False, update_pip=False): from .project import requirements_txt, git_repository_path installation_file = requirements_txt() installation_method = get_installation_method(installation_file) has_changed = False commit_range = '{}..{}'.format(previous_commit, current_commit) if not force: if installation_method == 'pip': has_changed, added, removed = diff_requirements( previous_commit, current_commit, installation_file) if has_changed: info('Requirements have changed, added: {}, removed: {}'.format( ', '.join(added), ', '.join(removed))) else: # Check if installation_file has changed commit_range = '{}..{}'.format(previous_commit, current_commit) has_changed, _, _ = git.diff_stat( git_repository_path(), commit_range, installation_file) if has_changed or force: install_requirements(installation_file, update_pip=update_pip) else: info(indent('(requirements not changed in {}...skipping)'), commit_range)
def deploy(auto_reload=True, force=False): """ Reset source to configured branch and install requirements, if needed :param bool auto_reload: Reload application providers if source has changed :param bool force: Force install of requirements :return bool: Source code has changed? """ # Reset git repo previous_commit, current_commit = update_source() code_changed = current_commit is not None and previous_commit != current_commit if use_virtualenv() and (code_changed or force): requirements = blueprint.get('requirements', 'requirements.txt') requirements_changed = False if not force: # Check if requirements has changed commit_range = '{}..{}'.format(previous_commit, current_commit) requirements_changed, _, _ = git.diff_stat(git_repository_path(), commit_range, requirements) # Install repo requirements.txt info('Install requirements {}', requirements) if requirements_changed or force: install_requirements() else: info(indent('(requirements not changed in {}...skipping)'), commit_range) if auto_reload: reload() return code_changed
def install_system_dependencies(): """ Install system wide packages that application depends on. """ with sudo(), silent(): info('Install system dependencies') system_dependencies = blueprint.get('system_dependencies') if system_dependencies: dependencies = [] repositories = [] ppa_dependencies = [] for dependency in system_dependencies: dep, _, rep = dependency.partition('@') if rep: if rep not in repositories: repositories.append(rep) ppa_dependencies.append(dep) elif dep not in dependencies: dependencies.append(dep) debian.apt_get_update() debian.apt_get('install', *dependencies) if repositories: for repository in repositories: debian.add_apt_repository(repository, src=True) debian.apt_get_update() debian.apt_get('install', *ppa_dependencies)
def deploy(revision=None, auto_reload=True, force=False, update_pip=False): """ Reset source to configured branch and install requirements, if needed :param bool auto_reload: Reload application providers if source has changed :param bool force: Force install of requirements :return bool: Source code has changed? """ from .deploy import update_source from .project import use_virtualenv # Reset git repo previous_commit, current_commit = update_source(revision) code_changed = current_commit is not None and previous_commit != current_commit if code_changed: info('Updated git repository from: {} to: {}', previous_commit, current_commit) else: info('Reset git repository to: {}', current_commit) if code_changed or force: # Install python dependencies if use_virtualenv(): maybe_install_requirements(previous_commit, current_commit, force, update_pip=update_pip) # Reload providers if auto_reload: reload() return (previous_commit, current_commit) if code_changed else False
def validate_boot_options(options): """ Validate systemd boot options """ if lsb_release() == '14.04': none_boot_options_check=boot_options_16 else: none_boot_options_check=boot_options_14 for option in options.split(','): stript=0 if re.search('=',option): option = option.split('=')[0] stript=1 if option in none_boot_options_check: warn(option +" is not a valid for "+lsb_release()+" check boot option in your yaml") if lsb_release() == '14.04': info("Valid boot options is:"+str(boot_options_14)) else: info("Valid boot options is:"+str(boot_options_16)) exit() if stript==0: if re.search('x-systemd',option): option = option.replace("x-systemd.","") if option in boot_options_16_more: warn("Missing = for x-systemd."+option) exit()
def service(name, action, check_status=True): c = fabric.context_managers with sudo('root'), c.settings(c.hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True): info('Service: {} {}', name, action) if check_status: output = run('service {} status'.format(name), pty=False, combine_stderr=True) if output.return_code != 0: puts(indent(magenta(output))) return elif action in output: puts( indent('...has status {}'.format( magenta(output[len(name) + 1:])))) return output = run('service {} {}'.format(name, action), pty=False, combine_stderr=True) if output.return_code != 0: puts(indent(magenta(output)))
def deployed(): """ Show deployed and last origin commit """ from .project import sudo_project, git_repository_path msg = '' params = [] with sudo_project(): repository_path = git_repository_path() git.fetch(repository_path) head_tag, head_tag_delta = git.current_tag(repository_path) if head_tag_delta > 0: msg += 'Latest tag: {} distance: {}' params += [head_tag, head_tag_delta] else: msg += 'Deployed tag: {}' params += [head_tag] head_commit, head_message = git.log(repository_path)[0] msg += '\nRevision: {} comment: {}' params += [head_commit, head_message] origin = git.get_origin(repository_path) origin_commit, origin_message = git.log(repository_path, refspec=origin)[0] if head_commit != origin_commit: msg += '\nRemote: {} revision: {} comment: {}' params += [origin, origin_commit, origin_message] info(msg, *params) return head_commit, origin_commit
def deploy(auto_reload=True, force=False): """ Reset source to configured branch and install requirements, if needed :return: Got new source? """ # Reset git repo previous_commit, current_commit = update_source() code_changed = current_commit is not None and previous_commit != current_commit if code_changed or force: requirements = blueprint.get('requirements', 'requirements.txt') requirements_changed = False if not force: # Check if requirements has changed commit_range = '{}..{}'.format(previous_commit, current_commit) requirements_changed, _, _ = git.diff_stat(git_repository_path(), commit_range, requirements) # Install repo requirements.txt info('Install requirements {}', requirements) if requirements_changed or force: install_requirements() else: info(indent('(requirements not changed in {}...skipping)'), commit_range) if auto_reload: reload() return code_changed
def get_provisioning_template(self, provider, program_name=None): """ Find the provider's configuration file for this manager. If program name is set and <provider>/<manager>/<program_name>.conf exists, it will be returned. Otherwise <provider>/<manager>/<provider>.conf will be used. :param provider: The provider whose configuration we're looking for. :param program_name: Optional program name. May be used to avoid collisions, or because it looks pretty. :return: a path to a template """ default_template = os.path.join(provider.name, self.name, provider.name) if program_name is not None: conf_name = program_name if not conf_name.endswith('.conf'): conf_name = '{}.conf'.format(conf_name) named_template = os.path.join(provider.name, self.name, conf_name) user_templates = blueprint.get_user_template_path() if os.path.isfile(os.path.join(user_templates, named_template)): info('Using local {} from {}', named_template, user_templates) return named_template return default_template
def install_project_user(): """ Create project user and groups. Create user home dir. Disable ssh host checking. Create log dir. """ with sudo(): info('Install application user') username = blueprint.get('project') home_path = project_home() # Setup groups for project user project_user_groups = ['app-data', 'www-data'] for group in project_user_groups: debian.groupadd(group, gid_min=10000) # Get UID for project user user.create_system_user(username, groups=project_user_groups, home=home_path) # Configure ssh for github user.set_strict_host_checking(username, 'github.com') # Create application log path application_log_path = os.path.join('/var', 'log', username) debian.mkdir(application_log_path, group='app-data', mode=1775)
def export(path, host, options='rw,async,no_root_squash,no_subtree_check'): config_line = '%s\t\t%s(%s)' % (path, host, options) with sudo(), cd('/etc'): if not files.contains('exports', config_line): info('Exporting: {}', path) files.append('exports', config_line) return True return False
def unmount(mount_point): """ Unmount mount point. :param str mount_point: Name of mount point to unmount """ with sudo(), silent(): info('Unmounting {}', mount_point) run('umount {}'.format(mount_point))
def install_system_dependencies(): """ Install system wide packages that application depends on. """ with sudo(): info('Install system dependencies') dependencies = blueprint.get('system_dependencies') if dependencies: debian.apt_get('install', *dependencies)
def status(user=None): """ Dumps the crontab for a single user """ if not user: abort('Please specify user account') info('Current crontab for {}:', user) with sudo(), hide_prefix(): run('crontab -l -u {}'.format(user))
def install_requirements(): """ Pip install requirements in project virtualenv. """ with sudo_project(): info('Install requirements') path = virtualenv_path() requirements = requirements_txt() with virtualenv.activate(path): python.pip('install', '-r', requirements)
def disable(user=None): """ Removes the crontab for a single user """ if not user: abort('Please specify user account') with sudo(): info('Disabling crontab for {}...', user) run('crontab -r -u {}'.format(user))
def configure(): """ Install crontab per termplate (user) """ with sudo(), silent(): with debian.temporary_dir(mode=555) as temp_dir: updates = blueprint.upload('./', temp_dir) for update in updates: user = os.path.basename(update) info('Installing new crontab for {}...', user) run('crontab -u {} {}'.format(user, os.path.join(temp_dir, user)))
def grant(schema, user, password, privs='ALL', host='localhost'): if not '.' in schema: schema = "%s.*" % schema info('Granting user {} @ {} to schema {}'.format(user, host, schema)) grant_cmd = "GRANT {privs} ON {schema} TO '{user}'@'{host}' IDENTIFIED BY '{password}';" client_exec(grant_cmd, privs=privs, schema=schema, user=user, host=host, password=password)
def kill(sig, process, use_pkill=False): with sudo(): with silent('warnings'): if use_pkill: output = run('pkill -{} {}'.format(sig, process)) else: output = run('kill -{} {}'.format(sig, process)) if output.return_code != 0: warn('No process got {} signal'.format(sig)) else: info('Successfully sent {} signal to {}', sig, process)
def list(*values): """ List sysctl values, e.g. vm.swappiness,vm.panic_on_oom' """ with sudo(), silent(): if not values: for key in run('sysctl -a').split('\n'): info(key) else: for value in values: info(run('sysctl %s' % value))
def configure(): """ Install incrontab per template (i.e. user) """ with sudo(), silent(): updates = blueprint.upload('./', '/etc') users = [os.path.basename(update) for update in updates] put(StringIO('\n'.join(users)), '/etc/incron.allow', use_sudo=True) for user in users: info('Installing new incrontab for {}...', user) run('incrontab -u {} {}'.format(user, os.path.join('/etc/incron.usertables', user)))
def configure(): """ Configure sysctl settings """ with sudo(): uploads = [] # Configure application local_params=blueprint.get('params',[]) uploads.append(blueprint.upload('./sysctl.conf',config_dir,{"params" : local_params})) uploads.append(blueprint.upload('./sysctl.d/', sysctl_dir)) info("In order for the new settings to work you need to reboot the system.")
def service(name, action, check_status=True, show_output=False): c = fabric.context_managers with sudo('root'), c.settings(c.hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True): info('Service: {} {}', name, action) if check_status: output = run('service {} status'.format(name), pty=False, combine_stderr=True) if output.return_code != 0: puts(indent(magenta(output))) return elif action in output: puts(indent('...has status {}'.format(magenta(output[len(name)+1:])))) return output = run('service {} {}'.format(name, action), pty=False, combine_stderr=True) if output.return_code != 0 or show_output: puts(indent(magenta(output)))
def install_project_structure(): """ Create project directory structure """ from .project import static_base, use_static with sudo(): info('Install application directory structure') create_app_root() if use_static(): # Create static web paths static_path = os.path.join(static_base(), 'static') media_path = os.path.join(static_base(), 'media') debian.mkdir(static_path, group='www-data', mode=1775) debian.mkdir(media_path, group='www-data', mode=1775)
def install_project_structure(): """ Create project directory structure """ with sudo(): info('Install application directory structure') project_name = blueprint.get('project') # Create global apps root root_path = app_root() debian.mkdir(root_path) # Create static web paths static_base = os.path.join('/srv/www/', project_name) static_path = os.path.join(static_base, 'static') media_path = os.path.join(static_base, 'media') debian.mkdir(static_path, group='www-data', mode=1775) debian.mkdir(media_path, group='www-data', mode=1775)
def configure(): """ Configure sysctl settings """ with sudo(): uploads = [] # Configure application local_params=blueprint.get('interfaces',[]) uploads.append(blueprint.upload('./isc-dhcp-server',dhcp_default_dir,{"params" : local_params})) scopes=blueprint.get('scopes',[]) uploads.append(blueprint.upload('./dhcpd.conf', config_dir,{"scopes" : scopes})) info("In order for the new settings to work you need to reboot the system !!")
def configure(): """ Install crontab per template (i.e. user) """ with sudo(), silent(): with debian.temporary_dir(mode=555) as temp_dir: updates = blueprint.upload('./', temp_dir) for user, schedule in blueprint.get('', {}).items(): tmp_file = os.path.join(temp_dir, user) blueprint.upload('./crontab', tmp_file, context={"schedule": schedule}) updates.append(user) for update in updates: if update.endswith('crontab'): continue user = os.path.basename(update) info('Installing new crontab for {}...', user) run('crontab -u {} {}'.format(user, os.path.join(temp_dir, user)))
def setup_schemas(drop=False): """ Create database schemas and grant user permissions :param drop: Drop existing schemas before creation """ schemas = blueprint.get('schemas', {}) for schema, config in schemas.iteritems(): user, password = config['user'], config['password'] host = config.get('host', 'localhost') if drop: info('Dropping schema {}', schema) client_exec('DROP DATABASE IF EXISTS {name}', name=schema) info('Creating schema {}', schema) create_db_cmd = 'CREATE DATABASE IF NOT EXISTS {name} ' \ 'CHARACTER SET UTF8 COLLATE UTF8_UNICODE_CI;' client_exec(create_db_cmd, name=schema) grant(schema, user, password, host=host)
def mount(mount_point, owner=None, group=None, **fstab): """ Mount and optionally add configuration to fstab. :param str mount_point: Name of mount point :param str owner: Name of mount point owner :param str group: Name of mount point group :param dict fstab: Optional kwargs passed to add_fstab() """ with sudo(): if fstab: add_fstab(mount_point=mount_point, **fstab) # Mount if not is_mounted(mount_point): # Ensure mount point dir exists mkdir(mount_point, owner=owner, group=group, mode=755) with silent(): info('Mounting {}', mount_point) run('mount {}'.format(mount_point))
def update_source(): """ Update application repository with configured branch. :return: tuple(previous commit, current commit) """ with sudo_project(): # Get current commit path = git_repository_path() previous_commit = git.get_commit(path, short=True) # Update source from git (reset) repository = git_repository() current_commit = git.reset(repository['branch'], repository_path=path) if current_commit is not None and current_commit != previous_commit: info(indent('(new version)')) else: info(indent('(same commit)')) return previous_commit, current_commit
def install(): with sudo(): # Generate a root password and save it in root home root_conf_path = '/root/.my.cnf' if not fabric.contrib.files.exists(root_conf_path): root_pw = generate_password() blueprint.upload('root_my.cnf', '/root/.my.cnf', {'password': root_pw}) debian.chmod('/root/.my.cnf', mode=600) else: # TODO: use fabric.operations.get instead of cat when up to date with upstream with silent(): output = run('cat {}'.format(root_conf_path)) fd = StringIO(output) config_parser = ConfigParser.RawConfigParser() config_parser.readfp(fd) root_pw = config_parser.get('client', 'password') # Install external PPA info('Adding apt key for {}', __name__) run("apt-key adv --keyserver keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A") info('Adding apt repository for {}', __name__) debian.add_apt_repository('http://repo.percona.com/apt trusty main') debian.apt_get('update') # Percona/MySQL base dependencies dependencies = ( 'percona-server-server', 'percona-server-client', 'libmysqlclient-dev', 'mysqltuner' ) # Configure debconf to autoset root password on installation prompts server_package = dependencies[0] debian.debconf_communicate('PURGE', server_package) with silent(): debian.debconf_set_selections( '{}/root_password password {}'.format(server_package, root_pw), '{}/root_password_again password {}'.format(server_package, root_pw) ) # Install package info('Installing {}', __name__) debian.apt_get('install', *dependencies) debian.debconf_communicate('PURGE', server_package) # Auto-answer mysql_secure_installation prompts prompts = { 'Enter current password for root (enter for none): ': root_pw, 'Change the root password? [Y/n] ': 'n', 'Remove anonymous users? [Y/n] ': 'Y', 'Disallow root login remotely? [Y/n] ': 'Y', 'Remove test database and access to it? [Y/n] ': 'Y', 'Reload privilege tables now? [Y/n] ': 'Y' } # Run mysql_secure_installation to remove test-db and remote root login with settings(prompts=prompts): run('mysql_secure_installation')
def add_fstab(filesystem=None, mount_point=None, type='auto', options='rw', dump='0', pazz='0'): """ Add mount point configuration to /etc/fstab. If mount point already mounted on different file system then unmount. :param str filesystem: The partition or storage device to be mounted :param str mount_point: The mount point where <filesystem> is mounted to :param str type: The file system type (Default: auto) :param str options: Mount options of the filesystem (Default: rw) :param str dump: Used by the dump utility to decide when to make a backup, 0|1 (Default: 0) :param str pazz: Used by fsck to decide which order filesystems are to be checked (Default: 0) """ with sudo(): fstab_line = '{fs} {mount} {type} {options} {dump} {pazz}'.format( fs=filesystem, mount=mount_point, type=type, options=options, dump=dump, pazz=pazz) # Add mount to /etc/fstab if not already there (?) with silent(): output = run('cat /etc/fstab') fstab = output.stdout if fstab_line not in fstab.split('\n'): # TODO: Handle comments info('Adding fstab: {} on {}', filesystem, mount_point) fabric.contrib.files.append('/etc/fstab', fstab_line, use_sudo=True) # Unmount any previous mismatching mount point mounted_file_system = get_mount(mount_point) if mounted_file_system and mounted_file_system != filesystem: unmount(mount_point)
def install(): with sudo(): # Generate a root password and save it in root home root_conf_path = '/root/.my.cnf' if not fabric.contrib.files.exists(root_conf_path): root_pw = generate_password() blueprint.upload('root_my.cnf', '/root/.my.cnf', {'password': root_pw}) debian.chmod('/root/.my.cnf', mode=600) else: # TODO: use fabric.operations.get instead of cat when up to date with upstream with silent(): output = run('cat {}'.format(root_conf_path)) fd = StringIO(output) config_parser = ConfigParser.RawConfigParser() config_parser.readfp(fd) root_pw = config_parser.get('client', 'password') # Install external PPA info('Adding apt key for {}', __name__) run("apt-key adv --keyserver keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A" ) info('Adding apt repository for {}', __name__) debian.add_apt_repository('http://repo.percona.com/apt trusty main') debian.apt_get('update') # Percona/MySQL base dependencies dependencies = ('percona-server-server', 'percona-server-client', 'libmysqlclient-dev', 'mysqltuner') # Configure debconf to autoset root password on installation prompts server_package = dependencies[0] debian.debconf_communicate('PURGE', server_package) with silent(): debian.debconf_set_selections( '{}/root_password password {}'.format(server_package, root_pw), '{}/root_password_again password {}'.format( server_package, root_pw)) # Install package info('Installing {}', __name__) debian.apt_get('install', *dependencies) debian.debconf_communicate('PURGE', server_package) # Auto-answer mysql_secure_installation prompts prompts = { 'Enter current password for root (enter for none): ': root_pw, 'Change the root password? [Y/n] ': 'n', 'Remove anonymous users? [Y/n] ': 'Y', 'Disallow root login remotely? [Y/n] ': 'Y', 'Remove test database and access to it? [Y/n] ': 'Y', 'Reload privilege tables now? [Y/n] ': 'Y' } # Run mysql_secure_installation to remove test-db and remote root login with settings(prompts=prompts): run('mysql_secure_installation')
def deployed(): """ Show deployed and last origin commit """ with sudo_project(): repository_path = git_repository_path() git.fetch(repository_path) head_commit, head_message = git.log(repository_path)[0] origin_commit, origin_message = git.log(repository_path, commit='origin')[0] info('Deployed commit: {} - {}', head_commit[:7], head_message) if head_commit == origin_commit: info(indent('(up-to-date with origin)')) else: info('Pending release: {} - {}', origin_commit[:7], origin_message) return head_commit, origin_commit
def dump(schema=None, ignore_tables=''): """ Dump and download a schema. :param schema: Specific shema to dump and download. :param ignore_tables: Tables to skip, separated by | (pipe) """ if not schema: schemas = blueprint.get('schemas', {}).keys() for i, schema in enumerate(schemas, start=1): print("{i}. {schema}".format(i=i, schema=schema)) valid_indices = '[1-{}]+'.format(len(schemas)) schema_choice = prompt('Select schema to dump:', default='1', validate=valid_indices) schema = schemas[int(schema_choice) - 1] now = datetime.now().strftime('%Y-%m-%d') output_file = '/tmp/{}_{}.backup.gz'.format(schema, now) filename = os.path.basename(output_file) info('Dumping schema {}...', schema) extra_args = [] for table in ignore_tables.split('|'): extra_args.append('--ignore-table={}.{}'.format(schema, table)) dump_cmd = 'mysqldump {} {} | gzip > {}'.format(schema, ' '.join(extra_args), output_file) run('sudo su root -c "{}"'.format(dump_cmd)) info('Downloading dump...') local_file = '~/%s' % filename fabric.contrib.files.get(output_file, local_file) with sudo(), silent(): debian.rm(output_file) info('New smoking hot dump at {}', local_file)