def get_image_registry_url(self, image_name): """Helper function for obtain registry url of image from it's name :param image_name: str, short name of an image, example: - conu:0.5.0 :return: str, image registry url, example: - 172.30.1.1:5000/myproject/conu:0.5.0 """ c = self._oc_command(["get", "is", image_name, "--output=jsonpath=\'{ .status.dockerImageRepository }\'"]) try: internal_registry_name = run_cmd(c, return_output=True) except subprocess.CalledProcessError as ex: raise ConuException("oc get is failed: %s" % ex) logger.info("Image registry url: %s", internal_registry_name) return internal_registry_name.replace("'", "").replace('"', '')
def _wait_for_machine_finish(self, name): """ Interna method wait until machine is really destroyed, machine does not exist. :param name: str machine name :return: True or exception """ # TODO: rewrite it using probes module in utils for foo in range(constants.DEFAULT_RETRYTIMEOUT): time.sleep(constants.DEFAULT_SLEEP) out = run_cmd( ["machinectl", "--no-pager", "status", name], ignore_status=True, return_output=True) if out != 0: return True raise ConuException( "Unable to stop machine %s within %d" % (name, constants.DEFAULT_RETRYTIMEOUT))
def create_from_tuple(cls, volume): """ Create instance from tuple. :param volume: tuple in one one of the following forms: target | source,target | source,target,mode :return: instance of Volume """ if isinstance(volume, six.string_types): return Volume(target=volume) elif len(volume) == 2: return Volume(source=volume[0], target=volume[1]) elif len(volume) == 3: return Volume(source=volume[0], target=volume[1], mode=volume[2]) else: logger.debug( "Cannot create volume instance from {}." "It has to be tuple of form target x source,target x source,target,mode." .format(volume)) raise ConuException("Cannot create volume instance.")
def pull(self): """ Pull this image from registry. Raises an exception if the image is not found in the registry. :return: None """ for json_s in self.d.pull(repository=self.name, tag=self.tag, stream=True): logger.debug(json_s) json_e = json.loads(json_s) status = graceful_get(json_e, "status") if status: logger.info(status) else: error = graceful_get(json_e, "error") logger.error(status) raise ConuException("There was an error while pulling the image %s: %s", self.name, error)
def pull(self): """ Pull this image from registry. Raises an exception if the image is not found in the registry. :return: None """ for json_e in self.d.pull(repository=self.name, tag=self.tag, stream=True, decode=True): logger.debug(json_e) status = graceful_get(json_e, "status") if status: logger.info(status) else: error = graceful_get(json_e, "error") logger.error(status) raise ConuException("There was an error while pulling the image %s: %s", self.name, error) self.using_transport(SkopeoTransport.DOCKER_DAEMON)
def create_app_from_template(self, image_name, name, template, name_in_template, other_images, oc_new_app_args, project): """ Helper function to create app from template :param image_name: image to be used as builder image :param name: name of app from template :param template: str, url or local path to a template to use :param name_in_template: dict, {repository:tag} image name used in the template :param other_images: list of dict, some templates need other image to be pushed into the OpenShift registry, specify them in this parameter as list of dict [{<image>:<tag>}], where "<image>" is image name with tag and "<tag>" is a tag under which the image should be available in the OpenShift registry. :param oc_new_app_args: additional parameters for the `oc new-app` :param project: project where app should be created :return: None """ self.project = project # push images to registry repository, tag = list(name_in_template.items())[0] self.import_image(repository + ":" + tag, image_name) other_images = other_images or [] for o in other_images: image, tag = list(o.items())[0] self.import_image( tag.split(':')[0] + ":" + tag.split(':')[1], image) c = self._oc_command(["new-app"] + [template] + oc_new_app_args + ["-n"] + [project] + ["--name=%s" % name]) logger.info("Creating new app in project %s", project) try: # ignore status because sometimes oc new-app can fail when image # is already pushed in register o = run_cmd(c, return_output=True, ignore_status=True) logger.debug(o) except subprocess.CalledProcessError as ex: raise ConuException("oc new-app failed: %s" % ex) return name
def get_metadata(self, refresh=True): """ return cached metadata by default :param refresh: bool, update the metadata with up to date content :return: dict """ if refresh or not self._metadata: ident = self._id or self.get_full_name() if not ident: raise ConuException( "This image does not have a valid identifier.") self._metadata = convert_kv_to_dict( run_cmd([ "machinectl", "--no-pager", "--output=export", "show-image", ident ], return_output=True)) return self._metadata
def transport_param(image): """ Parse DockerImage info into skopeo parameter :param image: DockerImage :return: string. skopeo parameter specifying image """ transports = {SkopeoTransport.CONTAINERS_STORAGE: "containers-storage:", SkopeoTransport.DIRECTORY: "dir:", SkopeoTransport.DOCKER: "docker://", SkopeoTransport.DOCKER_ARCHIVE: "docker-archive", SkopeoTransport.DOCKER_DAEMON: "docker-daemon:", SkopeoTransport.OCI: "oci:", SkopeoTransport.OSTREE: "ostree:"} transport = image.transport tag = image.tag repository = image.name path = image.path if not transport: transport = SkopeoTransport.DOCKER command = transports[transport] path_required = [SkopeoTransport.DIRECTORY, SkopeoTransport.DOCKER_ARCHIVE, SkopeoTransport.OCI] if transport in path_required and path is None: raise ValueError(transports[transport] + " path is required to be specified") if transport == SkopeoTransport.DIRECTORY: return command + path if transport == SkopeoTransport.DOCKER_ARCHIVE: command += path if repository is None: return command command += ":" if transport in [SkopeoTransport.CONTAINERS_STORAGE, SkopeoTransport.DOCKER, SkopeoTransport.DOCKER_ARCHIVE, transport.DOCKER_DAEMON]: return command + repository + ":" + tag if transport == SkopeoTransport.OCI: return command + path + ":" + tag if transport == SkopeoTransport.OSTREE: return command + repository + ("@" + path if path else "") raise ConuException("This transport is not supported")
def extend(self, source, new_image_name, s2i_args=None): """ extend this s2i-enabled image using provided source, raises ConuException if `s2i build` fails :param source: str, source used to extend the image, can be path or url :param new_image_name: str, name of the new, extended image :param s2i_args: list of str, additional options and arguments provided to `s2i build` :return: S2Image instance """ s2i_args = s2i_args or [] c = self._s2i_command(["build"] + s2i_args + [source, self.get_full_name()]) if new_image_name: c.append(new_image_name) try: run_cmd(c) except subprocess.CalledProcessError as ex: raise ConuException("s2i build failed: %s" % ex) return S2IDockerImage(new_image_name)
def wait_for_service(self, app_name, port, expected_output=None, timeout=100): """Block until service is not ready to accept requests, raises an exc ProbeTimeout if timeout is reached :param app_name: str, name of the app :param port: str or int, port of the service :param expected_output: If not None method will check output returned from request and try to find matching string. :param timeout: int or float (seconds), time to wait for pod to run :return: None """ logger.info('Waiting for service to get ready') try: Probe(timeout=timeout, pause=10, fnc=self.request_service, app_name=app_name, port=port, expected_output=expected_output, expected_retval=True).run() except ProbeTimeout: logger.warning("Timeout: Request to service unsuccessful.") raise ConuException("Timeout: Request to service unsuccessful.")
def copy(self, repository=None, tag=None, source_transport=None, target_transport=SkopeoTransport.DOCKER, source_path=None, target_path=None, logs=True): """ Copy this image :param repository to be copied to :param tag :param source_transport Transport :param target_transport Transport :param source_path needed to specify for dir, docker-archive or oci transport :param target_path needed to specify for dir, docker-archive or oci transport :param logs enable/disable logs :return: the new DockerImage """ if not repository: repository = self.name if not tag: tag = self.tag if self.tag else "latest" if target_transport == SkopeoTransport.OSTREE and tag and logs: logging.warning("tag was ignored") target = (DockerImage( repository, tag, pull_policy=DockerImagePullPolicy.NEVER).using_transport( target_transport, target_path)) self.using_transport(source_transport, source_path) try: run_cmd([ "skopeo", "copy", transport_param(self), transport_param(target) ]) except subprocess.CalledProcessError: raise ConuException("There was an error while copying repository", self.name) return target
def get_metadata(self, refresh=True): """ return cached metadata by default :param refresh: bool, returns up to date metadata if set to True :return: dict """ if refresh or not self._metadata: ident = self._id or self.name if not ident: raise ConuException( "This container does not have a valid identifier.") out = run_cmd(["machinectl", "--no-pager", "show", ident], return_output=True, ignore_status=True) if "Could not get path to machine" in out: self._metadata = {} else: self._metadata = convert_kv_to_dict(out) return self._metadata
def start_build(self, build, args=None): """ Start new build, raise exception if build failed :param build: str, name of the build :param args: list of str, another args of 'oc start-build' commands :return: None """ args = args or [] c = self._oc_command(["start-build"] + [build] + args) logger.info("Executing build %s", build) logger.info("Build command: %s", " ".join(c)) try: Probe(timeout=-1, pause=5, count=2, expected_exceptions=subprocess.CalledProcessError, expected_retval=None, fnc=run_cmd, cmd=c).run() except ProbeTimeout as e: raise ConuException("Cannot start build of application: %s" % e)
def run_via_binary_in_foreground(self, run_command_instance=None, popen_params=None, container_name=None): """ Create a container using this image and run it in foreground; this method is useful to test real user scenarios when users invoke containers using binary and pass input into the container via STDIN. Please bear in mind that conu doesn't know the ID of the container when created like this, so it's highly recommended to name your container. You are also responsible for checking whether the container exited successfully via: container.popen_instance.returncode Please consult the documentation for subprocess python module for best practices on how you should work with instance of Popen :param run_command_instance: instance of DockerRunBuilder :param popen_params: dict, keyword arguments passed to Popen constructor :param container_name: str, pretty container identifier :return: instance of DockerContainer """ logger.info("run container via binary in foreground") run_command_instance = run_command_instance or DockerRunBuilder() if not isinstance(run_command_instance, DockerRunBuilder): raise ConuException( "run_command_instance needs to be an instance of DockerRunBuilder" ) popen_params = popen_params or {} run_command_instance.image_name = self.get_id() if container_name: run_command_instance.options += ["--name", container_name] logger.debug("command = %s", str(run_command_instance)) popen_instance = subprocess.Popen(run_command_instance.build(), **popen_params) container_id = None return DockerContainer(self, container_id, popen_instance=popen_instance, name=container_name)
def create_new_app_from_source(self, image, project, source=None, oc_new_app_args=None): """ Deploy app using source-to-image in OpenShift cluster using 'oc new-app' :param image: image to be used as builder image :param project: project where app should be created :param source: source used to extend the image, can be path or url :param oc_new_app_args: additional parameters for the `oc new-app` :return: str, name of the app """ # app name is generated randomly name = 'app-{random_string}'.format(random_string=random_str(5)) oc_new_app_args = oc_new_app_args or [] new_image = push_to_registry(image, image.name.split('/')[-1], image.tag, project) c = self._oc_command(["new-app"] + [new_image.name + "~" + source] + oc_new_app_args + ["-n"] + [project] + ["--name=%s" % name]) logger.info("Creating new app in project %s", project) try: o = run_cmd(c, return_output=True) logger.debug(o) except subprocess.CalledProcessError as ex: raise ConuException("oc new-app failed: %s" % ex) # build from local source if os.path.isdir(source): self.start_build(name, ["-n", project, "--from-dir=%s" % source]) return name
def import_image(self, imported_image_name, image_name): """Import image using `oc import-image` command. :param imported_image_name: str, short name of an image in internal registry, example: - hello-openshift:latest :param image_name: full repository name, example: - docker.io/openshift/hello-openshift:latest :return: str, short name in internal registry """ c = self._oc_command(["import-image", imported_image_name, "--from=%s" % image_name, "--confirm"]) logger.info("Importing image from: %s, as: %s", image_name, imported_image_name) try: o = run_cmd(c, return_output=True, ignore_status=True) logger.debug(o) except subprocess.CalledProcessError as ex: raise ConuException("oc import-image failed: %s" % ex) return imported_image_name
def using_transport(self, transport=None, path=None, logs=True): """ change used transport :param transport: from where will be this image copied :param path in filesystem :param logs enable/disable :return: self """ if not transport: return self if self.transport == transport and self.path == path: return self path_required = [ SkopeoTransport.DIRECTORY, SkopeoTransport.DOCKER_ARCHIVE, SkopeoTransport.OCI ] if transport in path_required: if not path and logs: logging.debug("path not provided, temporary path was used") self.path = self.mount(path).mount_point elif transport == SkopeoTransport.OSTREE: if path and not os.path.isabs(path): raise ConuException("Path '", path, "' for OSTree transport is not absolute") if not path and logs: logging.debug( "path not provided, default /ostree/repo path was used") self.path = path else: if path and logs: logging.warning("path %s was ignored!", path) self.path = None self.transport = transport return self
def _internal_reschedule(callback, retry=3, sleep_time=constants.DEFAULT_SLEEP): """ workaround method for internal_run_container method It sometimes fails because of Dbus or whatever, so try to start it moretimes :param callback: callback method list :param retry: how many times try to invoke command :param sleep_time: how long wait before subprocess.poll() to find if it failed :return: subprocess object """ for foo in range(retry): container_process = callback[0](callback[1], *callback[2], **callback[3]) time.sleep(sleep_time) container_process.poll() rcode = container_process.returncode if rcode is None: return container_process raise ConuException( "Unable to start nspawn container - process failed for {}-times". format(retry))
def _run_container(self, run_command_instance, callback): """ this is internal method """ tmpfile = os.path.join(get_backend_tmpdir(), random_tmp_filename()) # the cid file must not exist run_command_instance.options += ["--cidfile=%s" % tmpfile] logger.debug("docker command: %s" % run_command_instance) response = callback() def get_cont_id(): if not os.path.exists(tmpfile): return False with open(tmpfile, 'r') as fd: content = fd.read() return bool(content) Probe(timeout=2, count=10, pause=0.1, fnc=get_cont_id).run() with open(tmpfile, 'r') as fd: container_id = fd.read() if not container_id: raise ConuException("We could not get container's ID, it probably was not created") return container_id, response
def __init__(self, repository, tag=None, pull_policy=ImagePullPolicy.IF_NOT_PRESENT, location=None): """ :param repository: str, expected name of this image :param tag: not used, nspawn doesn't utilize the concept of tags :param pull_policy: enum, strategy to apply for pulling the image :param location: str, location from which we can obtain the image, it can be local (a path) or remote (URL) """ self.system_requirements() self.container_process = None super(NspawnImage, self).__init__(repository, tag=None) if not isinstance(pull_policy, ImagePullPolicy): raise ConuException( "'pull_policy' is not an instance of ImagePullPolicy") self.pull_policy = pull_policy self.location = location self.local_location = os.path.join( CONU_IMAGES_STORE, self.name + ("_" + self.tag if self.tag else "")) # TODO: move this code to API __init__, will be same for various # backends or maybe add there some callback method if self.pull_policy == ImagePullPolicy.ALWAYS: logger.debug("pull policy set to 'always', pulling the image") self.pull() elif self.pull_policy == ImagePullPolicy.IF_NOT_PRESENT and not self.is_present( ): logger.debug( "pull policy set to 'if_not_present' and image is not present, " "pulling the image") self.pull() elif self.pull_policy == ImagePullPolicy.NEVER: logger.debug("pull policy set to 'never'")
def request_service(self, app_name, port, expected_output=None): """ Make request on service of app. If there is connection error function return False. :param app_name: str, name of the app :param expected_output: str, If not None method will check output returned from request and try to find matching string. :param port: str or int, port of the service :return: bool, True if connection was established False if there was connection error """ # get ip of service ip = [ service.get_ip() for service in self.list_services(namespace=self.project) if service.name == app_name ][0] # make http request to obtain output if expected_output is not None: try: output = self.http_request(host=ip, port=port) if expected_output not in output.text: raise ConuException( "Connection to service established, but didn't match expected output" ) else: logger.info( "Connection to service established and return expected output!" ) return True except ConnectionError as e: logger.info("Connection to service failed %s!", e) return False elif check_port(port, host=ip): # check if port is open return True return False
def has_pkgs_signed_with(self, allowed_keys): """ Check signature of packages installed in image. Raises exception when * rpm binary is not installed in image * parsing of rpm fails * there are packages in image that are not signed with one of allowed keys :param allowed_keys: list of allowed keys :return: bool """ if not allowed_keys or not isinstance(allowed_keys, list): raise ConuException("allowed_keys must be a list") command = ['rpm', '-qa', '--qf', '%{name} %{SIGPGP:pgpsig}\n'] cont = self.run_via_binary(command=command) try: out = cont.logs_unicode()[:-1].split('\n') check_signatures(out, allowed_keys) finally: cont.stop() cont.delete() return True
def run_in_pod(self, namespace="default"): """ run image inside Kubernetes Pod :param namespace: str, name of namespace where pod will be created :return: Pod instance """ core_api = get_core_api() image_data = self.get_metadata() pod = Pod.create(image_data) try: pod_instance = core_api.create_namespaced_pod(namespace=namespace, body=pod) except ApiException as e: raise ConuException("Exception when calling CoreV1Api->create_namespaced_pod: %s\n" % e) logger.info( "Starting Pod %s in namespace %s" % (pod_instance.metadata.name, namespace)) return Pod(name=pod_instance.metadata.name, namespace=pod_instance.metadata.namespace, spec=pod_instance.spec)
def _wait_for_machine_booted(name, suffictinet_texts=None): """ Internal method wait until machine is ready, in common case means there is running systemd-logind :param name: str with machine name :param suffictinet_texts: alternative text to check in output :return: True or exception """ # TODO: rewrite it using probes module in utils suffictinet_texts = suffictinet_texts or ["systemd-logind"] # optionally use: "Unit: machine" for foo in range(constants.DEFAULT_RETRYTIMEOUT): time.sleep(constants.DEFAULT_SLEEP) out = run_cmd(["machinectl", "--no-pager", "status", name], ignore_status=True, return_output=True) for restr in suffictinet_texts: if restr in out: time.sleep(constants.DEFAULT_SLEEP) return True raise ConuException( "Unable to start machine %s within %d (machinectl status command dos not contain %s)" % (name, constants.DEFAULT_RETRYTIMEOUT, suffictinet_texts))
def __init__(self, logging_level=logging.INFO, logging_kwargs=None, cleanup=None): """ This method serves as a configuration interface for conu. :param logging_level: int, control logger verbosity: see logging.{DEBUG,INFO,ERROR} :param logging_kwargs: dict, additional keyword arguments for logger set up, for more info see docstring of set_logging function :param cleanup: list, list of k8s cleanup policy values, examples: - [CleanupPolicy.EVERYTHING] - [CleanupPolicy.PODS, CleanupPolicy.SERVICES] - [CleanupPolicy.NOTHING] """ super(K8sBackend, self).__init__( logging_level=logging_level, logging_kwargs=logging_kwargs) self.core_api = get_core_api() self.apps_api = get_apps_api() self.managed_namespaces = [] self.cleanup = cleanup or [K8sCleanupPolicy.NOTHING] if K8sCleanupPolicy.NOTHING in self.cleanup and len(self.cleanup) != 1: raise ConuException("Cleanup policy NOTHING cannot be combined with other values")
def run_via_binary_in_foreground(self, run_command_instance=None, command=None, volumes=None, additional_opts=None, popen_params=None, container_name=None): """ Create a container using this image and run it in foreground; this method is useful to test real user scenarios when users invoke containers using binary and pass input into the container via STDIN. You are also responsible for: * redirecting STDIN when intending to use container.write_to_stdin afterwards by setting popen_params={"stdin": subprocess.PIPE} during run_via_binary_in_foreground * checking whether the container exited successfully via: container.popen_instance.returncode Please consult the documentation for subprocess python module for best practices on how you should work with instance of Popen :param run_command_instance: instance of PodmanRunBuilder :param command: list of str, command to run in the container, examples: - ["ls", "/"] - ["bash", "-c", "ls / | grep bin"] :param volumes: tuple or list of tuples in the form: * `("/path/to/directory", )` * `("/host/path", "/container/path")` * `("/host/path", "/container/path", "mode")` * `(conu.Directory('/host/path'), "/container/path")` (source can be also Directory instance) :param additional_opts: list of str, additional options for `docker run` :param popen_params: dict, keyword arguments passed to Popen constructor :param container_name: str, pretty container identifier :return: instance of PodmanContainer """ logger.info("run container via binary in foreground") if (command is not None or additional_opts is not None) \ and run_command_instance is not None: raise ConuException( "run_command_instance and command parameters cannot be " "passed into method at same time") if run_command_instance is None: command = command or [] additional_opts = additional_opts or [] if (isinstance(command, list) or isinstance(command, tuple) and isinstance(additional_opts, list) or isinstance(additional_opts, tuple)): run_command_instance = PodmanRunBuilder( command=command, additional_opts=additional_opts) else: raise ConuException( "command and additional_opts needs to be list of str or None" ) else: run_command_instance = run_command_instance or PodmanRunBuilder() if not isinstance(run_command_instance, PodmanRunBuilder): raise ConuException("run_command_instance needs to be an " "instance of PodmanRunBuilder") popen_params = popen_params or {} run_command_instance.image_name = self.get_id() if container_name: run_command_instance.options += ["--name", container_name] if volumes: run_command_instance.options += self.get_volume_options( volumes=volumes) def callback(): return subprocess.Popen(run_command_instance.build(), **popen_params) container_id, popen_instance = self._run_container( run_command_instance, callback) actual_name = graceful_get(self._inspect(container_id), "Name") if container_name and container_name != actual_name: raise ConuException( "Unexpected container name value. Expected = " + str(container_name) + " Actual = " + str(actual_name)) if not container_name: container_name = actual_name return PodmanContainer(self, container_id, popen_instance=popen_instance, name=container_name)
def run_via_binary(self, run_command_instance=None, command=None, volumes=None, additional_opts=None, **kwargs): """ create a container using this image and run it in background; this method is useful to test real user scenarios when users invoke containers using binary :param run_command_instance: instance of PodmanRunBuilder :param command: list of str, command to run in the container, examples: - ["ls", "/"] - ["bash", "-c", "ls / | grep bin"] :param volumes: tuple or list of tuples in the form: * `("/path/to/directory", )` * `("/host/path", "/container/path")` * `("/host/path", "/container/path", "mode")` * `(conu.Directory('/host/path'), "/container/path")` (source can be also Directory instance) :param additional_opts: list of str, additional options for `podman run` :return: instance of PodmanContainer """ logger.info("run container via binary in background") if (command is not None or additional_opts is not None) \ and run_command_instance is not None: raise ConuException( "run_command_instance and command parameters cannot be passed " "into method at same time") if run_command_instance is None: command = command or [] additional_opts = additional_opts or [] if (isinstance(command, list) or isinstance(command, tuple) and isinstance(additional_opts, list) or isinstance(additional_opts, tuple)): run_command_instance = PodmanRunBuilder( command=command, additional_opts=additional_opts) else: raise ConuException( "command and additional_opts needs to be list of str or None" ) else: run_command_instance = run_command_instance or PodmanRunBuilder() if not isinstance(run_command_instance, PodmanRunBuilder): raise ConuException( "run_command_instance needs to be an instance of PodmanRunBuilder" ) run_command_instance.image_name = self.get_id() run_command_instance.options += ["-d"] if volumes: run_command_instance.options += self.get_volume_options( volumes=volumes) def callback(): try: # FIXME: catch std{out,err}, print stdout to logger.debug, stderr to logger.error run_cmd(run_command_instance.build()) except subprocess.CalledProcessError as ex: raise ConuException("Container exited with an error: %s" % ex.returncode) container_id, _ = self._run_container(run_command_instance, callback) container_name = graceful_get(self._inspect(container_id), "Name") return PodmanContainer(self, container_id, name=container_name)
def callback(): try: # FIXME: catch std{out,err}, print stdout to logger.debug, stderr to logger.error run_cmd(run_command_instance.build()) except subprocess.CalledProcessError as ex: raise ConuException("Container exited with an error: %s" % ex.returncode)
def bootstrap( repositories, name, packages=None, additional_packages=None, tag="latest", prefix=constants.CONU_ARTIFACT_TAG, packager=None): """ bootstrap Image from scratch. It creates new image based on giver dnf repositories and package setts :param repositories:list of repositories :param packages: list of base packages in case you don't want to use base packages defined in contants :param additional_packages: list of additonal packages :param tag: tag the output image :param prefix: use some prefix for newly cretaed image (internal usage) :param packager: use another packages in case you dont want to use defined in constants (dnf) :return: NspawnImage instance """ additional_packages = additional_packages or [] if packages is None: packages = constants.CONU_NSPAWN_BASEPACKAGES package_set = packages + additional_packages if packager is None: packager = constants.BOOTSTRAP_PACKAGER mounted_dir = mkdtemp() if not os.path.exists(mounted_dir): os.makedirs(mounted_dir) imdescriptor, tempimagefile = mkstemp() image_size = constants.BOOTSTRAP_IMAGE_SIZE_IN_MB # create no partitions when create own image run_cmd(["dd", "if=/dev/zero", "of={}".format(tempimagefile), "bs=1M", "count=1", "seek={}".format(image_size)]) run_cmd([constants.BOOTSTRAP_FS_UTIL, tempimagefile]) # TODO: is there possible to use NspawnImageFS class instead of direct # mount/umount, image objects does not exist run_cmd(["mount", tempimagefile, mounted_dir]) if os.path.exists(os.path.join(mounted_dir, "usr")): raise ConuException("Directory %s already in use" % mounted_dir) if not os.path.exists(mounted_dir): os.makedirs(mounted_dir) repo_params = [] repo_file_content = "" for cnt in range(len(repositories)): repo_params += ["--repofrompath", "{0}{1},{2}".format(prefix, cnt, repositories[cnt])] repo_file_content += """ [{NAME}{CNT}] name={NAME}{CNT} baseurl={REPO} enabled=1 gpgcheck=0 """.format(NAME=prefix, CNT=cnt, REPO=repositories[cnt]) packages += set(additional_packages) logger.debug("Install system to direcory: %s" % mounted_dir) logger.debug("Install packages: %s" % packages) logger.debug("Repositories: %s" % repositories) packager_addition = [ "--installroot", mounted_dir, "--disablerepo", "*", "--enablerepo", prefix + "*"] final_command = packager + packager_addition + repo_params + package_set try: run_cmd(final_command) except Exception as e: raise ConuException("Unable to install packages via command: {} (original exception {})".format(final_command, e)) insiderepopath = os.path.join( mounted_dir, "etc", "yum.repos.d", "{}.repo".format(prefix)) if not os.path.exists(os.path.dirname(insiderepopath)): os.makedirs(os.path.dirname(insiderepopath)) with open(insiderepopath, 'w') as f: f.write(repo_file_content) run_cmd(["umount", mounted_dir]) # add sleep before umount, to ensure, that kernel finish ops time.sleep(constants.DEFAULT_SLEEP) nspawnimage = NspawnImage(repository=name, location=tempimagefile, tag=tag) os.remove(tempimagefile) os.rmdir(mounted_dir) return nspawnimage
def __init__(self, path, mode=None, user_owner=None, group_owner=None, facl_rules=None, selinux_context=None, selinux_user=None, selinux_role=None, selinux_type=None, selinux_range=None): """ For more info on SELinux, please see `$ man chcon`. An exception will be thrown if selinux_context is specified and at least one of other SELinux fields. :param path: str, path to the directory we will operate on :param mode: int, octal representation of permission bits, e.g. 0o0400 :param user_owner: str or int, uid or username to own the directory :param group_owner: str or int, gid or group name to own the directory :param facl_rules: list of str, file ACLs to apply, e.g. "u:26:rwx" :param selinux_context: str, set directory to this SELinux context (this is the full context with all the field, example: "system_u:object_r:unlabeled_t:s0") :param selinux_user: str, user in the target security context, e.g. "system_u" :param selinux_role: str, role in the target security context, e.g. "object_r" :param selinux_type: str, type in the target security context, e.g. "unlabeled_t" :param selinux_range: str, range in the target security context, e.g. "s0" """ if selinux_context and any( [selinux_user, selinux_role, selinux_type, selinux_range]): raise ConuException( "Don't specify both selinux_context and some of its fields.") if any([ selinux_context, selinux_user, selinux_role, selinux_type, selinux_range ]): if is_selinux_disabled(): raise ConuException( "You are trying to apply SELinux labels, but SELinux is " "disabled on this system. Please enable it first.") # if set to True, it means the directory is created and set up self._initialized = False # TODO: if path is None, we could do mkdtemp self.path = path self.mode = mode self.selinux_context = selinux_context self.selinux_user = selinux_user self.selinux_role = selinux_role self.selinux_type = selinux_type self.selinux_range = selinux_range self.facl_rules = facl_rules # os.chown wants int if isinstance(user_owner, six.string_types): try: self.owner = pwd.getpwnam(user_owner)[2] except KeyError as ex: raise ConuException("User %r not found, error message: %r" % (user_owner, ex)) else: self.owner = user_owner if isinstance(group_owner, six.string_types): try: self.group = pwd.getpwnam(group_owner)[3] except KeyError as ex: raise ConuException("Group %r not found, error message: %r" % (group_owner, ex)) else: self.group = group_owner