def start_qvarn_with_vars(name): qvarn_vars = get_qvarn(name) config = { 'log': [ { 'filename': qvarn_vars['api.log'], }, ], 'baseurl': qvarn_vars['url'], 'token-issuer': qvarn_vars['issuer'], 'token-audience': qvarn_vars['audience'], 'token-public-key': qvarn_vars['pubkey'], 'resource-type-dir': os.path.join(srcdir, 'resource_type'), 'enable-fine-grained-access-control': qvarn_vars['fine-grained'], } config_filename = os.path.join(datadir, 'qvarn-{}.yaml'.format(name)) write(config_filename, yaml.safe_dump(config)) env = dict(os.environ) env['QVARN_CONFIG'] = config_filename argv = [ 'gunicorn3', '--daemon', '--bind', '127.0.0.1:{}'.format(qvarn_vars['port']), '-p', qvarn_vars['pid-file'], 'qvarn.backend:app', ] cliapp.runcmd(argv, env=env, stdout=None, stderr=None) wait_for_file(qvarn_vars['pid-file'], 2.0)
def install_bootloader_extlinux(self, real_root): self.status(msg='Installing extlinux') cliapp.runcmd(['extlinux', '--install', real_root]) # FIXME this hack seems to be necessary to let extlinux finish cliapp.runcmd(['sync']) time.sleep(2)
def create_run(self, version_root): '''Create the 'run' snapshot.''' self.status(msg='Creating run subvolume') orig = os.path.join(version_root, 'orig') run = os.path.join(version_root, 'run') cliapp.runcmd(['btrfs', 'subvolume', 'snapshot', orig, run])
def mkfs_btrfs(self, location): '''Create a btrfs filesystem on the disk.''' self.status(msg='Creating btrfs filesystem') try: # The following command disables some new filesystem features. We # need to do this because at the time of writing, SYSLINUX has not # been updated to understand these new features and will fail to # boot if the kernel is on a filesystem where they are enabled. cliapp.runcmd( ['mkfs.btrfs','-f', '-L', 'baserock', '--features', '^extref', '--features', '^skinny-metadata', '--features', '^mixed-bg', '--nodesize', '4096', location]) except cliapp.AppException as e: if 'unrecognized option \'--features\'' in e.msg: # Old versions of mkfs.btrfs (including v0.20, present in many # Baserock releases) don't support the --features option, but # also don't enable the new features by default. So we can # still create a bootable system in this situation. logging.debug( 'Assuming mkfs.btrfs failure was because the tool is too ' 'old to have --features flag.') cliapp.runcmd(['mkfs.btrfs','-f', '-L', 'baserock', location]) else: raise
def format_txt(self, program): env = dict(os.environ) env['MANWIDTH'] = '80' with open('%s.1.txt' % program, 'w') as f: cliapp.runcmd(['man', '-l', '%s.1' % program], ['col', '-b'], stdout=f, env=env)
def generate_troff(self, program, lang): with open('%s.1%s' % (program, lang), 'w') as f: cliapp.runcmd( ['python', program, '--generate-manpage=%s.1%s.in' % (program, lang), '--output=%s.1' % program], stdout=f)
def create_run(self, version_root): '''Create the 'run' snapshot.''' self.status(msg='Creating run subvolume') orig = os.path.join(version_root, 'orig') run = os.path.join(version_root, 'run') cliapp.runcmd( ['btrfs', 'subvolume', 'snapshot', orig, run])
def run_obnam(self, args): env = dict(os.environ) env['OBNAM_PROFILE'] = self.profile_name opts = ['--no-default-config', '--config', self._config] cliapp.runcmd( ['./obnam'] + opts + args, env=env, cwd=self._srcdir)
def generate_troff(self, program, lang): with open('%s.1%s' % (program, lang), 'w') as f: cliapp.runcmd([ 'python', program, '--generate-manpage=%s.1%s.in' % (program, lang), '--output=%s.1' % program ], stdout=f)
def create_orig(self, version_root, temp_root): '''Create the default "factory" system.''' orig = os.path.join(version_root, 'orig') self.status(msg='Creating orig subvolume') cliapp.runcmd(['btrfs', 'subvolume', 'create', orig]) self.status(msg='Copying files to orig subvolume') cliapp.runcmd(['cp', '-a', temp_root + '/.', orig + '/.'])
def install_initramfs(self, initramfs_path, version_root): '''Install the initramfs outside of 'orig' or 'run' subvolumes. This is required because syslinux doesn't traverse subvolumes when loading the kernel or initramfs. ''' self.status(msg='Installing initramfs') initramfs_dest = os.path.join(version_root, 'initramfs') cliapp.runcmd(['cp', '-a', initramfs_path, initramfs_dest])
def _run_lorry(self, lorry): with tempfile.NamedTemporaryFile() as f: logging.debug(json.dumps(lorry)) json.dump(lorry, f) f.flush() cliapp.runcmd([ 'lorry', '--working-area', self.app.settings['lorry-working-dir'], '--pull-only', '--bundle', 'never', '--tarball', 'never', f.name])
def format_txt(self, program): env = dict(os.environ) env['MANWIDTH'] = '80' with open('%s.1.txt' % program, 'w') as f: cliapp.runcmd( ['man', '-l', '%s.1' % program], ['col', '-b'], stdout=f, env=env)
def run_rsync(self, sources, target): if isinstance(sources, str): sources = [sources] settings = [ '--bwlimit=%s' % config.bandwidth_limit_kbytes_sec, '--partial', '--progress', ] cliapp.runcmd( ['rsync'] + settings + sources + [target], stdout=sys.stdout)
def _run_lorry(self, lorry): with tempfile.NamedTemporaryFile() as f: logging.debug(json.dumps(lorry)) json.dump(lorry, f) f.flush() cliapp.runcmd([ 'lorry', '--working-area', self.app.settings['lorry-working-dir'], '--pull-only', '--bundle', 'never', '--tarball', 'never', f.name ])
def install_kernel(self, version_root, temp_root): '''Install the kernel outside of 'orig' or 'run' subvolumes''' self.status(msg='Installing kernel') image_names = ['vmlinuz', 'zImage', 'uImage'] kernel_dest = os.path.join(version_root, 'kernel') for name in image_names: try_path = os.path.join(temp_root, 'boot', name) if os.path.exists(try_path): cliapp.runcmd(['cp', '-a', try_path, kernel_dest]) break
def mount(self, location): self.status(msg='Mounting filesystem') try: mount_point = tempfile.mkdtemp() if self.is_device(location): cliapp.runcmd(['mount', location, mount_point]) else: cliapp.runcmd(['mount', '-o', 'loop', location, mount_point]) except BaseException, e: sys.stderr.write('Error mounting filesystem') os.rmdir(mount_point) raise
def check_with_pep8(self): output = cliapp.runcmd(['pep8', '--version']) parts = output.strip().split('.') # pep8 version 1.5.7 is in Debian jessie. Previous versions # give bad warnings about whitespace around some operators, # and later versions don't. So we only run pep8 if it's new # enough. if parts >= ['1', '5', '7']: print 'running pep8' cliapp.runcmd(['pep8', 'obnamlib'], stdout=None, stderr=None) else: print 'not running pep8'
def install_dtb(self, version_root, temp_root): '''Install the device tree outside of 'orig' or 'run' subvolumes''' self.status(msg='Installing devicetree') device_tree_path = self.get_dtb_path() dtb_dest = os.path.join(version_root, 'dtb') try_path = os.path.join(temp_root, device_tree_path) if os.path.exists(try_path): cliapp.runcmd(['cp', '-a', try_path, dtb_dest]) else: logging.error("Failed to find device tree %s", device_tree_path) raise cliapp.AppException( 'Failed to find device tree %s' % device_tree_path)
def install_dtb(self, version_root, temp_root): '''Install the device tree outside of 'orig' or 'run' subvolumes''' self.status(msg='Installing devicetree') device_tree_path = self.get_dtb_path() dtb_dest = os.path.join(version_root, 'dtb') try_path = os.path.join(temp_root, device_tree_path) if os.path.exists(try_path): cliapp.runcmd(['cp', '-a', try_path, dtb_dest]) else: logging.error("Failed to find device tree %s", device_tree_path) raise cliapp.AppException('Failed to find device tree %s' % device_tree_path)
def check_with_pylint(self): output = cliapp.runcmd(['pylint', '--version']) parts = output.splitlines()[0].split('.') # pylint version 1.3.1 is in Debian jessie. Previous versions # do not know all the things in the pylint.conf file we use, # so we only run pylint if it's new enough. if parts >= ['pylint 1', '3', '1,']: print 'running pylint' cliapp.runcmd( ['pylint', '--rcfile=pylint.conf', 'obnamlib'], stdout=None, stderr=None) else: print 'not running pylint', parts
def check_with_pylint(self): output = cliapp.runcmd(['pylint', '--version']) parts = output.splitlines()[0].split('.') # pylint version 1.3.1 is in Debian jessie. Previous versions # do not know all the things in the pylint.conf file we use, # so we only run pylint if it's new enough. if parts >= ['pylint 1', '3', '1,']: print 'running pylint' cliapp.runcmd(['pylint', '--rcfile=pylint.conf', 'obnamlib'], stdout=None, stderr=None) else: print 'not running pylint', parts
def delete_access(qvarn_vars, min_seconds): argv = [ srcpath('qvarn-access'), '--token', qvarn_vars['token'], '--api', qvarn_vars['url'], '--log', 'access_delete.log', '--delete', '--min-seconds', str(min_seconds), ] cliapp.runcmd(argv)
def dump_qvarn(qvarn_name, filename, names): qvarn_vars = get_qvarn(qvarn_name) token = new_token_for_user(qvarn_name, '', V['scopes']) argv = [ srcpath('qvarn-dump'), '--token', token, '--api', qvarn_vars['url'], '--output', filename, '--log', filename + '.log', ] + names cliapp.runcmd(argv)
def run(command, **kwargs): def callback(data): for line in data.decode().rstrip().split('\n'): logger.debug(line) _e = None kw = dict() kw['env'] = env.env kw['stdout_callback'] = callback kw['stderr_callback'] = callback kw.update(kwargs) if 'shell' in kwargs and kwargs['shell']: command = [command] else: command = shlex.split(command) try: return cliapp.runcmd(command, **kw) except KeyboardInterrupt as e: _e = e except cliapp.app.AppException as e: _e = Exception(e.msg.splitlines()[0]) if _e: raise _e
def find_all_source_files(self): exclude = [ r'\.gpg$', r'/random_seed$', r'\.gz$', r'\.xz$', r'\.yarn$', r'\.mdwn$', r'\.css$', r'\.conf$', r'^without-tests$', r'^test-plugins/.*\.py$', r'^debian/', r'^README\.', r'^NEWS$', r'^COPYING$', r'^CC-BY-SA-4\.0\.txt$', r'^\.gitignore$', r'^obnamlib/version\.py$', ] pats = [re.compile(x) for x in exclude] output = cliapp.runcmd(['git', 'ls-files']) result = [] for line in output.splitlines(): for pat in pats: if pat.search(line): break else: result.append(line) return result
def find_all_source_files(self): exclude = [ r'\.gpg$', r'/random_seed$', r'\.gz$', r'\.xz$', r'\.yarn$', r'\.mdwn$', r'\.css$', r'\.conf$', r'^without-tests$', r'^test-plugins/.*\.py$', r'^debian/', r'^README\.', r'^NEWS$', r'^COPYING$', r'^CC-BY-SA-4\.0\.txt$', r'^\.gitignore$', ] pats = [re.compile(x) for x in exclude] output = cliapp.runcmd(['git', 'ls-files']) result = [] for line in output.splitlines(): for pat in pats: if pat.search(line): break else: result.append(line) return result
def get_wanted_formats(self): if 'REPOSITORY_FORMAT' in os.environ: return [os.environ['REPOSITORY_FORMAT']] else: return cliapp.runcmd( ['./obnam', '--no-default-configs', 'list-formats']).splitlines()
def get_commit_timestamp(self): output = cliapp.runcmd( ['git', 'show', '--date=iso', 'HEAD'], cwd=self._srcdir) for line in output.splitlines(): if line.startswith('Date:'): return line[len('Date:'):].strip() raise Exception('commit has no Date:')
def runcmd(argv, *argvs, **kwargs): progress('Exec: %r' % (argv, )) kwargs['stdout_callback'] = _log_stdout kwargs['stderr_callback'] = _log_stderr env = kwargs.get('env', os.environ.copy()) env['LC_ALL'] = 'C' kwargs['env'] = env return cliapp.runcmd(argv, *argvs, **kwargs)
def customize(self, rootdir): script = self.settings['customize'] if not script: return if not os.path.exists(script): example = os.path.join("/usr/share/vmdebootstrap/examples/", script) if not os.path.exists(example): self.message("Unable to find %s" % script) return script = example self.message('Running customize script %s' % script) logging.info("rootdir=%s", rootdir) try: with open('/dev/tty', 'w') as tty: cliapp.runcmd([script, rootdir, self.settings['image']], stdout=tty, stderr=tty) except IOError: logging.debug('tty unavailable, trying in headless mode.') subprocess.call([script, rootdir, self.settings['image']])
def _runcmd(self, argv, **kwargs): '''Run a command at the root of the git directory. See cliapp.runcmd for arguments. Do NOT use this from outside the class. Add more public methods for specific git operations instead. ''' return cliapp.runcmd(argv, cwd=self.dirname, **kwargs)
def qvarn_copy(source_name, target_name, names): source = get_qvarn(source_name) assert source is not None target = get_qvarn(target_name) assert source is not None source_token = new_token_for_user(source_name, '', V['scopes'], force=True) target_token = new_token_for_user(target_name, '', V['scopes'], force=True) argv = [ srcpath('qvarn-copy'), '--source-token', source_token, '--target-token', target_token, '--source', source['url'], '--target', target['url'], '--log', 'copy.log', ] + names cliapp.runcmd(argv)
def create_token(qvarn_name, user_name, scopes): qvarn_vars = get_qvarn(qvarn_name) privkey = qvarn_vars['privkey'] filename = write_temp(privkey) argv = [ os.path.join(srcdir, 'create-token'), filename, qvarn_vars['issuer'], user_name, qvarn_vars['audience'], scopes, ] return cliapp.runcmd(argv)
def create_state_subvolume(self, system_dir, mountpoint, state_subdir): '''Create a shared state subvolume. We need to move any files added to the temporary rootfs by the configure extensions to their correct home. For example, they might have added keys in `/root/.ssh` which we now need to transfer to `/state/root/.ssh`. ''' self.status(msg='Creating %s subvolume' % state_subdir) subvolume = os.path.join(mountpoint, 'state', state_subdir) cliapp.runcmd(['btrfs', 'subvolume', 'create', subvolume]) os.chmod(subvolume, 0755) existing_state_dir = os.path.join(system_dir, state_subdir) files = [] if os.path.exists(existing_state_dir): files = os.listdir(existing_state_dir) if len(files) > 0: self.status(msg='Moving existing data to %s subvolume' % subvolume) for filename in files: filepath = os.path.join(existing_state_dir, filename) cliapp.runcmd(['mv', filepath, subvolume])
def create_deploy_workspace(self, path): '''Create or enter existing workspace for deploying release images.''' if not os.path.exists(path): status('Creating workspace %s' % path) cliapp.runcmd(['morph', 'init', path]) else: status('Reusing existing workspace %s' % path) repo = 'baserock:baserock/definitions' branch = 'master' with cwd(path): if not os.path.exists(branch): status('Checking out %s branch %s' % (repo, branch)) cliapp.runcmd(['morph', 'checkout', repo, branch]) else: status('Reusing checkout of %s %s' % (repo, branch)) definitions_dir = os.path.join( config.deploy_workspace, branch, 'baserock/baserock/definitions') return definitions_dir
def _fetch_or_update_source(self, lorry): assert len(lorry) == 1 lorry_name, lorry_entry = lorry.items()[0] url = lorry_entry['url'] reponame = '_'.join(lorry_name.split('/')) repopath = os.path.join(self.app.settings['lorry-working-dir'], reponame, 'git') checkoutpath = os.path.join(self.app.settings['checkouts-dir'], reponame) try: already_lorried = os.path.exists(repopath) if already_lorried: if self.app.settings['update-existing']: self.app.status('Updating lorry of %s', url) self._run_lorry(lorry) else: self.app.status('Lorrying %s', url) self._run_lorry(lorry) if os.path.exists(checkoutpath): repo = morphlib.gitdir.GitDirectory(checkoutpath) repo.update_remotes() else: if already_lorried: logging.warning( 'Expected %s to exist, but will recreate it', checkoutpath) cliapp.runcmd(['git', 'clone', repopath, checkoutpath]) repo = morphlib.gitdir.GitDirectory(checkoutpath) except cliapp.AppException as e: raise BaserockImportException(e.msg.rstrip()) return repo, url
def _fetch_or_update_source(self, lorry): assert len(lorry) == 1 lorry_name, lorry_entry = lorry.items()[0] url = lorry_entry['url'] reponame = '_'.join(lorry_name.split('/')) repopath = os.path.join( self.app.settings['lorry-working-dir'], reponame, 'git') checkoutpath = os.path.join( self.app.settings['checkouts-dir'], reponame) try: already_lorried = os.path.exists(repopath) if already_lorried: if self.app.settings['update-existing']: self.app.status('Updating lorry of %s', url) self._run_lorry(lorry) else: self.app.status('Lorrying %s', url) self._run_lorry(lorry) if os.path.exists(checkoutpath): repo = morphlib.gitdir.GitDirectory(checkoutpath) repo.update_remotes() else: if already_lorried: logging.warning( 'Expected %s to exist, but will recreate it', checkoutpath) cliapp.runcmd(['git', 'clone', repopath, checkoutpath]) repo = morphlib.gitdir.GitDirectory(checkoutpath) except cliapp.AppException as e: raise BaserockImportException(e.msg.rstrip()) return repo, url
def deploy_images(self, outputs): '''Use `morph deploy` to create the release images.''' # FIXME: once `morph deploy` supports partial deployment, this should # deploy only the images which aren't already deployed... it should # also check if they need redeploying based on the SHA1 they were # deployed from, perhaps. That's getting fancy! todo = [f for f in outputs.itervalues() if not os.path.exists(f)] if len(todo) == 0: status('Reusing existing release images') else: logging.debug('Need to deploy images: %s' % ', '.join(todo)) status('Creating release images from release.morph') version_label = 'baserock-%s' % config.release_number morph_config = ['--trove-host=%s' % config.build_trove] deploy_config = ['release.VERSION_LABEL=%s' % version_label] cliapp.runcmd( ['morph', 'deploy', 'release.morph'] + morph_config + deploy_config, stdout=sys.stdout)
def start_qvarn(name, audience='audience', fine_grained=False): privkey, pubkey = create_token_signing_key_pair() port = cliapp.runcmd([os.path.join(srcdir, 'randport')]).strip() qvarn_vars = { 'issuer': name, 'audience': audience, 'api.log': 'qvarn-{}.log'.format(name), 'gunicorn3.log': 'gunicorn3-{}.log'.format(name), 'pid-file': '{}.pid'.format(name), 'port': port, 'url': 'http://127.0.0.1:{}'.format(port), 'privkey': privkey, 'pubkey': pubkey, 'fine-grained': fine_grained, } save_qvarn(name, qvarn_vars) start_qvarn_with_vars(name)
def get_artifact_list(self, system_morphs): '''Return list of artifacts involved in the release. List is also written to a file. Note that this function requires the `list-artifacts` command from Morph of Baserock 14.23 or later. ''' artifact_list_file = os.path.join( config.artifacts_dir, 'baserock-%s-artifacts.txt' % config.release_number) if os.path.exists(artifact_list_file): with open(artifact_list_file) as f: artifact_basenames = [line.strip() for line in f] else: text = cliapp.runcmd( ['morph', '--quiet', '--trove-host=%s' % config.build_trove, 'list-artifacts', 'baserock:baserock/definitions', 'master'] + system_morphs) artifact_basenames = text.strip().split('\n') with morphlib.savefile.SaveFile(artifact_list_file, 'w') as f: f.write(text) return artifact_list_file, artifact_basenames
def test_runcmd_pipes_stdin_through_command_with_lots_of_data(self): data = 'x' * (1024**2) self.assertEqual(cliapp.runcmd(['cat'], feed_stdin=data), data)
def test_runcmd_pipes_stdin_through_two_commands(self): self.assertEqual(cliapp.runcmd( ['cat'], ['cat'], feed_stdin='hello, world'), 'hello, world')
def test_runcmd_returns_stdout_of_command(self): self.assertEqual(cliapp.runcmd(['echo', 'hello', 'world']), 'hello world\n')
def test_runcmd_executes_true(self): self.assertEqual(cliapp.runcmd(['true']), '')
def test_runcmd_ignores_failures_on_request(self): self.assertEqual(cliapp.runcmd(['false'], ignore_fail=True), '')
self.status(msg='Mounting filesystem') try: mount_point = tempfile.mkdtemp() if self.is_device(location): cliapp.runcmd(['mount', location, mount_point]) else: cliapp.runcmd(['mount', '-o', 'loop', location, mount_point]) except BaseException, e: sys.stderr.write('Error mounting filesystem') os.rmdir(mount_point) raise try: yield mount_point finally: self.status(msg='Unmounting filesystem') cliapp.runcmd(['umount', mount_point]) os.rmdir(mount_point) def create_btrfs_system_layout(self, temp_root, mountpoint, version_label, disk_uuid): '''Separate base OS versions from state using subvolumes. ''' initramfs = self.find_initramfs(temp_root) version_root = os.path.join(mountpoint, 'systems', version_label) state_root = os.path.join(mountpoint, 'state') os.makedirs(version_root) os.makedirs(state_root) self.create_orig(version_root, temp_root)
def mkfs_btrfs(self, location): '''Create a btrfs filesystem on the disk.''' self.status(msg='Creating btrfs filesystem') cliapp.runcmd(['mkfs.btrfs', '-f', '-L', 'baserock', location])
def get_uuid(self, location): '''Get the UUID of a block device's file system.''' # Requires util-linux blkid; busybox one ignores options and # lies by exiting successfully. return cliapp.runcmd(['blkid', '-s', 'UUID', '-o', 'value', location]).strip()
def test_runcmd_obeys_cwd(self): self.assertEqual(cliapp.runcmd(['pwd'], cwd='/'), '/\n')
def check_copyright_statements(self, sources): if self.copylint_is_available(): print 'check copyright statements in source files' cliapp.runcmd(['copyright-statement-lint'] + sources) else: print 'no copyright-statement-lint: no copyright checks'