예제 #1
0
    def run(self, parsed_args):
        extension_presentation: Dict[str, ExtensionModel] = {}

        # New extensions.
        for extension_name in extensions.registry.get_extension_names():
            extension_class = extensions.registry.get_extension_class(
                extension_name)
            extension_bases = list(extension_class.get_supported_bases())
            extension_presentation[extension_name] = ExtensionModel(
                name=extension_name, bases=extension_bases)

        # Extensions from snapcraft_legacy.
        for extension_name in supported_extension_names():
            extension_class = find_extension(extension_name)
            extension_name = extension_name.replace("_", "-")
            extension_bases = list(extension_class.get_supported_bases())
            if extension_name in extension_presentation:
                extension_presentation[extension_name].bases += extension_bases
            else:
                extension_presentation[extension_name] = ExtensionModel(
                    name=extension_name, bases=extension_bases)

        printable_extensions = sorted(
            [v.marshal() for v in extension_presentation.values()],
            key=lambda d: d["Extension name"],
        )
        emit.message(tabulate.tabulate(printable_extensions, headers="keys"))
예제 #2
0
    def run(self, parsed_args):
        snap_file = pathlib.Path(parsed_args.snap_file)
        if not snap_file.exists() or not snap_file.is_file():
            raise ArgumentParsingError(
                f"{str(snap_file)!r} is not a valid file")

        channels: Optional[List[str]] = None
        if parsed_args.channels:
            channels = parsed_args.channels.split(",")

        client = store.StoreClientCLI()

        snap_yaml = get_data_from_snap_file(snap_file)
        snap_name = snap_yaml["name"]
        built_at = snap_yaml.get("snapcraft-started-at")

        client.verify_upload(snap_name=snap_name)

        upload_id = client.store_client.upload_file(
            filepath=snap_file, monitor_callback=create_callback)

        revision = client.notify_upload(
            snap_name=snap_name,
            upload_id=upload_id,
            built_at=built_at,
            channels=channels,
            snap_file_size=snap_file.stat().st_size,
        )

        message = f"Revision {revision!r} created for {snap_name!r}"
        if channels:
            message += f" and released to {utils.humanize_list(channels, 'and')}"
        emit.message(message)
예제 #3
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}")
예제 #4
0
    def _parse_and_reformat_section(self, *, section, icon_path: Optional[str] = None):
        if "Exec" not in self._parser[section]:
            raise errors.DesktopFileError(self._filename, "missing 'Exec' key")

        self._parse_and_reformat_section_exec(section)

        if "Icon" in self._parser[section]:
            icon = self._parser[section]["Icon"]

            if icon_path is not None:
                icon = icon_path

            # Strip any leading slash.
            icon = icon[1:] if icon.startswith("/") else icon

            # Strip any leading ${SNAP}.
            icon = icon[8:] if icon.startswith("${SNAP}") else icon

            # With everything stripped, check to see if the icon is there.
            # if it is, add "${SNAP}" back and set the icon
            if (self._prime_dir / icon).is_file():
                self._parser[section]["Icon"] = os.path.join("${SNAP}", icon)
            else:
                emit.message(
                    f"Icon {icon!r} specified in desktop file {self._filename!r} "
                    f"not found in prime directory."
                )
예제 #5
0
파일: build.py 프로젝트: sed-i/charmcraft
    def show_linting_results(self, linting_results):
        """Manage the linters results, show some in different conditions, decide if continue."""
        attribute_results = []
        lint_results_by_outcome = {}
        for result in linting_results:
            if result.result == linters.IGNORED:
                continue
            if result.check_type == linters.CheckType.attribute:
                attribute_results.append(result)
            else:
                lint_results_by_outcome.setdefault(result.result,
                                                   []).append(result)

        # show attribute results
        for result in attribute_results:
            emit.trace(
                f"Check result: {result.name} [{result.check_type}] {result.result} "
                f"({result.text}; see more at {result.url}).", )

        # show warnings (if any), then errors (if any)
        template = "- {0.name}: {0.text} ({0.url})"
        if linters.WARNINGS in lint_results_by_outcome:
            emit.message("Lint Warnings:", intermediate=True)
            for result in lint_results_by_outcome[linters.WARNINGS]:
                emit.message(template.format(result), intermediate=True)
        if linters.ERRORS in lint_results_by_outcome:
            emit.message("Lint Errors:", intermediate=True)
            for result in lint_results_by_outcome[linters.ERRORS]:
                emit.message(template.format(result), intermediate=True)
            if self.force_packing:
                emit.message("Packing anyway as requested.", intermediate=True)
            else:
                raise CommandError(
                    "Aborting due to lint errors (use --force to override).",
                    retcode=2)
예제 #6
0
    def request(self, *args, **kwargs) -> requests.Response:
        """Request using the BaseClient and wrap responses that require action.

        Actionable items are those that could prompt a login or registration.
        """
        try:
            return self.store_client.request(*args, **kwargs)
        except craft_store.errors.StoreServerError as store_error:
            if (store_error.response.status_code == requests.codes.unauthorized  # pylint: disable=no-member
                ):
                if os.getenv(constants.ENVIRONMENT_STORE_CREDENTIALS):
                    raise errors.SnapcraftError(
                        "Provided credentials are no longer valid for the Snap Store.",
                        resolution="Regenerate them and try again.",
                    ) from store_error

                emit.message("You are required to re-login before continuing")
                self.store_client.logout()
            else:
                raise
        except craft_store.errors.CredentialsUnavailable:
            emit.message("You are required to login before continuing")

        self.login()
        return self.store_client.request(*args, **kwargs)
예제 #7
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}")
예제 #8
0
 def run(self, parsed_args):
     """Run the command."""
     if parsed_args.directory:
         snap_filename = pack.pack_snap(parsed_args.directory,
                                        output=parsed_args.output)
         emit.message(f"Created snap package {snap_filename}")
     else:
         super().run(parsed_args)
예제 #9
0
def _prompt_login() -> Tuple[str, str]:
    emit.message("Enter your Ubuntu One e-mail address and password.")
    emit.message(
        "If you do not have an Ubuntu One account, you can create one "
        "at https://snapcraft.io/account", )
    email = utils.prompt("Email: ")
    password = utils.prompt("Password: ", hide=True)

    return (email, password)
예제 #10
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}.")
예제 #11
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",
            )

        kwargs: Dict[str, Union[str, int]] = {}
        if parsed_args.snaps:
            kwargs["packages"] = parsed_args.snaps.split(",")
        if parsed_args.channels:
            kwargs["channels"] = parsed_args.channels.split(",")
        if parsed_args.acls:
            kwargs["acls"] = parsed_args.acls.split(",")
        if parsed_args.expires is not None:
            for date_format in _VALID_DATE_FORMATS:
                with contextlib.suppress(ValueError):
                    expiry_date = datetime.strptime(parsed_args.expires,
                                                    date_format)
                    break
            else:
                valid_formats = utils.humanize_list(_VALID_DATE_FORMATS, "or")
                raise ArgumentParsingError(
                    f"The expiry follow an ISO 8601 format ({valid_formats})")

            kwargs["ttl"] = int((expiry_date - datetime.now()).total_seconds())

        credentials = store.StoreClientCLI(ephemeral=True).login(**kwargs)

        # Support a login_file of '-', which indicates a desire to print to stdout
        if parsed_args.login_file.strip() == "-":
            message = f"Exported login credentials:\n{credentials}"
        else:
            # This is sensitive-- it should only be accessible by the owner
            private_open = functools.partial(os.open, mode=0o600)

            with open(parsed_args.login_file,
                      "w",
                      opener=private_open,
                      encoding="utf-8") as login_fd:
                print(credentials, file=login_fd, end="")

            # Now that the file has been written, we can just make it
            # owner-readable
            os.chmod(parsed_args.login_file, stat.S_IRUSR)

            message = f"Exported login credentials to {parsed_args.login_file!r}"

        message += "\n\nThese credentials must be used on Snapcraft 7.0 or greater."
        if os.getenv(store.constants.ENVIRONMENT_STORE_AUTH) == "candid":
            message += (
                f"\nSet '{store.constants.ENVIRONMENT_STORE_AUTH}=candid' for "
                "these credentials to work.")
        emit.message(message)
예제 #12
0
 def run(self, parsed_args):
     snap_project = get_snap_project()
     yaml_data = process_yaml(snap_project.project_file)
     expanded_yaml_data = extensions.apply_extensions(
         yaml_data,
         arch=get_host_architecture(),
         target_arch=get_host_architecture(),
     )
     Project.unmarshal(expanded_yaml_data)
     emit.message(
         yaml.safe_dump(expanded_yaml_data, indent=4, sort_keys=False))
예제 #13
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
예제 #14
0
    def run(self, parsed_args):
        channels = parsed_args.channels.split(",")

        store.StoreClientCLI().release(
            snap_name=parsed_args.name,
            revision=parsed_args.revision,
            channels=channels,
            progressive_percentage=parsed_args.progressive_percentage,
        )

        humanized_channels = utils.humanize_list(channels, conjunction="and")
        emit.message(f"Released {parsed_args.name!r} "
                     f"revision {parsed_args.revision!r} "
                     f"to channels: {humanized_channels}")
예제 #15
0
def _run_dispatcher(dispatcher: craft_cli.Dispatcher) -> None:
    global_args = dispatcher.pre_parse_args(sys.argv[1:])
    if global_args.get("version"):
        emit.message(f"snapcraft {__version__}")
    else:
        if global_args.get("trace"):
            emit.message(
                "Options -t and --trace are deprecated, use --verbosity=debug instead."
            )
            emit.set_mode(EmitterMode.DEBUG)

        dispatcher.load_command(None)
        dispatcher.run()
    emit.ended_ok()
예제 #16
0
def notify_deprecation(deprecation_id):
    """Present proper messages to the user for the indicated deprecation id.

    Prevent issuing duplicate warnings to the user by ignoring notifications if:
    - running in managed-mode
    - already issued by running process
    """
    if is_charmcraft_running_in_managed_mode() or deprecation_id in _ALREADY_NOTIFIED:
        return

    message = _DEPRECATION_MESSAGES[deprecation_id]
    emit.message(f"DEPRECATED: {message}", intermediate=True)
    url = _DEPRECATION_URL_FMT.format(deprecation_id=deprecation_id)
    emit.message(f"See {url} for more information.", intermediate=True)
    _ALREADY_NOTIFIED.add(deprecation_id)
예제 #17
0
def get_build_plan(yaml_data: Dict[str, Any],
                   parsed_args: "argparse.Namespace") -> List[Tuple[str, str]]:
    """Get a list of all build_on->build_for architectures from the project file.

    Additionally, check for the command line argument `--build-for <architecture>`
    When defined, the build plan will only contain builds where `build-for`
    matches `SNAPCRAFT_BUILD_FOR`.
    Note: `--build-for` defaults to the environmental variable `SNAPCRAFT_BUILD_FOR`.

    :param yaml_data: The project YAML data.
    :param parsed_args: snapcraft's argument namespace

    :return: List of tuples of every valid build-on->build-for combination.
    """
    archs = ArchitectureProject.unmarshal(yaml_data).architectures

    host_arch = get_host_architecture()
    build_plan: List[Tuple[str, str]] = []

    # `isinstance()` calls are for mypy type checking and should not change logic
    for arch in [arch for arch in archs if isinstance(arch, Architecture)]:
        for build_on in arch.build_on:
            if build_on in host_arch and isinstance(arch.build_for, list):
                build_plan.append((host_arch, arch.build_for[0]))
            else:
                emit.verbose(
                    f"Skipping build-on: {build_on} build-for: {arch.build_for}"
                    f" because build-on doesn't match host arch: {host_arch}")

    # filter out builds not matching argument `--build_for` or env `SNAPCRAFT_BUILD_FOR`
    build_for_arg = parsed_args.build_for
    if build_for_arg is not None:
        build_plan = [
            build for build in build_plan if build[1] == build_for_arg
        ]

    if len(build_plan) == 0:
        emit.message("Could not make build plan:"
                     " build-on architectures in snapcraft.yaml"
                     f" does not match host architecture ({host_arch}).")
    else:
        log_output = "Created build plan:"
        for build in build_plan:
            log_output += f"\n  build-on: {build[0]} build-for: {build[1]}"
        emit.trace(log_output)

    return build_plan
예제 #18
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
예제 #19
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))
예제 #20
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,
            )
        )
예제 #21
0
def _clean_provider(project: Project,
                    parsed_args: "argparse.Namespace") -> None:
    """Clean the provider environment.

    :param project: The project to clean.
    """
    emit.debug("Clean build provider")
    provider_name = "lxd" if parsed_args.use_lxd else None
    provider = providers.get_provider(provider_name)
    instance_names = provider.clean_project_environments(
        project_name=project.name,
        project_path=Path().absolute(),
        build_on=get_host_architecture(),
        build_for=get_host_architecture(),
    )
    if instance_names:
        emit.message(f"Removed instance: {', '.join(instance_names)}")
    else:
        emit.message("No instances to remove")
예제 #22
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")
예제 #23
0
def get_elf_files(root: str, file_list: Set[str]) -> FrozenSet[ElfFile]:
    """Return a frozenset of ELF files from file_list prepended with root.

    :param str root: the root directory from where the file_list is generated.
    :param file_list: a list of file in root.
    :returns: a frozentset of ElfFile objects.
    """
    elf_files: Set[ElfFile] = set()

    for part_file in file_list:
        # Filter out object (*.o) files-- we only care about binaries.
        if part_file.endswith(".o"):
            continue

        # No need to crawl links-- the original should be here, too.
        path = Path(root, part_file)
        if os.path.islink(path):
            emit.debug(f"Skipped link {path!r} while finding dependencies")
            continue

        # Ignore if file does not have ELF header.
        if not ElfFile.is_elf(path):
            continue

        try:
            elf_file = ElfFile(path=path)
        except elftools.common.exceptions.ELFError:
            # Ignore invalid ELF files.
            continue
        except errors.CorruptedElfFile as exception:
            # Log if the ELF file seems corrupted
            emit.message(str(exception))
            continue

        # If ELF has dynamic symbols, add it.
        if elf_file.needed:
            elf_files.add(elf_file)

    return frozenset(elf_files)
예제 #24
0
    def run(self, parsed_args):
        client = store.StoreClientCLI()

        # Account info request to retrieve the snap-id
        account_info = client.get_account_info()
        try:
            snap_id = account_info["snaps"][store.constants.DEFAULT_SERIES][
                parsed_args.name]["snap-id"]
        except KeyError as key_error:
            emit.debug(f"{key_error!r} no found in {account_info!r}")
            raise errors.SnapcraftError(
                f"{parsed_args.name!r} not found or not owned by this account"
            ) from key_error

        client.close(
            snap_id=snap_id,
            channel=parsed_args.channel,
        )

        emit.message(
            f"Channel {parsed_args.channel!r} for {parsed_args.name!r} is now closed"
        )
예제 #25
0
    def run(self, parsed_args):
        whoami = store.StoreClientCLI().store_client.whoami()

        if whoami.get("permissions"):
            permissions = ", ".join(whoami["permissions"])
        else:
            permissions = "no restrictions"

        if whoami.get("channels"):
            channels = ", ".join(whoami["channels"])
        else:
            channels = "no restrictions"

        account = whoami["account"]
        message = textwrap.dedent(f"""\
            email: {account["email"]}
            username: {account["username"]}
            id: {account["id"]}
            permissions: {permissions}
            channels: {channels}
            expires: {whoami["expires"]}Z""")

        emit.message(message)
예제 #26
0
    def run(self, parsed_args):
        snap_channel_map = store.StoreClientCLI().get_channel_map(
            snap_name=parsed_args.name
        )

        # Iterate over the entries, replace None with - for consistent presentation
        track_table: List[List[str]] = [
            [
                track.name,
                track.status,
                track.creation_date if track.creation_date else "-",
                track.version_pattern if track.version_pattern else "-",
            ]
            for track in snap_channel_map.snap.tracks
        ]

        emit.message(
            tabulate(
                # Sort by "creation-date".
                sorted(track_table, key=operator.itemgetter(2)),
                headers=["Name", "Status", "Creation-Date", "Version-Pattern"],
                tablefmt="plain",
            )
        )
예제 #27
0
    def run(self, parsed_args):
        account_info = store.StoreClientCLI().get_account_info()

        snaps = [
            (
                name,
                info["since"],
                "private" if info["private"] else "public",
                "-",
            ) for name, info in account_info["snaps"].get(
                store.constants.DEFAULT_SERIES, {}).items()
            # Presenting only approved snap registrations, which means name
            # disputes will be displayed/sorted some other way.
            if info["status"] == "Approved"
        ]
        if not snaps:
            emit.message("No registered snaps")
        else:
            tabulated_snaps = tabulate(
                sorted(snaps, key=operator.itemgetter(0)),
                headers=["Name", "Since", "Visibility", "Notes"],
                tablefmt="plain",
            )
            emit.message(tabulated_snaps)
예제 #28
0
    def _pack_bundle(self, parsed_args) -> List[pathlib.Path]:
        """Pack a bundle."""
        emit.progress("Packing the bundle.")
        if parsed_args.shell:
            build.launch_shell()
            return []

        project = self.config.project

        if self.config.parts:
            config_parts = self.config.parts.copy()
        else:
            # "parts" not declared, create an implicit "bundle" part
            config_parts = {"bundle": {"plugin": "bundle"}}

        # a part named "bundle" using plugin "bundle" is special and has
        # predefined values set automatically.
        bundle_part = config_parts.get("bundle")
        if bundle_part and bundle_part.get("plugin") == "bundle":
            special_bundle_part = bundle_part
        else:
            special_bundle_part = None

        # get the config files
        bundle_filepath = project.dirpath / "bundle.yaml"
        bundle_config = load_yaml(bundle_filepath)
        if bundle_config is None:
            raise CraftError(
                "Missing or invalid main bundle file: {!r}.".format(
                    str(bundle_filepath)))
        bundle_name = bundle_config.get("name")
        if not bundle_name:
            raise CraftError(
                "Invalid bundle config; missing a 'name' field indicating the bundle's name in "
                "file {!r}.".format(str(bundle_filepath)))

        if special_bundle_part:
            # set prime filters
            for fname in MANDATORY_FILES:
                fpath = project.dirpath / fname
                if not fpath.exists():
                    raise CraftError("Missing mandatory file: {!r}.".format(
                        str(fpath)))
            prime = special_bundle_part.setdefault("prime", [])
            prime.extend(MANDATORY_FILES)

            # set source if empty or not declared in charm part
            if not special_bundle_part.get("source"):
                special_bundle_part["source"] = str(project.dirpath)

        if env.is_charmcraft_running_in_managed_mode():
            work_dir = env.get_managed_environment_home_path()
        else:
            work_dir = project.dirpath / build.BUILD_DIRNAME

        # run the parts lifecycle
        emit.trace(f"Parts definition: {config_parts}")
        lifecycle = parts.PartsLifecycle(
            config_parts,
            work_dir=work_dir,
            project_dir=project.dirpath,
            project_name=bundle_name,
            ignore_local_sources=[bundle_name + ".zip"],
        )
        try:
            lifecycle.run(Step.PRIME)
        except (RuntimeError, CraftError) as error:
            if parsed_args.debug:
                emit.trace(f"Error when running PRIME step: {error}")
                build.launch_shell()
            raise

        # pack everything
        create_manifest(lifecycle.prime_dir, project.started_at, None, [])
        zipname = project.dirpath / (bundle_name + ".zip")
        build_zip(zipname, lifecycle.prime_dir)

        emit.message(f"Created {str(zipname)!r}.")

        if parsed_args.shell_after:
            build.launch_shell()

        return [zipname]
예제 #29
0
 def run(self, parsed_args):
     """Run the command."""
     emit.message(f"snapcraft {__version__}")
예제 #30
0
    def run(self, parsed_args):
        """Run the command."""
        tmpdir = self._unzip_charm(parsed_args.filepath)

        # run the analyzer
        override_ignore_config = bool(parsed_args.force)
        linting_results = linters.analyze(
            self.config,
            tmpdir,
            override_ignore_config=override_ignore_config,
        )

        # if format is json almost no further processing is needed
        if parsed_args.format == JSON_FORMAT:
            info = [{
                "name": r.name,
                "result": r.result,
                "url": r.url,
                "type": r.check_type,
            } for r in linting_results]
            emit.message(json.dumps(info, indent=4))
            return

        # group by attributes and lint outcomes (discarding ignored ones)
        grouped = {}
        for result in linting_results:
            if result.check_type == linters.CheckType.attribute:
                group_key = linters.CheckType.attribute
                result_info = result.result
            else:
                # linters
                group_key = result.result
                if result.result == linters.OK:
                    result_info = "no issues found"
                elif result.result in (linters.FATAL, linters.IGNORED):
                    result_info = None
                else:
                    result_info = result.text
            grouped.setdefault(group_key, []).append((result, result_info))

        # present the results
        titles = [
            ("Attributes", linters.CheckType.attribute),
            ("Lint Ignored", linters.IGNORED),
            ("Lint Warnings", linters.WARNINGS),
            ("Lint Errors", linters.ERRORS),
            ("Lint Fatal", linters.FATAL),
            ("Lint OK", linters.OK),
        ]
        for title, key in titles:
            results = grouped.get(key)
            if results is not None:
                emit.message(f"{title}:")
                for result, result_info in results:
                    if result_info:
                        emit.message(
                            f"- {result.name}: { result_info} ({result.url})")
                    else:
                        emit.message(f"- {result.name} ({result.url})")

        # the return code depends on the presence of different issues
        if linters.FATAL in grouped:
            retcode = 1
        elif linters.ERRORS in grouped:
            retcode = 2
        elif linters.WARNINGS in grouped:
            retcode = 3
        else:
            retcode = 0

        return retcode