示例#1
0
    def _pack_charm(self, parsed_args) -> List[pathlib.Path]:
        """Pack a charm."""
        emit.progress("Packing the charm.")
        # adapt arguments to use the build infrastructure
        build_args = Namespace(
            **{
                "debug": parsed_args.debug,
                "destructive_mode": parsed_args.destructive_mode,
                "from": self.config.project.dirpath,
                "entrypoint": parsed_args.entrypoint,
                "requirement": parsed_args.requirement,
                "shell": parsed_args.shell,
                "shell_after": parsed_args.shell_after,
                "bases_indices": parsed_args.bases_index,
                "force": parsed_args.force,
            })

        # mimic the "build" command
        validator = build.Validator(self.config)
        args = validator.process(build_args)
        emit.trace(f"Working arguments: {args}")
        builder = build.Builder(args, self.config)
        charms = builder.run(parsed_args.bases_index,
                             destructive_mode=build_args.destructive_mode)
        emit.message("Charms packed:")
        for charm in charms:
            emit.message(f"    {charm}")
示例#2
0
def get_parallel_build_count() -> int:
    """Obtain the number of concurrent jobs to execute.

    Try different strategies to obtain the number of parallel jobs
    to execute. If they fail, assume the safe default of 1. The
    number of concurrent build jobs can be limited by setting the
    environment variable ``SNAPCRAFT_MAX_PARALLEL_BUILD_COUNT``.

    :return: The number of parallel jobs for the current host.
    """
    try:
        build_count = len(os.sched_getaffinity(0))
        emit.debug(f"CPU count (from process affinity): {build_count}")
    except AttributeError:
        # Fall back to multiprocessing.cpu_count()...
        try:
            build_count = multiprocessing.cpu_count()
            emit.debug(f"CPU count (from multiprocessing): {build_count}")
        except NotImplementedError:
            emit.progress(
                "Unable to determine CPU count; disabling parallel builds",
                permanent=True,
            )
            build_count = 1

    try:
        max_count = int(
            os.environ.get("SNAPCRAFT_MAX_PARALLEL_BUILD_COUNT", ""))
        if max_count > 0:
            build_count = min(build_count, max_count)
    except ValueError:
        emit.debug("Invalid SNAPCRAFT_MAX_PARALLEL_BUILD_COUNT value")

    return build_count
示例#3
0
def _check_snap_dir(snap_dir_path: Path) -> None:
    """Verify if the given path only contains expected files."""
    unexpected_paths = set()
    for root, directories, files in os.walk(snap_dir_path):
        for entry in directories + files:
            path = Path(root, entry)
            relpath = path.relative_to(snap_dir_path)
            if not _snap_dir_path_expected(relpath):
                unexpected_paths.add(str(relpath))

    if unexpected_paths:
        snap_dir_relpath = snap_dir_path.relative_to(Path())
        emit.progress(
            "The {snap_dir!r} directory is meant specifically for snapcraft, but it contains\n"
            "the following non-snapcraft-related paths:"
            "\n- {unexpected_files}\n\n"
            "This is unsupported and may cause unexpected behavior. If you must store\n"
            "these files within the {snap_dir!r} directory, move them to {snap_dir_local!r}\n"
            "which is ignored by snapcraft.".format(
                snap_dir=str(snap_dir_relpath),
                snap_dir_local=str(snap_dir_relpath / "local"),
                unexpected_files="\n- ".join(sorted(unexpected_paths)),
            ),
            permanent=True,
        )
示例#4
0
    def extract_metadata(self) -> List[ExtractedMetadata]:
        """Obtain metadata information."""
        if self._adopt_info is None or self._adopt_info not in self._parse_info:
            return []

        dirs = ProjectDirs(work_dir=self._work_dir)
        part = Part(self._adopt_info, {}, project_dirs=dirs)
        locations = (
            part.part_src_dir,
            part.part_build_dir,
            part.part_install_dir,
        )
        metadata_list: List[ExtractedMetadata] = []

        for metadata_file in self._parse_info[self._adopt_info]:
            emit.trace(f"extract metadata: parse info from {metadata_file}")

            for location in locations:
                if pathlib.Path(location, metadata_file.lstrip("/")).is_file():
                    metadata = extract_metadata(metadata_file,
                                                workdir=str(location))
                    if metadata:
                        metadata_list.append(metadata)
                        break

                    emit.progress(
                        f"No metadata extracted from {metadata_file}",
                        permanent=True)

        return metadata_list
示例#5
0
    def _extract_file(self, image_tar: str, name: str, compress: bool = False) -> (str, int, str):
        """Extract a file from the tar and return its info. Optionally, gzip the content."""
        emit.progress(f"Extracting file {name!r} from local tar (compress={compress})")
        src_filehandler = image_tar.extractfile(name)
        mtime = image_tar.getmember(name).mtime

        hashing_temp_file = HashingTemporaryFile()
        if compress:
            # open the gzip file using the temporary file handler; use the original name and time
            # as 'filename' and 'mtime' correspondingly as those go to the gzip headers,
            # to ensure same final hash across different runs
            dst_filehandler = gzip.GzipFile(
                fileobj=hashing_temp_file,
                mode="wb",
                filename=os.path.basename(name),
                mtime=mtime,
            )
        else:
            dst_filehandler = hashing_temp_file
        try:
            while True:
                chunk = src_filehandler.read(CHUNK_SIZE)
                if not chunk:
                    break
                dst_filehandler.write(chunk)
        finally:
            dst_filehandler.close()
            # gzip does not automatically close the underlying file handler, let's do it manually
            hashing_temp_file.close()

        digest = "sha256:{}".format(hashing_temp_file.hexdigest)
        return hashing_temp_file.name, hashing_temp_file.total_length, digest
示例#6
0
def _generate_manifest(
    project: Project,
    *,
    lifecycle: PartsLifecycle,
    start_time: datetime,
    parsed_args: "argparse.Namespace",
) -> None:
    """Create and populate the manifest file."""
    emit.progress("Generating snap manifest...")
    image_information = parsed_args.manifest_image_information or "{}"

    parts = copy.deepcopy(project.parts)
    for name, part in parts.items():
        assets = lifecycle.get_part_pull_assets(part_name=name)
        if assets:
            part["stage-packages"] = assets.get("stage-packages", []) or []
        for key in ("stage", "prime", "stage-packages", "build-packages"):
            part.setdefault(key, [])

    manifest.write(
        project,
        lifecycle.prime_dir,
        arch=lifecycle.target_arch,
        parts=parts,
        start_time=start_time,
        image_information=image_information,
        primed_stage_packages=lifecycle.get_primed_stage_packages(),
    )
    emit.progress("Generated snap manifest", permanent=True)

    # Also copy the original snapcraft.yaml
    snap_project = get_snap_project()
    shutil.copy(snap_project.project_file, lifecycle.prime_dir / "snap")
示例#7
0
        def error_decorator(self, *args, **kwargs):
            """Handle craft-store error situations and login scenarios."""
            try:
                return method(self, *args, **kwargs)
            except craft_store.errors.CredentialsUnavailable:
                if os.getenv(ALTERNATE_AUTH_ENV_VAR):
                    raise RuntimeError(
                        "Charmcraft error: internal inconsistency detected "
                        "(CredentialsUnavailable error while having user provided credentials)."
                    )
                if not auto_login:
                    raise
                emit.progress("Credentials not found. Trying to log in...")
            except craft_store.errors.StoreServerError as error:
                if error.response.status_code == 401:
                    if os.getenv(ALTERNATE_AUTH_ENV_VAR):
                        raise CraftError(
                            "Provided credentials are no longer valid for Charmhub. "
                            "Regenerate them and try again."
                        )
                    if not auto_login:
                        raise CraftError("Existing credentials are no longer valid for Charmhub.")
                    emit.progress("Existing credentials no longer valid. Trying to log in...")
                    # Clear credentials before trying to login again
                    self.logout()
                else:
                    raise CraftError(str(error)) from error

            self.login()

            return method(self, *args, **kwargs)
示例#8
0
    def _pack_charm(self, parsed_args) -> List[pathlib.Path]:
        """Pack a charm."""
        emit.progress("Packing the charm.")
        # adapt arguments to use the build infrastructure
        build_args = Namespace(
            **{
                "debug": parsed_args.debug,
                "destructive_mode": parsed_args.destructive_mode,
                "from": self.config.project.dirpath,
                "entrypoint": parsed_args.entrypoint,
                "requirement": parsed_args.requirement,
                "shell": parsed_args.shell,
                "shell_after": parsed_args.shell_after,
                "bases_indices": parsed_args.bases_index,
                "force": parsed_args.force,
            })

        # mimic the "build" command
        validator = build.Validator(self.config)
        args = validator.process(build_args)
        emit.trace(f"Working arguments: {args}")
        builder = build.Builder(args, self.config)
        charms = builder.run(parsed_args.bases_index,
                             destructive_mode=build_args.destructive_mode)

        # avoid showing results when run inside a container (the outer charmcraft
        # is responsible of the final message to the user)
        if not env.is_charmcraft_running_in_managed_mode():
            emit.message("Charms packed:")
            for charm in charms:
                emit.message(f"    {charm}")
示例#9
0
    def _upload(self, endpoint, filepath, *, extra_fields=None):
        """Upload for all charms, bundles and resources (generic process)."""
        upload_id = self._client.push_file(filepath)
        payload = {"upload-id": upload_id}
        if extra_fields is not None:
            payload.update(extra_fields)
        response = self._client.request_urlpath_json("POST", endpoint, json=payload)
        status_url = response["status-url"]
        emit.progress(f"Upload {upload_id} started, got status url {status_url}")

        while True:
            response = self._client.request_urlpath_json("GET", status_url)
            emit.progress(f"Status checked: {response}")

            # as we're asking for a single upload_id, the response will always have only one item
            (revision,) = response["revisions"]
            status = revision["status"]

            if status in UPLOAD_ENDING_STATUSES:
                return Uploaded(
                    ok=UPLOAD_ENDING_STATUSES[status],
                    errors=_build_errors(revision),
                    status=status,
                    revision=revision["revision"],
                )

            # XXX Facundo 2020-06-30: Implement a slight backoff algorithm and fallout after
            # N attempts (which should be big, as per snapcraft experience). Issue: #79.
            time.sleep(POLL_DELAY)
示例#10
0
def _run_in_provider(project: Project, command_name: str,
                     parsed_args: "argparse.Namespace") -> None:
    """Pack image in provider instance."""
    emit.debug("Checking build provider availability")
    provider_name = "lxd" if parsed_args.use_lxd else None
    provider = providers.get_provider(provider_name)
    provider.ensure_provider_is_available()

    cmd = ["snapcraft", command_name]

    if hasattr(parsed_args, "parts"):
        cmd.extend(parsed_args.parts)

    if getattr(parsed_args, "output", None):
        cmd.extend(["--output", parsed_args.output])

    if emit.get_mode() == EmitterMode.VERBOSE:
        cmd.append("--verbose")
    elif emit.get_mode() == EmitterMode.QUIET:
        cmd.append("--quiet")
    elif emit.get_mode() == EmitterMode.DEBUG:
        cmd.append("--verbosity=debug")
    elif emit.get_mode() == EmitterMode.TRACE:
        cmd.append("--verbosity=trace")

    if parsed_args.debug:
        cmd.append("--debug")
    if getattr(parsed_args, "shell", False):
        cmd.append("--shell")
    if getattr(parsed_args, "shell_after", False):
        cmd.append("--shell-after")

    if getattr(parsed_args, "enable_manifest", False):
        cmd.append("--enable-manifest")
    build_information = getattr(parsed_args, "manifest_build_information",
                                None)
    if build_information:
        cmd.append("--manifest-build-information")
        cmd.append(build_information)

    output_dir = utils.get_managed_environment_project_path()

    emit.progress("Launching instance...")
    with provider.launched_environment(
            project_name=project.name,
            project_path=Path().absolute(),
            base=project.get_effective_base(),
            bind_ssh=parsed_args.bind_ssh,
            build_on=get_host_architecture(),
            build_for=get_host_architecture(),
    ) as instance:
        try:
            with emit.pause():
                instance.execute_run(cmd, check=True, cwd=output_dir)
            capture_logs_from_instance(instance)
        except subprocess.CalledProcessError as err:
            capture_logs_from_instance(instance)
            raise providers.ProviderError(
                f"Failed to execute {command_name} in instance.") from err
示例#11
0
def _launch_shell(*, cwd: Optional[pathlib.Path] = None) -> None:
    """Launch a user shell for debugging environment.

    :param cwd: Working directory to start user in.
    """
    emit.progress("Launching shell on build environment...", permanent=True)
    with emit.pause():
        subprocess.run(["bash"], check=False, cwd=cwd)
示例#12
0
    def is_blob_already_uploaded(self, reference):
        """Verify if the blob is already uploaded, using a generic reference.

        If yes, return its digest.
        """
        emit.progress("Checking if the blob is already uploaded")
        url = self._get_url("blobs/{}".format(reference))
        return self._is_item_already_uploaded(url)
示例#13
0
文件: clean.py 项目: sed-i/charmcraft
    def run(self, parsed_args):
        """Run the command."""
        project_path = self.config.project.dirpath
        metadata = parse_metadata_yaml(project_path)
        emit.progress(f"Cleaning project {metadata.name!r}.")

        provider = get_provider()
        provider.clean_project_environments(charm_name=metadata.name, project_path=project_path)
        emit.message(f"Cleaned project {metadata.name!r}.")
示例#14
0
def pack_snap(
    directory: Path,
    *,
    output: Optional[str],
    compression: Optional[str] = None,
    name: Optional[str] = None,
    version: Optional[str] = None,
    target_arch: Optional[str] = None,
) -> str:
    """Pack snap contents with `snap pack`.

    `output` may either be a directory, a file path, or just a file name.
      - directory: write snap to directory with default snap name
      - file path: write snap to specified directory with specified snap name
      - file name: write snap to cwd with specified snap name

    If name, version, and target architecture are not specified, then snap
    will use its default naming convention.

    :param directory: Directory to pack.
    :param output: Snap file name or directory.
    :param compression: Compression type to use, None for defaults.
    :param name: Name of snap project.
    :param version: Version of snap project.
    :param target_arch: Target architecture the snap project is built to.
    """
    emit.debug(f"pack_snap: output={output!r}, compression={compression!r}")

    # TODO remove workaround once LP: #1950465 is fixed
    _verify_snap(directory)

    # create command formatted as `snap pack <options> <snap-dir> <output-dir>`
    command: List[Union[str, Path]] = ["snap", "pack"]
    output_file = _get_filename(output, name, version, target_arch)
    if output_file is not None:
        command.extend(["--filename", output_file])
    if compression is not None:
        command.extend(["--compression", compression])
    command.append(directory)
    command.append(_get_directory(output))

    emit.progress("Creating snap package...")
    emit.debug(f"Pack command: {command}")
    try:
        proc = subprocess.run(command,
                              capture_output=True,
                              check=True,
                              universal_newlines=True)
    except subprocess.CalledProcessError as err:
        msg = f"Cannot pack snap file: {err!s}"
        if err.stderr:
            msg += f" ({err.stderr.strip()!s})"
        raise errors.SnapcraftError(msg)

    snap_filename = Path(str(proc.stdout).partition(":")[2].strip()).name
    return snap_filename
示例#15
0
    def _upload_blob(self, filepath: str, size: int, digest: str) -> None:
        """Upload the blob (if necessary)."""
        # if it's already uploaded, nothing to do
        if self.registry.is_blob_already_uploaded(digest):
            emit.progress("Blob was already uploaded")
        else:
            self.registry.upload_blob(filepath, size, digest)

        # finally remove the temp filepath
        os.unlink(filepath)
示例#16
0
 def upload_manifest(self, manifest_data, reference):
     """Upload a manifest."""
     url = self._get_url("manifests/{}".format(reference))
     headers = {
         "Content-Type": MANIFEST_V2_MIMETYPE,
     }
     emit.progress(f"Uploading manifest with reference {reference}")
     response = self._hit("PUT", url, headers=headers, data=manifest_data.encode("utf8"))
     assert_response_ok(response, expected_status=201)
     emit.progress("Manifest uploaded OK")
示例#17
0
    def build_charm(self) -> None:
        """Build the charm."""
        emit.progress(f"Building charm in {str(self.buildpath)!r}")

        if self.buildpath.exists():
            shutil.rmtree(str(self.buildpath))
        self.buildpath.mkdir()

        linked_entrypoint = self.handle_generic_paths()
        self.handle_dispatcher(linked_entrypoint)
        self.handle_dependencies()
示例#18
0
    def _is_valid_elf(self, resolved_path: Path) -> bool:
        if not resolved_path.exists() or not ElfFile.is_elf(resolved_path):
            return False

        try:
            elf_file = ElfFile(path=resolved_path)
        except errors.CorruptedElfFile as error:
            # Warn if the ELF file seems corrupted.
            emit.progress(str(error), permanent=True)
            return False

        return elf_file.arch_tuple == self.arch_tuple
示例#19
0
    def build_charm(self, bases_config: BasesConfiguration) -> str:
        """Build the charm.

        :param bases_config: Bases configuration to use for build.

        :returns: File name of charm.

        :raises CraftError: on lifecycle exception.
        :raises RuntimeError: on unexpected lifecycle exception.
        """
        if env.is_charmcraft_running_in_managed_mode():
            work_dir = env.get_managed_environment_home_path()
        else:
            work_dir = self.buildpath

        emit.progress(f"Building charm in {str(work_dir)!r}")

        if self._special_charm_part:
            # all current deprecated arguments set charm plugin parameters
            self._handle_deprecated_cli_arguments()

            # add charm files to the prime filter
            self._set_prime_filter()

            # set source if empty or not declared in charm part
            if not self._special_charm_part.get("source"):
                self._special_charm_part["source"] = str(self.charmdir)

        # run the parts lifecycle
        emit.trace(f"Parts definition: {self._parts}")
        lifecycle = parts.PartsLifecycle(
            self._parts,
            work_dir=work_dir,
            project_dir=self.charmdir,
            project_name=self.metadata.name,
            ignore_local_sources=["*.charm"],
        )
        lifecycle.run(Step.PRIME)

        # run linters and show the results
        linting_results = linters.analyze(self.config, lifecycle.prime_dir)
        self.show_linting_results(linting_results)

        create_manifest(
            lifecycle.prime_dir,
            self.config.project.started_at,
            bases_config,
            linting_results,
        )

        zipname = self.handle_package(lifecycle.prime_dir, bases_config)
        emit.message(f"Created '{zipname}'.", intermediate=True)
        return zipname
示例#20
0
    def __init__(
        self,
        all_parts: Dict[str, Any],
        *,
        work_dir: pathlib.Path,
        assets_dir: pathlib.Path,
        base: str,
        package_repositories: List[Dict[str, Any]],
        parallel_build_count: int,
        part_names: Optional[List[str]],
        adopt_info: Optional[str],
        parse_info: Dict[str, List[str]],
        project_name: str,
        project_vars: Dict[str, str],
        extra_build_snaps: Optional[List[str]] = None,
        target_arch: str,
    ):
        self._work_dir = work_dir
        self._assets_dir = assets_dir
        self._package_repositories = package_repositories
        self._part_names = part_names
        self._adopt_info = adopt_info
        self._parse_info = parse_info
        self._all_part_names = [*all_parts]

        emit.progress("Initializing parts lifecycle")

        # set the cache dir for parts package management
        cache_dir = BaseDirectory.save_cache_path("snapcraft")

        if target_arch == "all":
            target_arch = get_host_architecture()

        platform_arch = convert_architecture_deb_to_platform(target_arch)

        try:
            self._lcm = craft_parts.LifecycleManager(
                {"parts": all_parts},
                application_name="snapcraft",
                work_dir=work_dir,
                cache_dir=cache_dir,
                arch=platform_arch,
                base=base,
                ignore_local_sources=["*.snap"],
                extra_build_snaps=extra_build_snaps,
                parallel_build_count=parallel_build_count,
                project_name=project_name,
                project_vars_part_name=adopt_info,
                project_vars=project_vars,
            )
        except craft_parts.PartsError as err:
            raise errors.PartsLifecycleError(str(err)) from err
示例#21
0
    def clean(self, *, part_names: Optional[List[str]] = None) -> None:
        """Remove lifecycle artifacts.

        :param part_names: The names of the parts to clean. If not
            specified, all parts will be cleaned.
        """
        if part_names:
            message = "Cleaning parts: " + ", ".join(part_names)
        else:
            message = "Cleaning all parts"

        emit.progress(message)
        self._lcm.clean(part_names=part_names)
示例#22
0
def _finalize_icon(
    icon: Optional[str], *, assets_dir: Path, gui_dir: Path, prime_dir: Path
) -> Optional[Path]:
    """Ensure sure icon is properly configured and installed.

    Fetch from a remote URL, if required, and place in the meta/gui
    directory.
    """
    emit.debug(f"finalize icon: {icon!r}")

    # Nothing to do if no icon is configured, search for existing icon.
    if icon is None:
        return _find_icon_file(assets_dir)

    # Extracted appstream icon paths will either:
    # (1) point to a file relative to prime
    # (2) point to a remote http(s) url
    #
    # The 'icon' specified in the snapcraft.yaml has the same
    # constraint as (2) and would have already been validated
    # as existing by the schema.  So we can treat it the same
    # at this point, regardless of the source of the icon.
    parsed_url = urllib.parse.urlparse(icon)
    parsed_path = Path(parsed_url.path)
    icon_ext = parsed_path.suffix[1:]
    target_icon_path = Path(gui_dir, f"icon.{icon_ext}")

    target_icon_path.parent.mkdir(parents=True, exist_ok=True)
    if parsed_url.scheme in ["http", "https"]:
        # Remote - fetch URL and write to target.
        emit.progress(f"Fetching icon from {icon!r}")
        icon_data = requests.get(icon).content
        target_icon_path.write_bytes(icon_data)
    elif parsed_url.scheme == "":
        source_path = Path(
            prime_dir,
            parsed_path.relative_to("/") if parsed_path.is_absolute() else parsed_path,
        )
        if source_path.exists():
            # Local with path relative to prime.
            _copy_file(source_path, target_icon_path)
        elif parsed_path.exists():
            # Local with path relative to project.
            _copy_file(parsed_path, target_icon_path)
        else:
            # No icon found, fall back to searching for existing icon.
            return _find_icon_file(assets_dir)
    else:
        raise RuntimeError(f"Unexpected icon path: {parsed_url!r}")

    return target_icon_path
示例#23
0
def process_version(version: Optional[str]) -> str:
    """Handle special version strings."""
    if version is None:
        raise ValueError("version cannot be None")

    new_version = version
    if version == "git":
        emit.progress("Determining the version from the project repo (version: git).")
        new_version = GitSource.generate_version()

    if new_version != version:
        emit.progress(f"Version has been set to {new_version!r}", permanent=True)

    return new_version
示例#24
0
    def handle_package(self, prime_dir, bases_config: BasesConfiguration):
        """Handle the final package creation."""
        emit.progress("Creating the package itself")
        zipname = format_charm_file_name(self.metadata.name, bases_config)
        zipfh = zipfile.ZipFile(zipname, "w", zipfile.ZIP_DEFLATED)
        for dirpath, dirnames, filenames in os.walk(prime_dir,
                                                    followlinks=True):
            dirpath = pathlib.Path(dirpath)
            for filename in filenames:
                filepath = dirpath / filename
                zipfh.write(str(filepath),
                            str(filepath.relative_to(prime_dir)))

        zipfh.close()
        return zipname
示例#25
0
    def run(self, parsed_args):
        # dest does not work when filling the parser so getattr instead
        snap_name = getattr(parsed_args, "snap-name")

        if parsed_args.private:
            emit.progress(
                _MESSAGE_REGISTER_PRIVATE.format(snap_name),
                permanent=True,
            )
        if parsed_args.yes or utils.confirm_with_user(
                _MESSAGE_REGISTER_CONFIRM.format(snap_name)):
            store.StoreClientCLI().register(snap_name,
                                            is_private=parsed_args.private,
                                            store_id=parsed_args.store_id)
            emit.message(_MESSAGE_REGISTER_SUCCESS.format(snap_name))
        else:
            emit.message(_MESSAGE_REGISTER_NO.format(snap_name))
示例#26
0
文件: build.py 项目: sed-i/charmcraft
    def plan(
            self, *, bases_indices: Optional[List[int]],
            destructive_mode: bool, managed_mode: bool
    ) -> List[Tuple[BasesConfiguration, Base, int, int]]:
        """Determine the build plan based on user inputs and host environment.

        Provide a list of bases that are buildable and scoped according to user
        configuration. Provide all relevant details including the applicable
        bases configuration and the indices of the entries to build for.

        :returns: List of Tuples (bases_config, build_on, bases_index, build_on_index).
        """
        build_plan: List[Tuple[BasesConfiguration, Base, int, int]] = []

        for bases_index, bases_config in enumerate(self.config.bases):
            if bases_indices and bases_index not in bases_indices:
                emit.trace(
                    f"Skipping 'bases[{bases_index:d}]' due to --base-index usage."
                )
                continue

            for build_on_index, build_on in enumerate(bases_config.build_on):
                if managed_mode or destructive_mode:
                    matches, reason = check_if_base_matches_host(build_on)
                else:
                    matches, reason = self.provider.is_base_available(build_on)

                if matches:
                    emit.trace(
                        f"Building for 'bases[{bases_index:d}]' "
                        f"as host matches 'build-on[{build_on_index:d}]'.", )
                    build_plan.append(
                        (bases_config, build_on, bases_index, build_on_index))
                    break
                else:
                    emit.progress(
                        f"Skipping 'bases[{bases_index:d}].build-on[{build_on_index:d}]': "
                        f"{reason}.", )
            else:
                emit.message(
                    "No suitable 'build-on' environment found "
                    f"in 'bases[{bases_index:d}]' configuration.",
                    intermediate=True,
                )

        return build_plan
示例#27
0
    def run(self, parsed_args):
        snap_channel_map = store.StoreClientCLI().get_channel_map(
            snap_name=parsed_args.name
        )

        existing_architectures = snap_channel_map.get_existing_architectures()
        if not snap_channel_map.channel_map:
            emit.message("This snap has no released revisions")
            return

        architectures = existing_architectures
        if parsed_args.arch:
            architectures = set(parsed_args.arch)
            for architecture in architectures.copy():
                if architecture not in existing_architectures:
                    emit.progress(f"No revisions for architecture {architecture!r}")
                    architectures.remove(architecture)

            # If we have no revisions for any of the architectures requested, there's
            # nothing to do here.
            if not architectures:
                return

        tracks: List[str] = []
        if parsed_args.track:
            tracks = cast(list, parsed_args.track)
            existing_tracks = {
                s.track for s in snap_channel_map.snap.channels if s.track in tracks
            }
            for track in set(tracks) - existing_tracks:
                emit.progress(f"No revisions for track {track!r}")
            tracks = list(existing_tracks)

            # If we have no revisions in any of the tracks requested, there's
            # nothing to do here.
            if not tracks:
                return

        emit.message(
            get_tabulated_channel_map(
                snap_channel_map,
                architectures=list(architectures),
                tracks=tracks,
            )
        )
示例#28
0
    def error_decorator(self, *args, **kwargs):
        """Handle craft-store error situations and login scenarios."""
        try:
            return method(self, *args, **kwargs)
        except craft_store.errors.NotLoggedIn:
            emit.progress("Credentials not found. Trying to log in...")
        except craft_store.errors.StoreServerError as error:
            if error.response.status_code == 401:
                emit.progress("Existing credentials no longer valid. Trying to log in...")
            else:
                raise CommandError(str(error)) from error
        except craft_store.errors.CraftStoreError as error:
            raise CommandError(
                f"Server error while communicating to the Store: {error!s}"
            ) from error

        self.login()

        return method(self, *args, **kwargs)
示例#29
0
    def run(self, parsed_args):
        if parsed_args.experimental_login:
            raise ArgumentParsingError(
                "--experimental-login no longer supported. "
                f"Set {store.constants.ENVIRONMENT_STORE_AUTH}=candid instead",
            )

        if parsed_args.login_with:
            config_content = _read_config(parsed_args.login_with)
            emit.progress(
                "--with is no longer supported, export the auth to the environment "
                f"variable {store.constants.ENVIRONMENT_STORE_CREDENTIALS!r} instead",
                permanent=True,
            )
            store.LegacyUbuntuOne.store_credentials(config_content)
        else:
            store.StoreClientCLI().login()

        emit.message("Login successful")
示例#30
0
    def handle_dependencies(self):
        """Handle from-directory and virtualenv dependencies."""
        emit.trace("Handling dependencies")
        if not (self.requirement_paths or self.binary_python_packages or self.python_packages):
            emit.trace("No dependencies to handle")
            return

        staging_venv_dir = self.charmdir / STAGING_VENV_DIRNAME
        hash_file = self.charmdir / DEPENDENCIES_HASH_FILENAME

        # find out if current dependencies are the same than the last run.
        current_deps_hash = self._calculate_dependencies_hash()
        emit.trace(f"Current dependencies hash: {current_deps_hash!r}")
        if not staging_venv_dir.exists():
            emit.trace("Dependencies directory not found")
            same_dependencies = False
        elif hash_file.exists():
            try:
                previous_deps_hash = hash_file.read_text(encoding="utf8")
            except Exception as exc:
                emit.trace(f"Problems reading the dependencies hash file: {exc}")
                same_dependencies = False
            else:
                emit.trace(f"Previous dependencies hash: {previous_deps_hash!r}")
                same_dependencies = previous_deps_hash == current_deps_hash
        else:
            emit.trace("Dependencies hash file not found")
            same_dependencies = False

        if same_dependencies:
            emit.trace("Reusing installed dependencies, they are equal to last run ones")
        else:
            emit.progress("Installing dependencies")
            self._install_dependencies(staging_venv_dir)

            # save the hash file after all successful installations
            hash_file.write_text(current_deps_hash, encoding="utf8")

        # always copy the virtualvenv site-packages directory to /venv in charm
        basedir = pathlib.Path(STAGING_VENV_DIRNAME)
        site_packages_dir = _find_venv_site_packages(basedir)
        shutil.copytree(site_packages_dir, self.buildpath / VENV_DIRNAME)