def _ensure_container(self): new_container = not self._get_container_status() if new_container: try: subprocess.check_call( ['lxc', 'init', self._image, self._container_name]) except subprocess.CalledProcessError as e: raise ContainerConnectionError('Failed to setup container') if self._get_container_status()['status'] == 'Stopped': self._configure_container() try: subprocess.check_call(['lxc', 'start', self._container_name]) except subprocess.CalledProcessError: msg = 'The container could not be started.' if self._remote == 'local': msg += ('\nThe files /etc/subuid and /etc/subgid need to ' 'contain this line for mounting the local folder:' '\n root:1000:1' '\nNote: Add the line to both files, do not ' 'remove any existing lines.' '\nRestart lxd after making this change.') raise ContainerConnectionError(msg) self._wait_for_network() if new_container: self._container_run(['apt-get', 'update']) # Because of https://bugs.launchpad.net/snappy/+bug/1628289 # Needed to run snapcraft as a snap and build-snaps self._container_run(['apt-get', 'install', 'squashfuse', '-y']) self._inject_snapcraft(new_container=new_container)
def _ensure_container(self): new_container = not self._get_container_status() if new_container: try: subprocess.check_call([ 'lxc', 'init', self._image, self._container_name]) except subprocess.CalledProcessError as e: raise ContainerConnectionError('Failed to setup container') if self._get_container_status()['status'] == 'Stopped': self._configure_container() try: subprocess.check_call([ 'lxc', 'start', self._container_name]) except subprocess.CalledProcessError: msg = 'The container could not be started.' if self._container_name.startswith('local:'): msg += ('\nThe files /etc/subuid and /etc/subgid need to ' 'contain this line for mounting the local folder:' '\n root:1000:1' '\nNote: Add the line to both files, do not ' 'remove any existing lines.' '\nRestart lxd after making this change.') raise ContainerConnectionError(msg) self._wait_for_network() if new_container: self._container_run(['apt-get', 'update']) self._inject_snapcraft()
def __init__(self, *, output, source, project_options, metadata, container_name, remote=None): if not output: output = common.format_snap_name(metadata) self._snap_output = output self._source = os.path.realpath(source) self._project_options = project_options self._metadata = metadata self._project_folder = '/root/build_{}'.format(metadata['name']) if not remote: remote = _get_default_remote() _verify_remote(remote) self._container_name = '{}:snapcraft-{}'.format(remote, container_name) server_environment = self._get_remote_info()['environment'] # Use the server architecture to avoid emulation overhead try: kernel = server_environment['kernel_architecture'] except KeyError: kernel = server_environment['kernelarchitecture'] self._server_arch = _get_deb_arch(kernel) if not self._server_arch: raise ContainerConnectionError( 'Unrecognized server architecture {}'.format(kernel)) self._image = 'ubuntu:xenial/{}'.format(self._server_arch) # Use a temporary folder the 'lxd' snap can access lxd_common_dir = os.path.expanduser( os.path.join('~', 'snap', 'lxd', 'common')) os.makedirs(lxd_common_dir, exist_ok=True) self.tmp_dir = tempfile.mkdtemp(prefix='snapcraft', dir=lxd_common_dir)
def __init__(self, *, output, source, project_options, metadata, container_name, remote=None): if not output: output = common.format_snap_name(metadata) self._snap_output = output self._source = os.path.realpath(source) self._project_options = project_options self._metadata = metadata self._project_folder = '/root/build_{}'.format(metadata['name']) if not remote: remote = _get_default_remote() _verify_remote(remote) self._container_name = '{}:snapcraft-{}'.format(remote, container_name) server_environment = self._get_remote_info()['environment'] # Use the server architecture to avoid emulation overhead try: kernel = server_environment['kernel_architecture'] except KeyError: kernel = server_environment['kernelarchitecture'] deb_arch = _get_deb_arch(kernel) if not deb_arch: raise ContainerConnectionError( 'Unrecognized server architecture {}'.format(kernel)) self._host_arch = deb_arch self._image = 'ubuntu:xenial/{}'.format(deb_arch)
def _ensure_container(self): try: subprocess.check_call( ['lxc', 'launch', '-e', self._image, self._container_name]) except subprocess.CalledProcessError as e: raise ContainerConnectionError('Failed to setup container') self._configure_container() self._wait_for_network() self._container_run(['apt-get', 'update']) self._inject_snapcraft()
def _verify_remote(remote): """Verify that the lxd remote exists. :param str remote: the lxd remote to verify. :raises snapcraft.internal.errors.ContainerConnectionError: raised if the lxc call listing the remote fails. """ # There is no easy way to grep the results from `lxc remote list` # so we try and execute a simple operation against the remote. try: subprocess.check_output(['lxc', 'list', '{}:'.format(remote)]) except FileNotFoundError: raise ContainerConnectionError( 'You must have LXD installed in order to use cleanbuild.') except subprocess.CalledProcessError as e: raise ContainerConnectionError( 'There are either no permissions or the remote {!r} ' 'does not exist.\n' 'Verify the existing remotes by running `lxc remote list`\n' .format(remote)) from e
def _get_default_remote(): """Query and return the default lxd remote. Use the lxc command to query for the default lxd remote. In most cases this will return the local remote. :returns: default lxd remote. :rtype: string. :raises snapcraft.internal.errors.ContainerConnectionError: raised if the lxc call fails. """ try: default_remote = check_output(['lxc', 'remote', 'get-default']) except FileNotFoundError: raise ContainerConnectionError( 'You must have LXD installed in order to use cleanbuild.') except CalledProcessError: raise ContainerConnectionError( 'Something seems to be wrong with your installation of LXD.') return default_remote.decode(sys.getfilesystemencoding()).strip()
def _ensure_container(self): try: subprocess.check_call([ 'lxc', 'launch', '-e', self._image, self._container_name]) except subprocess.CalledProcessError as e: raise ContainerConnectionError('Failed to setup container') self._configure_container() self._wait_for_network() self._container_run(['apt-get', 'update']) # Because of https://bugs.launchpad.net/snappy/+bug/1628289 # Needed to run snapcraft as a snap and build-snaps self._container_run(['apt-get', 'install', 'squashfuse', '-y']) self._inject_snapcraft(new_container=True)
def _ensure_container(self): if not self._get_container_status(): check_call(['lxc', 'init', self._image, self._container_name]) if self._get_container_status()['status'] == 'Stopped': check_call([ 'lxc', 'config', 'set', self._container_name, 'environment.SNAPCRAFT_SETUP_CORE', '1' ]) if os.getenv('SNAPCRAFT_PARTS_URI'): check_call([ 'lxc', 'config', 'set', self._container_name, 'environment.SNAPCRAFT_PARTS_URI', os.getenv('SNAPCRAFT_PARTS_URI') ]) # Necessary to read asset files with non-ascii characters. check_call([ 'lxc', 'config', 'set', self._container_name, 'environment.LC_ALL', 'C.UTF-8' ]) if self._container_name.startswith('local:'): # Map host user to root inside container check_call([ 'lxc', 'config', 'set', self._container_name, 'raw.idmap', 'both {} 0'.format(os.getuid()) ]) # Remove existing device (to ensure we update old containers) devices = self._get_container_status()['devices'] if self._project_folder in devices: check_call([ 'lxc', 'config', 'device', 'remove', self._container_name, self._project_folder ]) if 'fuse' not in devices: check_call([ 'lxc', 'config', 'device', 'add', self._container_name, 'fuse', 'unix-char', 'path=/dev/fuse' ]) try: check_call(['lxc', 'start', self._container_name]) except CalledProcessError: msg = 'The container could not be started.' if self._container_name.startswith('local:'): msg += ('\nThe files /etc/subuid and /etc/subgid need to ' 'contain this line for mounting the local folder:' '\n root:1000:1' '\nNote: Add the line to both files, do not ' 'remove any existing lines.' '\nRestart lxd after making this change.') raise ContainerConnectionError(msg)
def _wait_for_network(self): logger.info('Waiting for a network connection...') not_connected = True retry_count = 5 while not_connected: time.sleep(5) try: self._container_run(['python3', '-c', _NETWORK_PROBE_COMMAND]) not_connected = False except ContainerRunError as e: retry_count -= 1 if retry_count == 0: raise ContainerConnectionError( 'No network connection in the container.\n' 'If using a proxy, check its configuration.') logger.info('Network connection established')
def _remote_mount(self, destination, source): # Pipes for sshfs and sftp-server to communicate stdin1, stdout1 = os.pipe() stdin2, stdout2 = os.pipe() # XXX: This needs to be extended once we support other distros try: self._background_process_run(['/usr/lib/sftp-server'], stdin=stdin1, stdout=stdout2) except FileNotFoundError: raise SnapcraftEnvironmentError( 'You must have openssh-sftp-server installed to use a LXD ' 'remote on a different host.\n' ) except subprocess.CalledProcessError: raise SnapcraftEnvironmentError( 'sftp-server seems to be installed but could not be run.\n' ) # Use sshfs in slave mode to reverse mount the destination self._container_run(['apt-get', 'install', '-y', 'sshfs']) self._container_run(['mkdir', '-p', destination], user=self._user) self._background_process_run([ 'lxc', 'exec', self._container_name, '--', 'sudo', '-H', '-u', self._user, 'sshfs', '-o', 'slave', '-o', 'nonempty', ':{}'.format(source), destination], stdin=stdin2, stdout=stdout1) # It may take a second or two for sshfs to come up retry_count = 5 while retry_count: time.sleep(1) if subprocess.check_output([ 'lxc', 'exec', self._container_name, '--', 'sudo', '-H', '-u', self._user, 'ls', self._project_folder]): return retry_count -= 1 raise ContainerConnectionError( 'The project folder could not be mounted.\n' 'Fuse must be enabled on the LXD host.\n' 'You can run the following command to enable it:\n' 'echo Y | sudo tee /sys/module/fuse/parameters/userns_mounts')