Ejemplo n.º 1
0
    def run(self, parsed_args):
        """Run the command."""
        # get the config files
        bundle_filepath = self.config.project.dirpath / 'bundle.yaml'
        bundle_config = load_yaml(bundle_filepath)
        if bundle_config is None:
            raise CommandError(
                "Missing or invalid main bundle file: '{}'.".format(
                    bundle_filepath))
        bundle_name = bundle_config.get('name')
        if not bundle_name:
            raise CommandError(
                "Invalid bundle config; missing a 'name' field indicating the bundle's name in "
                "file '{}'.".format(bundle_filepath))

        # so far 'pack' works for bundles only (later this will operate also on charms)
        if self.config.type != 'bundle':
            raise CommandError(
                "Bad config: 'type' field in charmcraft.yaml must be 'bundle' for this command."
            )

        # pack everything
        paths = get_paths_to_include(self.config)
        zipname = self.config.project.dirpath / (bundle_name + '.zip')
        build_zip(zipname, self.config.project.dirpath, paths)
        logger.info("Created '%s'.", zipname)
Ejemplo n.º 2
0
def test_load_yaml_success(tmp_path):
    test_file = tmp_path / "testfile.yaml"
    test_file.write_text("""
        foo: 33
    """)
    content = load_yaml(test_file)
    assert content == {"foo": 33}
Ejemplo n.º 3
0
def load(dirpath: Optional[str]) -> Config:
    """Load the config from charmcraft.yaml in the indicated directory."""
    if dirpath is None:
        if is_charmcraft_running_in_managed_mode():
            dirpath = get_managed_environment_project_path()
        else:
            dirpath = pathlib.Path.cwd()
    else:
        dirpath = pathlib.Path(dirpath).expanduser().resolve()

    now = datetime.datetime.utcnow()

    content = load_yaml(dirpath / "charmcraft.yaml")
    if content is None:
        # configuration is mandatory only for some commands; when not provided, it will
        # be initialized all with defaults (but marked as not provided for later verification)
        return Config(
            type="charm",
            project=Project(
                dirpath=dirpath,
                config_provided=False,
                started_at=now,
            ),
        )

    return Config.unmarshal(
        content,
        project=Project(
            dirpath=dirpath,
            config_provided=True,
            started_at=now,
        ),
    )
Ejemplo n.º 4
0
def load(dirpath):
    """Load the config from charmcraft.yaml in the indicated directory."""
    if dirpath is None:
        dirpath = pathlib.Path.cwd()
    else:
        dirpath = pathlib.Path(dirpath).expanduser().resolve()

    content = load_yaml(dirpath / "charmcraft.yaml")
    if content is None:
        # configuration is mandatory only for some commands; when not provided, it will
        # be initialized all with defaults (but marked as not provided for later verification)
        content = {}
        config_provided = False

    else:
        # config provided! validate the loaded config is ok and mark as such
        try:
            jsonschema.validate(
                instance=content, schema=CONFIG_SCHEMA, format_checker=format_checker
            )
        except jsonschema.ValidationError as exc:
            adapt_validation_error(exc)
        config_provided = True

    # this timestamp will be used in several places, even sent to Charmhub: needs to be UTC
    now = datetime.datetime.utcnow()

    # inject project's config
    content["project"] = Project(
        dirpath=dirpath, config_provided=config_provided, started_at=now
    )

    return Config(**content)
Ejemplo n.º 5
0
def test_load_yaml_no_file(tmp_path, emitter):
    test_file = tmp_path / "testfile.yaml"
    content = load_yaml(test_file)
    assert content is None

    expected = "Couldn't find config file {!r}".format(str(test_file))
    emitter.assert_trace(expected)
Ejemplo n.º 6
0
def load(dirpath):
    """Load the config from charmcraft.yaml in the indicated directory."""
    if dirpath is None:
        dirpath = pathlib.Path.cwd()
    else:
        dirpath = pathlib.Path(dirpath).expanduser().resolve()

    content = load_yaml(dirpath / 'charmcraft.yaml')
    if content is None:
        # configuration is mandatory only for some commands; when not provided, it will
        # be initialized all with defaults (but marked as not provided for later verification)
        content = {}
        config_provided = False

    else:
        # config provided! validate the loaded config is ok and mark as such
        try:
            jsonschema.validate(instance=content,
                                schema=CONFIG_SCHEMA,
                                format_checker=format_checker)
        except jsonschema.ValidationError as exc:
            adapt_validation_error(exc)
        config_provided = True

    # inject project's config
    content['project'] = Project(dirpath=dirpath,
                                 config_provided=config_provided)

    return Config(**content)
Ejemplo n.º 7
0
    def _pack_bundle(self):
        """Pack a bundle."""
        # get the config files
        bundle_filepath = self.config.project.dirpath / "bundle.yaml"
        bundle_config = load_yaml(bundle_filepath)
        if bundle_config is None:
            raise CommandError(
                "Missing or invalid main bundle file: '{}'.".format(
                    bundle_filepath))
        bundle_name = bundle_config.get("name")
        if not bundle_name:
            raise CommandError(
                "Invalid bundle config; missing a 'name' field indicating the bundle's name in "
                "file '{}'.".format(bundle_filepath))

        # so far 'pack' works for bundles only (later this will operate also on charms)
        if self.config.type != "bundle":
            raise CommandError(
                "Bad config: 'type' field in charmcraft.yaml must be 'bundle' for this command."
            )

        # pack everything
        project = self.config.project
        manifest_filepath = create_manifest(project.dirpath,
                                            project.started_at)
        try:
            paths = get_paths_to_include(self.config)
            zipname = project.dirpath / (bundle_name + ".zip")
            build_zip(zipname, project.dirpath, paths)
        finally:
            manifest_filepath.unlink()
        logger.info("Created '%s'.", zipname)
Ejemplo n.º 8
0
def test_load_yaml_no_file(tmp_path, caplog):
    caplog.set_level(logging.DEBUG, logger="charmcraft.commands")

    test_file = tmp_path / "testfile.yaml"
    content = load_yaml(test_file)
    assert content is None

    expected = "Couldn't find config file {!r}".format(str(test_file))
    assert [expected] == [rec.message for rec in caplog.records]
Ejemplo n.º 9
0
def test_load_yaml_corrupted_format(tmp_path, emitter):
    test_file = tmp_path / "testfile.yaml"
    test_file.write_text("""
        foo: [1, 2
    """)
    content = load_yaml(test_file)
    assert content is None

    expected = "Failed to read/parse config file.*testfile.yaml.*ParserError.*"
    emitter.assert_trace(expected, regex=True)
Ejemplo n.º 10
0
def test_load_yaml_file_problem(tmp_path, emitter):
    test_file = tmp_path / "testfile.yaml"
    test_file.write_text("""
        foo: bar
    """)
    test_file.chmod(0o000)
    content = load_yaml(test_file)
    assert content is None

    expected = f"Failed to read/parse config file {str(test_file)!r}.*PermissionError.*"
    emitter.assert_trace(expected, regex=True)
Ejemplo n.º 11
0
def test_load_yaml_corrupted_format(tmp_path, caplog):
    caplog.set_level(logging.ERROR, logger="charmcraft.commands")

    test_file = tmp_path / "testfile.yaml"
    test_file.write_text("""
        foo: [1, 2
    """)
    content = load_yaml(test_file)
    assert content is None

    (logged, ) = [rec.message for rec in caplog.records]
    assert "Failed to read/parse config file {}".format(test_file) in logged
    assert "ParserError" in logged
Ejemplo n.º 12
0
def test_load_yaml_file_problem(tmp_path, caplog):
    caplog.set_level(logging.ERROR, logger="charmcraft.commands")

    test_file = tmp_path / "testfile.yaml"
    test_file.write_text("""
        foo: bar
    """)
    test_file.chmod(0o000)
    content = load_yaml(test_file)
    assert content is None

    (logged, ) = [rec.message for rec in caplog.records]
    assert "Failed to read/parse config file {}".format(test_file) in logged
    assert "PermissionError" in logged
Ejemplo n.º 13
0
    def _pack_bundle(self):
        """Pack a bundle."""
        project = self.config.project
        config_parts = self.config.parts.copy()
        bundle_part = config_parts.setdefault("bundle", {})
        prime = bundle_part.setdefault("prime", [])

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

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

        # set source for buiding
        bundle_part["source"] = str(project.dirpath)

        # run the parts lifecycle
        logger.debug("Parts definition: %s", config_parts)
        lifecycle = parts.PartsLifecycle(
            config_parts,
            work_dir=project.dirpath / build.BUILD_DIRNAME,
            ignore_local_sources=[bundle_name + ".zip"],
        )
        lifecycle.run(Step.PRIME)

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

        logger.info("Created %r.", str(zipname))
Ejemplo n.º 14
0
def load(dirpath: Optional[str]) -> Config:
    """Load the config from charmcraft.yaml in the indicated directory."""
    if dirpath is None:
        if is_charmcraft_running_in_managed_mode():
            dirpath = get_managed_environment_project_path()
        else:
            dirpath = pathlib.Path.cwd()
    else:
        dirpath = pathlib.Path(dirpath).expanduser().resolve()

    now = datetime.datetime.utcnow()

    content = load_yaml(dirpath / "charmcraft.yaml")
    if content is None:
        # configuration is mandatory only for some commands; when not provided, it will
        # be initialized all with defaults (but marked as not provided for later verification)
        return Config(
            project=Project(
                dirpath=dirpath,
                config_provided=False,
                started_at=now,
            ),
        )

    if any("_" in x for x in content.get("charmhub", {}).keys()):
        # underscores in config attribs deprecated on 2021-05-31
        notify_deprecation("dn01")

    return Config.unmarshal(
        content,
        project=Project(
            dirpath=dirpath,
            config_provided=True,
            started_at=now,
        ),
    )
Ejemplo n.º 15
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]