def _get_tickets(manifest, container_dir): """Get tickets.""" principals = set(manifest.get('tickets', [])) if not principals: return False tkts_spool_dir = os.path.join(container_dir, 'root', 'var', 'spool', 'tickets') try: tickets.request_tickets(context.GLOBAL.zk.conn, manifest['name'], tkts_spool_dir, principals) except Exception: _LOGGER.exception('Exception processing tickets.') raise exc.ContainerSetupError('Get tickets error', app_abort.AbortedReason.TICKETS) # Check that all requested tickets are valid. for princ in principals: krbcc_file = os.path.join(tkts_spool_dir, princ) if not tickets.krbcc_ok(krbcc_file): _LOGGER.error('Missing or expired tickets: %s, %s', princ, krbcc_file) raise exc.ContainerSetupError(princ, app_abort.AbortedReason.TICKETS) _LOGGER.info('Ticket ok: %s, %s', princ, krbcc_file) return True
def _fetch_image(client, image): """Fetch image from local file or registry returns image metadata object """ (scheme, path) = _parse_image_name(image) if scheme == 'file': try: image_meta = _load_image(client, path) except docker.errors.ImageNotFound: raise exc.ContainerSetupError( 'Failed to load image file {}'.format(image), app_abort.AbortedReason.IMAGE) elif scheme == 'docker': # simulate docker pull logic, if tag not provided, assume latest if ':' not in path: path += ':latest' try: image_meta = _pull_image(client, path) except docker.errors.ImageNotFound: raise exc.ContainerSetupError( 'Fail to pull {}, check image name or disk size'.format(image), app_abort.AbortedReason.IMAGE) else: raise exc.ContainerSetupError( 'Unrecognized image name {}'.format(image), app_abort.AbortedReason.IMAGE) return image_meta
def add_manifest_features(manifest, runtime, tm_env): """Configure optional container features.""" for feature in manifest.get('features', []): if not features.feature_exists(feature): _LOGGER.error('Unable to load feature: %s', feature) raise exc.ContainerSetupError( msg='Unsupported feature: {}'.format(feature), reason='feature', ) feature_mod = features.get_feature(feature)(tm_env) if not feature_mod.applies(manifest, runtime): _LOGGER.error('Feature does not apply: %s', feature) raise exc.ContainerSetupError( msg='Unsupported feature: {}'.format(feature), reason='feature', ) try: feature_mod.configure(manifest) except Exception: _LOGGER.exception('Error configuring feature: %s', feature) raise exc.ContainerSetupError( msg='Error configuring feature: {}'.format(feature), reason='feature', )
def mount_bind(newroot, target, source=None, recursive=True, read_only=True): """Bind mounts `source` to `newroot/target` so that `source` is accessed when reaching `newroot/target`. If a directory, the source will be mounted using --rbind. """ # Ensure root directory exists if not os.path.exists(newroot): raise exc.ContainerSetupError('Path %r does not exist' % newroot) if source is None: source = target target = fs.norm_safe(target) source = fs.norm_safe(source) # Make sure target directory exists. if not os.path.exists(source): raise exc.ContainerSetupError('Source path %r does not exist' % source) mnt_flags = [mount.MS_BIND] # Use --rbind for directories and --bind for files. if recursive and os.path.isdir(source): mnt_flags.append(mount.MS_REC) # Strip leading /, ensure that mount is relative path. while target.startswith('/'): target = target[1:] # Create mount directory, make sure it does not exists. target_fp = os.path.join(newroot, target) if os.path.isdir(source): fs.mkdir_safe(target_fp) else: fs.mkfile_safe(target_fp) res = mount.mount(source=source, target=target_fp, fs_type=None, mnt_flags=mnt_flags) if res == 0 and read_only: res = mount.mount(source=None, target=target_fp, fs_type=None, mnt_flags=(mount.MS_BIND, mount.MS_RDONLY, mount.MS_REMOUNT)) return res
def mount_bind(newroot, mount, target=None, bind_opt=None): """Mounts directory in the new root. Call to mount should be done before chrooting into new root. Unless specified, the target directory will be mounted using --rbind. """ # Ensure root directory exists if not os.path.exists(newroot): raise exc.ContainerSetupError('Path %s does not exist' % newroot) if target is None: target = mount mount = norm_safe(mount) target = norm_safe(target) # Make sure target directory exists. if not os.path.exists(target): raise exc.ContainerSetupError('Target path %s does not exist' % target) # If bind_opt is not explicit, use --rbind for directories and # --bind for files. if bind_opt is None: if os.path.isdir(target): bind_opt = '--rbind' else: bind_opt = '--bind' # Strip leading /, ensure that mount is relative path. while mount.startswith('/'): mount = mount[1:] # Create mount directory, make sure it does not exists. mount_fp = os.path.join(newroot, mount) if os.path.isdir(target): mkdir_safe(mount_fp) else: mkfile_safe(mount_fp) subproc.check_call( [ 'mount', '-n', bind_opt, target, mount_fp ] )
def _allocate_sockets(environment, host_ip, sock_type, count): """Return a list of `count` socket bound to an ephemeral port. """ # TODO(boysson): this should probably be abstracted away if environment == 'prod': port_pool = range(iptables.PROD_PORT_LOW, iptables.PROD_PORT_HIGH + 1) else: port_pool = range(iptables.NONPROD_PORT_LOW, iptables.NONPROD_PORT_HIGH + 1) port_pool = random.sample(port_pool, iptables.PORT_SPAN) # socket objects are closed on GC so we need to return # them and expect the caller to keep them around while needed sockets = [] for real_port in port_pool: if len(sockets) == count: break socket_ = socket.socket(socket.AF_INET, sock_type) socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: socket_.bind((host_ip, real_port)) except socket.error as err: if err.errno == errno.EADDRINUSE: continue raise sockets.append(socket_) else: raise exc.ContainerSetupError('run.alloc_sockets:{0} < {1}'.format( len(sockets), count)) return sockets
def _get_gmsa(tm_env, client, app, container_args): """Waits on GMSA details and adds the credential spec to the args. """ check = gmsa.HostGroupCheck(tm_env) count = 0 found = False while count < 60: found = check.host_in_proid_group(app.proid) if found: break count += 1 time.sleep(1) if not found: raise exc.ContainerSetupError( 'Image {0} was not found'.format(app.image), app_abort.AbortedReason.GMSA) path = credential_spec.generate(app.proid, container_args['name'], client) container_args['security_opt'] = ['credentialspec={}'.format(path)] container_args['hostname'] = app.proid
def run(self): """Prepares container environment and exec's container The function is intended to be invoked from 'run' script and never returns. :returns: This function never returns """ manifest_file = os.path.join(self.container_dir, appcfg.APP_JSON) manifest = app_manifest.read(manifest_file) if not self._can_run(manifest): raise exc.ContainerSetupError( 'Runtime {0} does not support {1}.'.format( self.__class__.__name__, manifest.get('type'))) # Intercept SIGTERM from supervisor, so that initialization is not # left in broken state. terminated = utils.make_signal_flag(utils.term_signal()) unique_name = appcfg.manifest_unique_name(manifest) watchdog_name = 'app_run-%s' % unique_name self.watchdog = self.tm_env.watchdogs.create( watchdog_name, self.run_timeout(manifest), 'Run of {container_dir!r} stalled'.format( container_dir=self.container_dir)) self._run(manifest, self.watchdog, terminated)
def test__run_aborted(self): """Tests docker runtime run when app has been aborted.""" # Access to a protected member # pylint: disable=W0212 docker_runtime = runtime.DockerRuntime(self.tm_env, self.container_dir) treadmill.runtime.docker.runtime._create_container.side_effect = \ docker.errors.ImageNotFound('test') with self.assertRaises(treadmill.exc.ContainerSetupError) as context: docker_runtime._run(self.manifest) self.assertEqual(app_abort.AbortedReason.IMAGE, context.exception.why) app_abort.report_aborted.reset_mock() app_presence = mock.Mock() treadmill.presence.EndpointPresence.return_value = app_presence app_presence.register.side_effect = exc.ContainerSetupError('test') with self.assertRaises(treadmill.exc.ContainerSetupError) as context: docker_runtime._run(self.manifest) self.assertEqual(app_abort.AbortedReason.PRESENCE, context.exception.why)
def run(self, terminated): """Prepares container environment and exec's container The function is intended to be invoked from 'run' script and never returns. :param terminated: Flag where terminated signal will accumulate. :param terminated: ``set`` :returns: This function never returns """ manifest_file = os.path.join(self.container_dir, _APP_YML) manifest = app_manifest.read(manifest_file) if not self._can_run(manifest): raise exc.ContainerSetupError( 'Runtime {0} does not support {1}.'.format( self.__class__.__name__, manifest.get('type') ) ) watchdog_name = 'app_run-%s' % os.path.basename(self.container_dir) self.watchdog = self.tm_env.watchdogs.create( watchdog_name, '60s', 'Run of {0} stalled'.format(self.container_dir)) self._run(manifest, self.watchdog, terminated)
def _start_service_sup(container_dir): """Safely start services supervisor.""" try: supervisor.control_service( os.path.join(container_dir, 'sys', 'start_container'), supervisor.ServiceControlAction.once) except subproc.CalledProcessError: raise exc.ContainerSetupError('start_container')
def _run(self, manifest): context.GLOBAL.zk.conn.add_listener(zkutils.exit_on_lost) with lc.LogContext(_LOGGER, self._service.name, lc.ContainerAdapter) as log: log.info('Running %r', self._service.directory) _sockets = runtime.allocate_network_ports( '0.0.0.0', manifest ) app = runtime.save_app(manifest, self._service.data_dir) app_presence = presence.EndpointPresence( context.GLOBAL.zk.conn, manifest ) app_presence.register_identity() app_presence.register_running() try: client = self._get_client() try: container = _create_container( self._tm_env, self._get_config(), client, app ) except docker.errors.ImageNotFound: raise exc.ContainerSetupError( 'Image {0} was not found'.format(app.image), app_abort.AbortedReason.IMAGE ) container.start() container.reload() _LOGGER.info('Container is running.') app_presence.register_endpoints() appevents.post( self._tm_env.app_events_dir, events.ServiceRunningTraceEvent( instanceid=app.name, uniqueid=app.uniqueid, service='docker' ) ) while container.status == 'running': container.wait(timeout=10) container.reload() finally: _LOGGER.info('Stopping zookeeper.') context.GLOBAL.zk.conn.stop()
def _run(self, manifest): context.GLOBAL.zk.conn.add_listener(zkutils.exit_on_lost) with lc.LogContext(_LOGGER, self._service.name, lc.ContainerAdapter) as log: log.info('Running %r', self._service.directory) manifest['ephemeral_ports']['tcp'] = [] manifest['ephemeral_ports']['udp'] = [] _create_docker_log_symlink(self._service.data_dir) app = runtime.save_app(manifest, self._service.data_dir) volume_mapping = self._get_volume_mapping() app_presence = presence.EndpointPresence(context.GLOBAL.zk.conn, manifest) app_presence.register_identity() app_presence.register_running() client = self._get_client() try: container = _create_container(self._tm_env, self._get_config(), client, app, volume_mapping) except docker.errors.ImageNotFound: raise exc.ContainerSetupError( 'Image {0} was not found'.format(app.image), app_abort.AbortedReason.IMAGE) container.start() container.reload() _update_network_info_in_manifest(container, manifest) # needs to share manifest with container if volume_mapping: container_data_dir = next(iter(volume_mapping)) runtime.save_app(manifest, container_data_dir, app_json='app.json') _LOGGER.info('Container is running.') app_presence.register_endpoints() trace.post( self._tm_env.app_events_dir, events.ServiceRunningTraceEvent(instanceid=app.name, uniqueid=app.uniqueid, service='docker')) _print_container_logs(container)
def run(self, name, image, entrypoint, cmd, **args): """Run """ client = self._get_client() if 'volumes' in args: args['volumes'] = _transform_volumes(args['volumes']) if 'envdirs' in args: args['environment'] = _read_environ(args.pop('envdirs')) # simulate docker pull logic, if tag not provided, assume latest if ':' not in image: image += ':latest' try: image_meta = _pull_image(client, image) except docker.errors.ImageNotFound: raise exc.ContainerSetupError( 'Fail to pull {}, check image name or disk size'.format(image), app_abort.AbortedReason.IMAGE) container = _create_container(client, name, image, image_meta, entrypoint, cmd, **args) # TODO: start docker container event container.start() container.reload() logs_gen = container.logs(stdout=True, stderr=True, stream=True, follow=True) _LOGGER.info('Container %s is running', name) while container.status == 'running': try: for log_lines in logs_gen: sys.stderr.write(log_lines) except socket.error: pass container.reload() # container.wait returns dict with key 'StatusCode' rc = container.wait()['StatusCode'] if os.WIFSIGNALED(rc): # Process died with a signal in docker sig = os.WTERMSIG(rc) os.kill(os.getpid(), sig) else: utils.sys_exit(os.WEXITSTATUS(rc))
def _run(self, manifest): try: app_run.run( tm_env=self._tm_env, runtime_config=self._config, container_dir=self._service.data_dir, manifest=manifest ) except services.ResourceServiceTimeoutError as err: raise exc.ContainerSetupError( err.message, app_abort.AbortedReason.TIMEOUT )
def mount_docker_daemon_path(newroot_norm, app): """Mount tmpfs for docker """ if not _has_docker(app): return # /etc/docker as temp fs as dockerd create /etc/docker/key.json try: fs_linux.mount_tmpfs(newroot_norm, '/etc/docker') except FileNotFoundError as err: _LOGGER.error('Failed to mount docker tmpfs: %s', err) # this exception is caught by sproc run to generate abort event raise exc.ContainerSetupError( msg=str(err), reason=app_abort.AbortedReason.UNSUPPORTED, )
def run(self): """Prepares container environment and exec's container The function is intended to be invoked from 'run' script and never returns. :returns: This function never returns """ manifest_file = os.path.join(self._service.data_dir, appcfg.APP_JSON) manifest = app_manifest.read(manifest_file) if not self._can_run(manifest): raise exc.ContainerSetupError('invalid_type', app_abort.AbortedReason.INVALID_TYPE) self._run(manifest)
def _create_container(client, name, image_meta, entrypoint, cmd, ulimit, **args): """Create docker container from given app. """ # if success, pull returns an image object container_args = { 'name': name, 'image': image_meta.id, 'command': cmd, 'entrypoint': entrypoint, 'detach': True, 'stdin_open': True, 'tty': True, 'network_mode': 'host', 'pid_mode': 'host', 'ipc_mode': 'host', 'ulimits': ulimit, # XXX: uts mode not supported by python lib yet # 'uts_mode': 'host', } # assign user argument user = _get_image_user(image_meta.attrs) if user is None or user == '': uid = os.getuid() gid = os.getgid() container_args['user'] = '******'.format(uid, gid) # add additonal container args for key, value in six.iteritems(args): if value is not None: container_args[key] = value try: # The container might exist already container = client.containers.get(name) container.remove(force=True) # TODO: remove container event except docker.errors.NotFound: pass except docker.errors.APIError as err: raise exc.ContainerSetupError( 'Fail to remove container {}: {}'.format(name, err), app_abort.AbortedReason.PRESENCE) _LOGGER.info('Run docker: %r', container_args) return client.containers.create(**container_args)
def _start_service_sup(tm_env, manifest, container_dir): """Safely start services supervisor.""" for service in manifest['services']: if service['trace']: appevents.post( tm_env.app_events_dir, traceevents.ServiceRunningTraceEvent( instanceid=manifest['name'], uniqueid=manifest['uniqueid'], service=service['name'])) try: supervisor.control_service( os.path.join(container_dir, 'sys', 'start_container'), supervisor.ServiceControlAction.once) except subproc.CalledProcessError: raise exc.ContainerSetupError('start_container')
def _create_ephemeral_with_retry(zkclient, path, data): """Create ephemeral node with retry.""" prev_data = None for _ in range(0, _EPHEMERAL_RETRY_COUNT): try: return zkutils.create(zkclient, path, data, acl=[_SERVERS_ACL], ephemeral=True) except kazoo.client.NodeExistsError: prev_data = zkutils.get_default(zkclient, path) _LOGGER.warning('Node exists, will retry: %s, data: %r', path, prev_data) time.sleep(_EPHEMERAL_RETRY_INTERVAL) raise exc.ContainerSetupError('%s:%s' % (path, prev_data), app_abort.AbortedReason.PRESENCE)
def _get_tickets(appname, app, container_dir): """Get tickets.""" tkts_spool_dir = os.path.join( container_dir, 'root', 'var', 'spool', 'tickets') reply = tickets.request_tickets(context.GLOBAL.zk.conn, appname) if reply: tickets.store_tickets(reply, tkts_spool_dir) # Check that all requested tickets are valid. for princ in app.get('tickets', []): krbcc_file = os.path.join(tkts_spool_dir, princ) if not tickets.krbcc_ok(krbcc_file): _LOGGER.error('Missing or expired tickets: %s, %s', princ, krbcc_file) raise exc.ContainerSetupError('tickets.%s' % princ) else: _LOGGER.info('Ticket ok: %s, %s', princ, krbcc_file)
def run(self, name, image, entrypoint, cmd, **args): """Run """ client = self._get_client() try: if 'volumes' in args: args['volumes'] = _transform_volumes(args['volumes']) if 'envdirs' in args: args['environment'] = _read_environ(args.pop('envdirs')) container = _create_container(client, name, image, entrypoint, cmd, **args) except docker.errors.ImageNotFound: raise exc.ContainerSetupError( 'Image {0} was not found'.format(image), app_abort.AbortedReason.IMAGE) container.start() container.reload() logs_gen = container.logs(stdout=True, stderr=True, stream=True, follow=True) _LOGGER.info('Container %s is running', name) while container.status == 'running': try: for log_lines in logs_gen: sys.stderr.write(log_lines) except socket.error: pass container.reload() rc = container.wait() if os.WIFSIGNALED(rc): # Process died with a signal in docker sig = os.WTERMSIG(rc) os.kill(os.getpid(), sig) else: utils.sys_exit(os.WEXITSTATUS(rc))
def _create_ephemeral_with_retry(zkclient, path, data): """Create ephemeral node with retry.""" prev_data = None for _ in range(0, 5): try: return zkutils.create(zkclient, path, data, acl=[_SERVERS_ACL], ephemeral=True) except kazoo.client.NodeExistsError: prev_data, metadata = zkutils.get_default(zkclient, path, need_metadata=True) _LOGGER.warn('Node exists, will retry: %s, data: %r, metadata: %r', path, prev_data, metadata) time.sleep(_EPHEMERAL_RETRY_INTERVAL) raise exc.ContainerSetupError('presence.%s:%s' % (path, prev_data))
def _allocate_sockets(environment, host_ip, sock_type, count): """Return a list of `count` socket bound to an ephemeral port. """ # TODO: this should probably be abstracted away if environment == 'prod': port_pool = six.moves.range(PROD_PORT_LOW, PROD_PORT_HIGH + 1) else: port_pool = six.moves.range(NONPROD_PORT_LOW, NONPROD_PORT_HIGH + 1) port_pool = random.sample(port_pool, PORT_SPAN) # socket objects are closed on GC so we need to return # them and expect the caller to keep them around while needed sockets = [] for real_port in port_pool: if len(sockets) == count: break socket_ = socket.socket(socket.AF_INET, sock_type) try: socket_.bind((host_ip, real_port)) if sock_type == socket.SOCK_STREAM: socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) socket_.listen(0) except socket.error as err: if err.errno == errno.EADDRINUSE: continue raise if six.PY3: # We want the sockets to survive an execv socket_.set_inheritable(True) sockets.append(socket_) else: raise exc.ContainerSetupError('{0} < {1}'.format(len(sockets), count), app_abort.AbortedReason.PORTS) return sockets
def _get_keytabs(manifest, container_dir, locker_pattern): """Get keytabs.""" # keytab of 'proid:service' is not fetched from keytab locker keytabs = [kt for kt in manifest.get('keytabs', []) if ':' not in kt] _LOGGER.debug('keytabs to fetch from locker: %r', keytabs) if not keytabs: return kts_spool_dir = os.path.join(container_dir, 'root', 'var', 'spool', 'keytabs') try: kt_client.request_keytabs( context.GLOBAL.zk.conn, manifest['name'], kts_spool_dir, locker_pattern, ) except Exception: _LOGGER.exception('Exception processing keytabs.') raise exc.ContainerSetupError('Get keytabs error', app_abort.AbortedReason.KEYTABS)
def _get_keytabs(manifest, container_dir, locker_pattern): """Get keytabs.""" # keytab of 'proid:service' is not fetched from keytab locker keytabs_to_fetch = set() kt_specs = set() for kt in manifest.get('keytabs', []): if ':' not in kt: keytabs_to_fetch.add(kt) else: kt_specs.add(kt) _LOGGER.debug('keytabs to fetch from locker: %r', keytabs_to_fetch) if not keytabs_to_fetch: return kts_spool_dir = os.path.join(container_dir, 'root', 'var', 'spool', 'keytabs') try: kt_client.request_keytabs( context.GLOBAL.zk.conn, manifest['name'], kts_spool_dir, locker_pattern, ) except Exception: _LOGGER.exception('Exception processing keytabs.') raise exc.ContainerSetupError('Get keytabs error', app_abort.AbortedReason.KEYTABS) # add fetched host keytabs to /etc/krb5.keytab kt_dest = os.path.join(container_dir, 'overlay', 'etc', 'krb5.keytab') keytabs.add_keytabs_to_file(kts_spool_dir, 'host', kt_dest) # add fetched keytabs to keytab spec file for kt_spec in kt_specs: owner, princ = kt_spec.split(':', 1) kt_dest = os.path.join(kts_spool_dir, owner) keytabs.add_keytabs_to_file(kts_spool_dir, princ, kt_dest, owner)
def _run(self, manifest): context.GLOBAL.zk.conn.add_listener(zkutils.exit_on_lost) with lc.LogContext(_LOGGER, self._service.name, lc.ContainerAdapter) as log: log.info('Running %r', self._service.directory) manifest['ephemeral_ports']['tcp'] = [] manifest['ephemeral_ports']['udp'] = [] # create container_data dir container_data_dir = os.path.join(self._service.data_dir, 'container_data') log.info('container_data %r', container_data_dir) fs.mkdir_safe(container_data_dir) # volume mapping config : read-only mapping volume_mapping = { container_data_dir: { 'bind': 'c:\\container_data', 'mode': 'ro' } } app = runtime.save_app(manifest, self._service.data_dir) app_presence = presence.EndpointPresence(context.GLOBAL.zk.conn, manifest) app_presence.register_identity() app_presence.register_running() client = self._get_client() try: container = _create_container(self._tm_env, self._get_config(), client, app, volume_mapping) except docker.errors.ImageNotFound: raise exc.ContainerSetupError( 'Image {0} was not found'.format(app.image), app_abort.AbortedReason.IMAGE) container.start() container.reload() _update_network_info_in_manifest(container, manifest) runtime.save_app(manifest, container_data_dir, app_json='app.json') _LOGGER.info('Container is running.') app_presence.register_endpoints() trace.post( self._tm_env.app_events_dir, events.ServiceRunningTraceEvent(instanceid=app.name, uniqueid=app.uniqueid, service='docker')) while container.status == 'running': container.wait(timeout=10) container.reload()
def make_fsroot(root_dir, app): """Initializes directory structure for the container in a new root. The container uses pretty much a blank a FHS 3 layout. - Bind directories in parent / (with exceptions - see below.) - Skip /tmp, create /tmp in the new root with correct permissions. - Selectively create / bind /var. - /var/tmp (new) - /var/log (new) - /var/spool - create empty with dirs. - Bind everything in /var, skipping /spool/tickets tm_env is used to deliver abort events """ newroot_norm = fs.norm_safe(root_dir) emptydirs = [ '/bin', '/dev', '/etc', '/home', '/lib', '/lib64', '/opt', '/proc', '/root', '/run', '/sbin', '/sys', '/tmp', '/usr', '/var/cache', '/var/empty', '/var/empty/sshd', '/var/lib', '/var/lock', '/var/log', '/var/opt', '/var/spool', '/var/tmp', '/var/spool/keytabs', '/var/spool/tickets', '/var/spool/tokens', # for SSS '/var/lib/sss', ] stickydirs = [ '/opt', '/run', '/tmp', '/var/cache', '/var/lib', '/var/lock', '/var/log', '/var/opt', '/var/tmp', '/var/spool/keytabs', '/var/spool/tickets', '/var/spool/tokens', ] # these folders are shared with underlying host and other containers, mounts = [ '/bin', '/etc', # TODO: Add /etc/opt '/lib', '/lib64', '/root', '/sbin', '/usr', # for SSS '/var/lib/sss', # TODO: Remove below once PAM UDS is implemented os.path.expandvars('${TREADMILL_APPROOT}/env'), os.path.expandvars('${TREADMILL_APPROOT}/spool'), ] for directory in emptydirs: fs.mkdir_safe(newroot_norm + directory) for directory in stickydirs: os.chmod(newroot_norm + directory, 0o777 | stat.S_ISVTX) # /var/empty must be owned by root and not group or world-writable. os.chmod(os.path.join(newroot_norm, 'var/empty'), 0o711) fs_linux.mount_bind(newroot_norm, os.path.join(os.sep, 'sys'), source='/sys', recursive=True, read_only=False) make_dev(newroot_norm) # Per FHS3 /var/run should be a symlink to /run which should be tmpfs fs.symlink_safe(os.path.join(newroot_norm, 'var', 'run'), '/run') # We create an unbounded tmpfs mount so that runtime data can be written to # it, counting against the memory limit of the container. fs_linux.mount_tmpfs(newroot_norm, '/run') # Make shared directories/files readonly to container for mount in mounts: if os.path.exists(mount): fs_linux.mount_bind(newroot_norm, mount, recursive=True, read_only=True) if hasattr(app, 'docker') and app.docker: # If unable to mount docker directory, we throw Aborted events try: _mount_docker_tmpfs(newroot_norm) except FileNotFoundError as err: _LOGGER.error('Failed to mount docker tmpfs: %s', err) # this exception is caught by sproc run to generate abort event raise exc.ContainerSetupError( msg=str(err), reason=app_abort.AbortedReason.UNSUPPORTED, )