def stop_unit(unit_name): state = get_unit_state(unit_name) if state['ActiveState'] == 'stopped': return Unchanged(msg='{} already stopped'.format(unit_name)) proc.run([config['cmd_systemctl'], 'stop', unit_name]) return Changed(msg='Stopped {}'.format(unit_name))
def create(self, python='python3', global_site_packages=False): # FIXME: check if python version and global_site_packages are correct, # and correct / recreat (--clear?) otherwise if not remote.stat(self.python): args = [ config['cmd_venv'], '-p', python, ] if global_site_packages: args.append('--system-site-packages') else: args.append('--no-site-packages') args.append(self.remote_path) proc.run(args) return Changed( msg='Initialized virtualenv in {}'.format(self.remote_path)) return Unchanged(msg='virtualenv at {} already initialized'.format( self.remote_path))
def reload_unit(unit_name, only_if_running=False): if only_if_running: cmd = 'reload-or-try-restart' else: cmd = 'reload-or-restart' proc.run([config['cmd_systemctl'], cmd, unit_name]) return Changed(msg='Reloaded {}'.format(unit_name))
def remove_packages(pkgs, check_first=True, purge=False, max_age=3600): if check_first and not set(pkgs).intersection( set(info_installed_packages().keys())): return Unchanged(msg='Not installed: {}'.format(' '.join(pkgs))) update(max_age) args = [config['cmd_apt_get']] args.extend([ 'remove' if not purge else 'purge', '--quiet', '--yes', # FIXME: options below don't work. why? # '--option', 'Dpkg::Options::="--force-confdef"', # '--option', 'Dpkg::Options::="--force-confold"' ]) args.extend(pkgs) proc.run( args, extra_env={ 'DEBIAN_FRONTEND': 'noninteractive', }) info_installed_packages.invalidate_cache() return Changed(msg='{} {}'.format('Removed' if not purge else 'Purged', ' '.join(pkgs)))
def disable_unit(unit_name): state = get_unit_state(unit_name) if state.get('UnitFileState') == 'disabled': return Unchanged(msg='{} already disabled'.format(unit_name)) proc.run([config['cmd_systemctl'], 'disable', unit_name]) return Changed(msg='Disabled {}'.format(unit_name))
def start_unit(unit_name): state = get_unit_state(unit_name) if 'ActiveState' in state and state['ActiveState'] == 'active' and state['SubState'] == 'running': return Unchanged(msg='{} already running'.format(unit_name)) proc.run([config['cmd_systemctl'], 'start', unit_name]) return Changed(msg='Started {}'.format(unit_name))
def install_packages(pkgs, check_first=True, release=None, max_age=3600, force=False): if check_first and set(pkgs) < set(info_installed_packages().keys()): return Unchanged(msg='Already installed: {}'.format(' '.join(pkgs))) update(max_age) args = [config['cmd_apt_get']] if release: args.extend(['-t', release]) args.extend([ 'install', '--quiet', '--yes', # FIXME: options below don't work. why? # '--option', 'Dpkg::Options::="--force-confdef"', # '--option', 'Dpkg::Options::="--force-confold"' ]) if force: args.append('--force-yes') args.extend(pkgs) proc.run(args, extra_env={'DEBIAN_FRONTEND': 'noninteractive', }) # FIXME: make this a decorator for info, add "change_invalides" decorator? info_installed_packages.invalidate_cache() # FIXME: detect if packages were installed? return Changed(msg='Installed {}'.format(' '.join(pkgs)))
def upgrade(max_age=3600, force=False, dist_upgrade=False): # FIXME: should allow upgrading selected packages update(max_age) args = [config['cmd_apt_get']] # FIXME: check for upgrades first and output proper changed status args.extend([ 'upgrade' if not dist_upgrade else 'dist-upgrade', '--quiet', '--yes', # FIXME: options below don't work. why? # '--option', 'Dpkg::Options::="--force-confdef"', # '--option', 'Dpkg::Options::="--force-confold"' ]) if force: args.append('--force-yes') proc.run( args, extra_env={ 'DEBIAN_FRONTEND': 'noninteractive', }) info_installed_packages.invalidate_cache() return Changed(msg='Upgraded all packages')
def dpkg_install(paths, check=True): if not hasattr(paths, 'keys'): pkgs = {} # determine package names from filenames. ideally, we would open the # package here and check for p in paths: fn = os.path.basename(p) try: name, version, tail = fn.split('_', 3) pkgs[(name, version)] = p except ValueError: raise ValueError( 'Could not determine package version from ' 'package filename {}. Please rename the .deb ' 'to standard debian convention ' '(name_version_arch.deb) or supply a specific ' 'version by passing a dictionary parameter.'.format(fn)) # log names log.debug('Package names: ' + ', '.join('{} -> {}'.format(k, v) for k, v in pkgs.items())) if check: missing = [] installed = info_installed_packages() for name, version in pkgs: if name not in installed or not installed[name].eq_version(version): missing.append((name, version)) else: missing = pkgs.keys() log.debug('Installing packages: {}'.format(missing)) if not missing: return Unchanged('Packages {!r} already installed'.format(pkgs.keys())) # FIXME: see above info_installed_packages.invalidate_cache() with fs.remote_tmpdir() as rtmp: # upload packages to be installed pkg_files = [] for idx, key in enumerate(missing): tmpdest = remote.path.join(rtmp, str(idx) + '.deb') fs.upload_file(pkgs[key], tmpdest) pkg_files.append(tmpdest) # install in a single dpkg install line # FIXME: add debconf default and such (same as apt) args = [config['cmd_dpkg'], '-i'] args.extend(pkg_files) proc.run( args, extra_env={ 'DEBIAN_FRONTEND': 'noninteractive', }) return Changed(msg='Installed packages {!r}'.format(missing))
def start_unit(unit_name): state = get_unit_state(unit_name) if 'ActiveState' in state and state['ActiveState'] == 'active' and state[ 'SubState'] == 'running': return Unchanged(msg='{} already running'.format(unit_name)) proc.run([config['cmd_systemctl'], 'start', unit_name]) return Changed(msg='Started {}'.format(unit_name))
def dpkg_install(paths, check=True): pkgs = paths if not hasattr(paths, 'keys'): pkgs = {} # determine package names from filenames. ideally, we would open the # package here and check for p in paths: fn = os.path.basename(p) try: name, version, tail = fn.split('_', 3) pkgs[(name, version)] = p except ValueError: raise ValueError( 'Could not determine package version from ' 'package filename {}. Please rename the .deb ' 'to standard debian convention ' '(name_version_arch.deb) or supply a specific ' 'version by passing a dictionary parameter.'.format(fn)) # log names log.debug('Package names: ' + ', '.join('{} -> {}'.format(k, v) for k, v in pkgs.items())) if check: missing = [] installed = info_installed_packages() for name, version in pkgs: if name not in installed or not installed[name].eq_version( version): missing.append((name, version)) else: missing = pkgs.keys() log.debug('Installing packages: {}'.format(missing)) if not missing: return Unchanged('Packages {!r} already installed'.format(pkgs.keys())) # FIXME: see above info_installed_packages.invalidate_cache() with fs.remote_tmpdir() as rtmp: # upload packages to be installed pkg_files = [] for idx, key in enumerate(missing): tmpdest = remote.path.join(rtmp, str(idx) + '.deb') fs.upload_file(pkgs[key], tmpdest) pkg_files.append(tmpdest) # install in a single dpkg install line # FIXME: add debconf default and such (same as apt) args = [config['cmd_dpkg'], '-i'] args.extend(pkg_files) proc.run(args, extra_env={'DEBIAN_FRONTEND': 'noninteractive', }) return Changed(msg='Installed packages {!r}'.format(missing))
def install_requirements(self, requirements_txt, upgrade=False): # FIXME: collect changes, via freeze? args = [self.pip, 'install', '-r', requirements_txt] if upgrade: args.append('-U') proc.run(args) return Changed(msg='Installed requirements from {} into {}'.format( requirements_txt, self.remote_path))
def enable_unit(unit_name, check_first=False): if check_first: state = get_unit_state(unit_name) # we use 'WantedBy' as a guess whether or not the service is enabled # when UnitFileState is not available (SysV init or older systemd) ufs = state.get('UnitFileState') if ufs == 'enabled' or ufs is None and 'WantedBy' in state: return Unchanged(msg='{} already enabled'.format(unit_name)) proc.run([config['cmd_systemctl'], 'enable', unit_name]) return Changed(msg='Enabled {}'.format(unit_name))
def reboot(): if config.get_bool('systemd'): try: proc.run([config['cmd_systemctl'], 'reboot']) except RemoteFailureError: # FIXME: should be more discerning; also verify reboot is taking # place pass # ignored, as the command will not finish - due to rebooting elif config['remote_os'] in ('unix', 'posix'): proc.run([config['cmd_shutdown'], '-r', 'now']) return Changed(msg='Server rebooting')
def dpkg_add_architecture(arch): archs = [info_dpkg_architecture()] + info_dpkg_foreign_architectures() if arch in archs: return Unchanged(msg='Architecture already enabled: {}'.format(arch)) proc.run([config['cmd_dpkg'], '--add-architecture', arch]) # invalidate caches info_dpkg_foreign_architectures.invalidate_cache() info_update_timestamp().mark_stale() return Changed(msg='New architecture added: {}'.format(arch))
def expand_root_fs(): dev_size, _, _ = proc.run(['fdisk', '-s', '/dev/mmcblk0']) p1_size, _, _ = proc.run(['fdisk', '-s', '/dev/mmcblk0p1']) p2_size, _, _ = proc.run(['fdisk', '-s', '/dev/mmcblk0p2']) free_space = (int(dev_size) - int(p1_size) - int(p2_size)) * 512 if free_space <= 4 * 1024 * 1024: return Unchanged( msg='Free space is <= 4M. Not expanding root filesystem') else: # FIXME: run fdisk and resize2fs instead of raspi-config? proc.run(['raspi-config', '--expand-rootfs']) return Changed(msg='Expanded root filesystem')
def install(self, pkgs, upgrade=True, editable=False): # FIXME: collect changes args = [self.pip, 'install'] if editable: args.append('-e') if upgrade: args.append('-U') args.extend(pkgs) proc.run(args) return Changed(msg='Installed packages {} into virtualenv {}'.format( pkgs, self.remote_path))
def info_openssh_version(): stdout, stderr, rval = proc.run(['sshd', '-?'], status_ok='any') if rval == 1: m = OPENSSH_VERSION_RE.match(stderr.splitlines()[1]) if m: return (int(m.group(1)), int(m.group(2)), m.group(3))
def install(self, pkgs, upgrade=True, reinstall=False, editable=False): # FIXME: collect changes args = [self.pip, 'install'] if editable: args.append('-e') if upgrade: args.append('-U') if reinstall: args.append('-I') args.extend(pkgs) proc.run(args) return Changed(msg='Installed packages {} into virtualenv {}'.format( pkgs, self.remote_path))
def chown(remote_path, uid=None, gid=None, recursive=False): new_owner = ':' # no-op if uid is None and gid is None: return if uid is not None: new_owner = str(uid) + new_owner if gid is not None: new_owner += str(gid) cmd = [config['cmd_chown']] if recursive: cmd.append('-R') cmd.append('-c') # FIXME: on BSDs, we need -v here? cmd.append(new_owner) cmd.append(remote_path) stdout, _, _ = proc.run(cmd) if stdout.strip(): return Changed(msg='Changed ownership of {} to {}' .format(remote_path, new_owner)) return Unchanged(msg='Ownership of {} already {}' .format(remote_path, new_owner))
def chown(remote_path, uid=None, gid=None, recursive=False): new_owner = ':' # no-op if uid is None and gid is None: return if uid is not None: new_owner = str(uid) + new_owner if gid is not None: new_owner += str(gid) cmd = [config['cmd_chown']] if recursive: cmd.append('-R') cmd.append('-c') # FIXME: on BSDs, we need -v here? cmd.append(new_owner) cmd.append(remote_path) stdout, _, _ = proc.run(cmd) if stdout.strip(): return Changed(msg='Changed ownership of {} to {}'.format( remote_path, new_owner)) return Unchanged(msg='Ownership of {} already {}'.format( remote_path, new_owner))
def install_strict_ssh(allow_users=['root'], allow_groups=None, address_family="any", permit_root=True, modern_ciphers=True, sftp_enabled=True, agent_forwarding=False, x11=False, tcp_forwarding=True, unix_forwarding=True, tunnel=False, port=22, use_dns=False, print_motd=False, auto_restart=True, check_sshd_config=True, password_enabled=None): # FIXME: change default in jinja templates to strict reporting of missing # values to avoid creating broken ssh configs # FIXME: add (possibly generic) support for atomic-tested-configuration # swaps (i.e. run sshd -t on a config) tpl = ssh_preset.templates.render( 'sshd_config', allow_users=allow_users, allow_groups=allow_groups, address_family=address_family, permit_root=permit_root, modern_ciphers=modern_ciphers, sftp_enabled=sftp_enabled, agent_forwarding=agent_forwarding, x11=x11, tcp_forwarding=tcp_forwarding, unix_forwarding=unix_forwarding, tunnel=tunnel, ports=port if isinstance(port, list) else [port], print_motd=print_motd, password_enabled=password_enabled) if fs.upload_string(tpl, '/etc/ssh/sshd_config').changed: if check_sshd_config: proc.run(['sshd', '-t']) # FIXME: we may want to abstract the init-system here if auto_restart: systemd.restart_unit('ssh.service') return Changed(msg='Changed sshd configuration') return Unchanged(msg='sshd config already strict')
def _get_lsb_info(): stdout, stderr = proc.run([config['cmd_lsb_release'], '--all', '--short']) lines = stdout.splitlines() return { 'dist_id': lines[0], 'desc': lines[1], 'release': lines[2], 'codename': lines[3], }
def install_strict_ssh(allow_users=['root'], allow_groups=None, address_family="any", permit_root=True, modern_ciphers=True, sftp_enabled=True, agent_forwarding=False, x11=False, tcp_forwarding=True, unix_forwarding=True, tunnel=False, port=22, use_dns=False, print_motd=False, auto_restart=True, check_sshd_config=True): # FIXME: change default in jinja templates to strict reporting of missing # values to avoid creating broken ssh configs # FIXME: add (possibly generic) support for atomic-tested-configuration # swaps (i.e. run sshd -t on a config) tpl = ssh_preset.templates.render('sshd_config', allow_users=allow_users, allow_groups=allow_groups, address_family=address_family, permit_root=permit_root, modern_ciphers=modern_ciphers, sftp_enabled=sftp_enabled, agent_forwarding=agent_forwarding, x11=x11, tcp_forwarding=tcp_forwarding, unix_forwarding=unix_forwarding, tunnel=tunnel, port=port, print_motd=print_motd) if fs.upload_string(tpl, '/etc/ssh/sshd_config').changed: if check_sshd_config: proc.run(['sshd', '-t']) # FIXME: we may want to abstract the init-system here if auto_restart: systemd.restart_unit('ssh.service') return Changed(msg='Changed sshd configuration') return Unchanged(msg='sshd config already strict')
def update(max_age=3600): if max_age < 0: return Unchanged(msg='apt update disabled (max_age < 0).') ts = info_update_timestamp() if max_age: age = ts.get_age() if age < max_age: return Unchanged( msg='apt cache is only {:.0f} minutes old, not updating' .format(age / 60)) proc.run([config['cmd_apt_get'], 'update']) # modify update stamp ts.mark_current() return Changed(msg='apt cache updated')
def info_installed_packages(): stdout, _, _ = proc.run([config['cmd_dpkg_query'], '--show']) pkgs = {} for line in stdout.splitlines(): rec = PackageRecord(*line.split('\t')) pkgs[rec.name] = rec return pkgs
def enable_module(module_name, load=True, modules_file="/etc/modules"): mods = info["linux.modules"] c = False # load module if not loaded if load and module_name not in mods: proc.run(["modprobe", module_name]) c = True # ensure module is found in modules_files with fs.edit(modules_file) as mf: mf.insert_line(module_name) c |= mf.changed if c: return Changed(msg="Kernel module {}, enabled in {}".format(module_name, modules_file)) return Unchanged(msg="Kernel module {} already enabled in {}".format(module_name, modules_file))
def regenerate_host_keys(mark='/etc/ssh/host_keys_regenerated'): if mark: if remote.lstat(mark): return Unchanged(msg='Hostkeys have already been regenerated') key_names = [ '/etc/ssh/ssh_host_ecdsa_key', '/etc/ssh/ssh_host_ed25519_key', '/etc/ssh/ssh_host_rsa_key', '/etc/ssh/ssh_host_dsa_key', ] def collect_fingerprints(): fps = '' for key in key_names: if remote.lstat(key): fps += proc.run(['ssh-keygen', '-l', '-f', key])[0] return fps old_fps = collect_fingerprints() # remove old keys for key in key_names: fs.remove_file(key) fs.remove_file(key + '.pub') # generate new ones proc.run(['dpkg-reconfigure', 'openssh-server']) # restart openssh systemd.restart_unit('ssh.service') new_fps = collect_fingerprints() # mark host keys as new fs.touch(mark) return Changed( msg='Regenerated SSH host keys.\n' 'Old fingerprints:\n{}\nNew fingerprints:\n{}\n'.format( util.indent(' ', old_fps), util.indent(' ', new_fps)))
def create(self, python='python3', global_site_packages=False): # FIXME: check if python version and global_site_packages are correct, # and correct / recreat (--clear?) otherwise if not remote.stat(self.python): args = [config['cmd_venv'], '-p', python, ] if global_site_packages: args.append('--system-site-packages') else: args.append('--no-site-packages') args.append(self.remote_path) proc.run(args) return Changed( msg='Initialized virtualenv in {}'.format(self.remote_path)) return Unchanged(msg='virtualenv at {} already initialized'.format( self.remote_path))
def useradd(name, groups=[], user_group=True, comment=None, home=None, create_home=None, system=False, shell=None): cmd = [config['cmd_useradd']] gs = groups[:] if gs: if not user_group: cmd.extend(('-g', gs.pop(0))) for g in gs: cmd.extend(('-G', g)) if user_group is True: cmd.append('-U') elif user_group is False: cmd.append('-N') if comment is not None: cmd.extend(('-c', comment)) if home is not None: cmd.extend(('-d', home)) if create_home is False: cmd.append('-M') elif create_home is True: cmd.append('-m') if shell: cmd.extend(('-s', shell)) if system: cmd.append('-r') cmd.append(name) stdout, stderr, returncode = proc.run(cmd, status_ok=(0, 9), status_meaning=_USERADD_STATUS_CODES) if returncode == 9: # FIXME: should check if user is up-to-date (home, etc) return Unchanged(msg='User {} already exists'.format(name)) info_users.invalidate_cache() return Changed(msg='Created user {}'.format(name))
def useradd(name, groups=[], user_group=True, comment=None, home=None, create_home=None, system=False, shell=None): cmd = [config['cmd_useradd']] gs = groups[:] if gs: if not user_group: cmd.extend(('-g', gs.pop(0))) if gs: cmd.extend(('-G', ','.join(groups))) if user_group is True: cmd.append('-U') elif user_group is False: cmd.append('-N') if comment is not None: cmd.extend(('-c', comment)) if home is not None: cmd.extend(('-d', home)) if create_home is False: cmd.append('-M') elif create_home is True: cmd.append('-m') if shell: cmd.extend(('-s', shell)) if system: cmd.append('-r') cmd.append(name) stdout, stderr, returncode = proc.run(cmd, status_ok=(0, 9), status_meaning=_USERADD_STATUS_CODES) if returncode == 9: # FIXME: should check if user is up-to-date (home, etc) return Unchanged(msg='User {} already exists'.format(name)) info_users.invalidate_cache() return Changed(msg='Created user {}'.format(name))
def enable_module(module_name, load=True, modules_file='/etc/modules'): mods = info['linux.modules'] c = False # load module if not loaded if load and module_name not in mods: proc.run(['modprobe', module_name]) c = True # ensure module is found in modules_files with fs.edit(modules_file) as mf: mf.insert_line(module_name) c |= mf.changed if c: return Changed(msg='Kernel module {}, enabled in {}'.format( module_name, modules_file)) return Unchanged(msg='Kernel module {} already enabled in {}'.format( module_name, modules_file))
def add_apt_keys(key_filename, fingerprints=None): def get_fingerprints(buf): PREFIX = 'Key fingerprint = ' fps = set() for line in buf.splitlines(): line = line.strip() if line.startswith(PREFIX): fps.add(line[len(PREFIX):].replace(' ', '')) return fps def id_from_fingerprint(fp): return fp[-8:] if fingerprints is None: # first, we need to list all keys in the keyfile. # FIXME: allow use of remote gpg output = subprocess.check_output( ['gpg', '--with-fingerprint'], stdin=open(key_filename, 'r')) # FIXME: is utf8 the right call here? fingerprints = get_fingerprints(output.decode('utf8')) # check if key fingerprints are missing local_fps = fingerprints remote_fps = get_fingerprints(proc.run(['apt-key', 'fingerprints'])[0]) missing_fps = local_fps.difference(remote_fps) if missing_fps: with open(key_filename, 'r') as k: proc.run(['apt-key', 'add', '-'], input=k) return Changed( msg='Added missing apt keys: {}'.format(', '.join( id_from_fingerprint(fp) for fp in sorted(missing_fps)))) return Unchanged( msg='Apt keys {} already installed'.format(', '.join( id_from_fingerprint(fp) for fp in sorted(local_fps))))
def upgrade(max_age=3600, force=False, dist_upgrade=False): # FIXME: should allow upgrading selected packages update(max_age) args = [config['cmd_apt_get']] # FIXME: check for upgrades first and output proper changed status args.extend([ 'upgrade' if not dist_upgrade else 'dist-upgrade', '--quiet', '--yes', # FIXME: options below don't work. why? # '--option', 'Dpkg::Options::="--force-confdef"', # '--option', 'Dpkg::Options::="--force-confold"' ]) if force: args.append('--force-yes') proc.run(args, extra_env={'DEBIAN_FRONTEND': 'noninteractive', }) info_installed_packages.invalidate_cache() return Changed(msg='Upgraded all packages')
def set_hostname(hostname, domain=None, config_only=False): prev_hostname = info_hostname() changed = False changed |= fs.upload_string('{}\n'.format(hostname), '/etc/hostname').changed if not config_only and prev_hostname != hostname: proc.run(['hostname', hostname]) changed = True # update /etc/hosts # this will also set the domain name # see http://jblevins.org/log/hostname # # we adopt the following convention: host_line = '127.0.1.1\t' + hostname if domain: host_line = '127.0.1.1\t{}.{}\t{}'.format(hostname, domain, hostname) with fs.edit('/etc/hosts') as hosts: if host_line not in hosts.lines(): hosts.comment_out(r'^127.0.1.1') # comment out old lines lines = [host_line] + hosts.lines() hosts.set_lines(lines) changed |= hosts.changed if changed: info_hostname.invalidate_cache() info_fqdn.invalidate_cache() return Changed(msg='Hostname changed from {} to {}'.format( prev_hostname, hostname)) return Unchanged(msg='Hostname already set to {}'.format(hostname))
def add_apt_keys(key_filename, fingerprints=None): def get_fingerprints(buf): PREFIX = 'Key fingerprint = ' fps = set() for line in buf.splitlines(): line = line.strip() if line.startswith(PREFIX): fps.add(line[len(PREFIX):].replace(' ', '')) return fps def id_from_fingerprint(fp): return fp[-8:] if fingerprints is None: # first, we need to list all keys in the keyfile. # FIXME: allow use of remote gpg output = subprocess.check_output( ['gpg', '--with-fingerprint'], stdin=open(key_filename, 'r')) # FIXME: is utf8 the right call here? fingerprints = get_fingerprints(output.decode('utf8')) # check if key fingerprints are missing local_fps = fingerprints remote_fps = get_fingerprints(proc.run(['apt-key', 'fingerprints'])[0]) missing_fps = local_fps.difference(remote_fps) if missing_fps: with open(key_filename, 'r') as k: proc.run(['apt-key', 'add', '-'], input=k) return Changed(msg='Added missing apt keys: {}'.format(', '.join( id_from_fingerprint(fp) for fp in sorted(missing_fps)))) return Unchanged(msg='Apt keys {} already installed'.format(', '.join( id_from_fingerprint(fp) for fp in sorted(local_fps))))
def remove_packages(pkgs, check_first=True, purge=False, max_age=3600): if check_first and not set(pkgs).intersection(set(info_installed_packages( ).keys())): return Unchanged(msg='Not installed: {}'.format(' '.join(pkgs))) update(max_age) args = [config['cmd_apt_get']] args.extend([ 'remove' if not purge else 'purge', '--quiet', '--yes', # FIXME: options below don't work. why? # '--option', 'Dpkg::Options::="--force-confdef"', # '--option', 'Dpkg::Options::="--force-confold"' ]) args.extend(pkgs) proc.run(args, extra_env={'DEBIAN_FRONTEND': 'noninteractive', }) info_installed_packages.invalidate_cache() return Changed(msg='{} {}'.format('Removed' if not purge else 'Purged', ' '.join(pkgs)))
def install_packages(pkgs, check_first=True, release=None, max_age=3600, force=False): # check if packages are already installed, ignoring version and arch if check_first and set(pkgs) < set( k.rsplit(':', 1)[0] for k in info_installed_packages().keys()): return Unchanged(msg='Already installed: {}'.format(' '.join(pkgs))) update(max_age) args = [config['cmd_apt_get']] if release: args.extend(['-t', release]) args.extend([ 'install', '--quiet', '--yes', # FIXME: options below don't work. why? # '--option', 'Dpkg::Options::="--force-confdef"', # '--option', 'Dpkg::Options::="--force-confold"' ]) if force: args.append('--force-yes') args.extend(pkgs) proc.run( args, extra_env={ 'DEBIAN_FRONTEND': 'noninteractive', }) # FIXME: make this a decorator for info, add "change_invalides" decorator? info_installed_packages.invalidate_cache() # FIXME: detect if packages were installed? return Changed(msg='Installed {}'.format(' '.join(pkgs)))
def auto_remove(max_age=3600): update(max_age) # FIXME: make max_age become a config setting, add a # with_config context manager args = [config['cmd_apt_get']] args.extend(['autoremove', '--quiet', '--yes', ]) stdout, _, _ = proc.run(args, extra_env={'DEBIAN_FRONTEND': 'noninteractive', }) if '0 to remove' in stdout: return Unchanged(msg='No packages auto-removed') info_installed_packages.invalidate_cache() return Changed(msg='Some packages were auto-removed')
def info_system(): FLAG_LIST = { 'machine': '-m', 'nodename': '-n', 'kernel_name': '-s', 'kernel_release': '-r', 'kernel_version': '-v', 'processor': '-p', } flag_values = {} for flag_name, flag in FLAG_LIST.items(): out, _, _ = proc.run([config['cmd_uname'], flag]) flag_values[flag_name] = out.rstrip() return flag_values
def query_cache(pkgs): stdout, _, _ = proc.run([config['cmd_apt_cache'], 'show'] + list(pkgs)) pkgs = OrderedDict() for dump in stdout.split('\n\n'): # skip empty lines if not dump or dump.isspace(): continue try: pkg_info = Deb822(dump) except ValueError: log.debug(dump) raise RemoteFailureError('Error parsing Deb822 info.') pkgs[pkg_info['Package']] = pkg_info return Unchanged(pkgs)
def userdel(name, remove_home=False, force=False): cmd = [config['cmd_userdel']] if remove_home: cmd.append('-r') if force: cmd.append('-f') cmd.append(name) stdout, stderr, returncode = proc.run(cmd, status_ok=(0, 6), status_meaning=_USERDEL_STATUS_CODES) if returncode == 6: return Unchanged(msg='User {} does not exist'.format(name)) info_users.invalidate_cache() return Changed(msg='Removed user {}'.format(name))
def info_timedatestatus(): stdout, _, _ = proc.run(['timedatectl', 'status']) vals = {} for line in stdout.splitlines(): line = line.strip() if not line: continue k, v = line.strip().split(':', 1) k = k.strip() v = v.strip() if v == 'yes': v = True if v == 'no': v = False vals[k] = v return vals
def auto_remove(max_age=3600): update(max_age) # FIXME: make max_age become a config setting, add a # with_config context manager args = [config['cmd_apt_get']] args.extend([ 'autoremove', '--quiet', '--yes', ]) stdout, _, _ = proc.run( args, extra_env={ 'DEBIAN_FRONTEND': 'noninteractive', }) if '0 to remove' in stdout: return Unchanged(msg='No packages auto-removed') info_installed_packages.invalidate_cache() return Changed(msg='Some packages were auto-removed')
def remote_tmpdir(delete=True, randbytes=16, mode=0o700): # FIXME: audit this for security issues if config['cmd_mktemp']: # create directory using mktemp command tmpdir, _, _ = proc.run([config['cmd_mktemp'], '-d']) tmpdir = tmpdir.rstrip('\n') else: # emulate mktemp tmpdir = remote.path.join(config['fs_fallback_tmpdir'], 'remand-' + hexlify(os.urandom(randbytes))) remote.mkdir(tmpdir, mode=mode) log.debug('Created temporary directory {}'.format(tmpdir)) try: yield tmpdir finally: if delete: log.debug('Removing temporary directory {}'.format(tmpdir)) remove_dir(tmpdir)