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)
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}
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, ), )
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)
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)
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)
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)
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]
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)
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)
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
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
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))
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, ), )
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]