def run(self, parsed_args): """Run the command.""" # this command is deprecated now (note that the whole infrastructure behind # is ok to use, but through PackCommand) notify_deprecation("dn06") validator = Validator(self.config) args = validator.process(parsed_args) emit.trace(f"Working arguments: {args}") builder = Builder(args, self.config) builder.run(destructive_mode=args["destructive_mode"])
def test_notice_skipped_in_managed_mode(monkeypatch, emitter): """Present proper messages to the user.""" monkeypatch.setitem(_DEPRECATION_MESSAGES, "dn666", "Test message for the user.") monkeypatch.setattr(deprecations, "_DEPRECATION_URL_FMT", "http://docs.com/#{deprecation_id}") with patch( "charmcraft.deprecations.is_charmcraft_running_in_managed_mode", return_value=True, ): notify_deprecation("dn666") emitter.assert_interactions(None)
def test_notice_ok(monkeypatch, caplog): """Present proper messages to the user.""" caplog.set_level(logging.WARNING, logger="charmcraft") monkeypatch.setitem(_DEPRECATION_MESSAGES, "dn666", "Test message for the user.") monkeypatch.setattr(deprecations, "_DEPRECATION_URL_FMT", "http://docs.com/#{deprecation_id}") notify_deprecation("dn666") expected = [ "DEPRECATED: Test message for the user.", "See http://docs.com/#dn666 for more information.", ] assert expected == [rec.message for rec in caplog.records]
def test_notice_ok(monkeypatch, emitter): """Present proper messages to the user.""" monkeypatch.setitem(_DEPRECATION_MESSAGES, "dn666", "Test message for the user.") monkeypatch.setattr(deprecations, "_DEPRECATION_URL_FMT", "http://docs.com/#{deprecation_id}") notify_deprecation("dn666") emitter.assert_interactions([ call("message", "DEPRECATED: Test message for the user.", intermediate=True), call("message", "See http://docs.com/#dn666 for more information.", intermediate=True), ])
def test_notice_skipped_in_managed_mode(monkeypatch, caplog): """Present proper messages to the user.""" caplog.set_level(logging.WARNING, logger="charmcraft") monkeypatch.setitem(_DEPRECATION_MESSAGES, "dn666", "Test message for the user.") monkeypatch.setattr(deprecations, "_DEPRECATION_URL_FMT", "http://docs.com/#{deprecation_id}") with patch( "charmcraft.deprecations.is_charmcraft_running_in_managed_mode", return_value=True, ): notify_deprecation("dn666") assert [rec.message for rec in caplog.records] == []
def test_log_deprecation_only_once(monkeypatch, caplog): """Show the message only once even if it was called several times.""" caplog.set_level(logging.WARNING, logger="charmcraft") monkeypatch.setitem(_DEPRECATION_MESSAGES, "dn666", "Test message for the user.") monkeypatch.setattr(deprecations, "_DEPRECATION_URL_FMT", "http://docs.com/#{deprecation_id}") # call twice, log once notify_deprecation("dn666") notify_deprecation("dn666") expected = [ "DEPRECATED: Test message for the user.", "See http://docs.com/#dn666 for more information.", ] assert expected == [rec.message for rec in caplog.records]
def test_log_deprecation_only_once(monkeypatch, emitter): """Show the message only once even if it was called several times.""" monkeypatch.setitem(_DEPRECATION_MESSAGES, "dn666", "Test message for the user.") monkeypatch.setattr(deprecations, "_DEPRECATION_URL_FMT", "http://docs.com/#{deprecation_id}") # call twice, log once notify_deprecation("dn666") notify_deprecation("dn666") emitter.assert_interactions([ call("message", "DEPRECATED: Test message for the user.", intermediate=True), call("message", "See http://docs.com/#dn666 for more information.", intermediate=True), ])
def unmarshal(cls, obj: Dict[str, Any], project: Project): """Unmarshal object with necessary translations and error handling. (1) Perform any necessary translations. (2) Standardize error reporting. :returns: valid CharmcraftConfig. :raises CommandError: On failure to unmarshal object. """ try: # Ensure optional type is specified if loading the yaml. # This can be removed once charmcraft.yaml is mandatory. if "type" not in obj: obj["type"] = None # Ensure short-form bases are expanded into long-form # base configurations. Doing it here rather than a Union # type will simplify user facing errors. bases = obj.get("bases") if bases is None: if obj["type"] in (None, "charm"): notify_deprecation("dn03") # Set default bases to Ubuntu 20.04 to match strict snap's # effective behavior. bases = [ { "name": "ubuntu", "channel": "20.04", "architectures": [get_host_architecture()], } ] # Expand short-form bases if only the bases is a valid list. If it # is not a valid list, parse_obj() will properly handle the error. if isinstance(bases, list): cls.expand_short_form_bases(bases) return cls.parse_obj({"project": project, **obj}) except pydantic.error_wrappers.ValidationError as error: raise CommandError(format_pydantic_errors(error.errors()))
def unmarshal(cls, obj: Dict[str, Any], project: Project): """Unmarshal object with necessary translations and error handling. (1) Perform any necessary translations. (2) Standardize error reporting. :returns: valid CharmcraftConfig. :raises CraftError: On failure to unmarshal object. """ try: # Ensure short-form bases are expanded into long-form # base configurations. Doing it here rather than a Union # type will simplify user facing errors. bases = obj.get("bases") if bases is None: # "type" is accessed with get because this code happens before # pydantic actually validating that type is present if obj.get("type") == "charm": notify_deprecation("dn03") # Set default bases to Ubuntu 20.04 to match strict snap's # effective behavior. bases = [{ "name": "ubuntu", "channel": "20.04", "architectures": [get_host_architecture()], }] # Expand short-form bases if only the bases is a valid list. If it # is not a valid list, parse_obj() will properly handle the error. if isinstance(bases, list): cls.expand_short_form_bases(bases) return cls.parse_obj({"project": project, **obj}) except pydantic.error_wrappers.ValidationError as error: raise CraftError(format_pydantic_errors(error.errors()))
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 run(self, bases_indices: Optional[List[int]] = None, destructive_mode: bool = False) -> List[str]: """Run build process. In managed-mode or destructive-mode, build for each bases configuration which has a matching build-on to the host we are executing on. Warn for each base configuration that is incompatible. Error if unable to produce any builds for any bases configuration. :returns: List of charm files created. """ charms: List[str] = [] managed_mode = env.is_charmcraft_running_in_managed_mode() if not managed_mode and not destructive_mode: self.provider.ensure_provider_is_available() if self.entrypoint: notify_deprecation("dn04") if self.requirement_paths: notify_deprecation("dn05") if not (self.charmdir / "charmcraft.yaml").exists(): notify_deprecation("dn02") build_plan = self.plan( bases_indices=bases_indices, destructive_mode=destructive_mode, managed_mode=managed_mode, ) if not build_plan: raise CommandError( "No suitable 'build-on' environment found in any 'bases' configuration." ) charms = [] for bases_config, build_on, bases_index, build_on_index in build_plan: emit.trace( f"Building for 'bases[{ bases_index:d}][{build_on_index:d}]'.") if managed_mode or destructive_mode: if self.shell: # Execute shell in lieu of build. launch_shell() continue try: charm_name = self.build_charm(bases_config) except (CommandError, RuntimeError) as error: if self.debug: emit.trace( f"Launching shell as charm building ended in error: {error}" ) launch_shell() raise if self.shell_after: launch_shell() else: charm_name = self.pack_charm_in_instance( bases_index=bases_index, build_on=build_on, build_on_index=build_on_index, ) charms.append(charm_name) return charms
def run(self, bases_indices: Optional[List[int]] = None, destructive_mode: bool = False) -> List[str]: """Run build process. In managed-mode or destructive-mode, build for each bases configuration which has a matching build-on to the host we are executing on. Warn for each base configuration that is incompatible. Error if unable to produce any builds for any bases configuration. :returns: List of charm files created. """ charms: List[str] = [] managed_mode = is_charmcraft_running_in_managed_mode() if not managed_mode and not destructive_mode: ensure_provider_is_available() if self.entrypoint: notify_deprecation("dn04") if self.requirement_paths: notify_deprecation("dn05") if not (self.charmdir / "charmcraft.yaml").exists(): notify_deprecation("dn02") for bases_index, bases_config in enumerate(self.config.bases): if bases_indices and bases_index not in bases_indices: logger.debug( "Skipping 'bases[%d]' due to --base-index usage.", bases_index, ) 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 = is_base_providable(build_on) if matches: logger.debug( "Building for 'bases[%d]' as host matches 'build-on[%d]'.", bases_index, build_on_index, ) if managed_mode or destructive_mode: charm_name = self.build_charm(bases_config) else: charm_name = self.pack_charm_in_instance( bases_index=bases_index, build_on=build_on, build_on_index=build_on_index, ) charms.append(charm_name) break else: logger.info( "Skipping 'bases[%d].build-on[%d]': %s.", bases_index, build_on_index, reason, ) else: logger.warning( "No suitable 'build-on' environment found in 'bases[%d]' configuration.", bases_index, ) if not charms: raise CommandError( "No suitable 'build-on' environment found in any 'bases' configuration." ) return charms