예제 #1
0
    def make(
        cls,
        value: Optional[Union[str, Tuple[str, ...]]] = None,
        expires_at: Optional[datetime] = None,
    ) -> "Resolution":
        """
        Makes a `Resolution` to describe a value and the duration to cache it.

        Args:
            value:      Environment variable value, or `None` to not set.
            expires_at: Time to cache the value until. `None` to not cache.

        Returns:
            Resolution.
        """
        store: Dict[str, Any] = {}
        if value is not None:
            if isinstance(value, str):
                store.update({"values": (value, )})
            else:
                store.update({"values": value})
        if expires_at:
            store.update({"expires_at": expires_at.isoformat()})

        # Don't log the store; it contains confidential information.
        get_logger().debug('"Resolution.make" created a new store.')
        return Resolution(store=store)
예제 #2
0
def resolve(state: Optional[BaseState] = None) -> Dict[str, str]:
    """
    Gathers values for all the required environment variables. Reads from the
    cache where possible, and invokes plugins where needed.

    Returns:
        Environment variable names and values.
    """

    logger = get_logger()
    this_state = state or State()

    environs: Dict[str, str] = {**environ}

    for variable in this_state.get_variables():
        if resolution := fresh_resolution(variable.resolution):
            logger.debug("%s has a fresh cache.", variable.names)
        else:
            logger.info("Resolving %s...", bold(variable.names))

            support = ResolutionSupport(
                logger=get_logger(name=variable.plugin.id),
                confidential_prompt=confidential_prompt,
            )

            plugin = get_plugin(variable.plugin)

            try:
                resolution = plugin.resolve(support=support)
            except CannotResolveError as ex:
                raise CannotResolveError(
                    f'"{variable.plugin.id}" failed: {ex}')

            # If the resolution has a cache expiry date then cache it.
            # Otherwise don't cache it, and remove any previously-set cache.
            if resolution.expires_at:
                this_state.resolution_cache.update(
                    names=variable.names,
                    resolution=resolution,
                )
            else:
                this_state.resolution_cache.remove(names=variable.names)

        if resolution.values:
            logger.debug("Enumerating resolved values: %s", resolution.values)
            variable_count = len(variable.names)
            resolution_count = len(resolution.values)

            if variable_count != resolution_count:
                raise IncorrectResolutionCountError(
                    plugin_id=variable.plugin.id,
                    variable_count=variable_count,
                    resolution_count=resolution_count,
                )

            for index, name in enumerate(variable.names):
                value = resolution.values[index]
                logger.debug("Adding resolved variable: %s=%s", name, value)
                environs.update({name: value})
예제 #3
0
파일: plugins.py 프로젝트: cariad/wev
def get_plugin(config: PluginConfiguration) -> PluginBase:
    """
    Gets a plugin instance to handle the given configuration.

    Raises `NoPluginError` if there isn't a plugin available to handle this
    configuration.

    Raises `MultiplePluginsError` if there are multple plugins available to
    handle this configuration.
    """
    plugins = [
        e for e in iter_entry_points("wev.plugins") if e.name == config.id
    ]

    if len(plugins) == 0:
        raise NoPluginError(plugin_id=config.id)

    if len(plugins) > 1:
        raise MultiplePluginsError(plugin_id=config.id, count=len(plugins))

    logger = get_logger()

    logger.debug("Instantiating plugin with: %s", config)
    plugin = cast(PluginBase, plugins[0].load().Plugin(config))

    logger.debug("Instantiated plugin: %s", plugin)
    return plugin
예제 #4
0
파일: explainer.py 프로젝트: cariad/wev
def explain(logger: Optional[Logger] = None, state: Optional[BaseState] = None) -> None:
    """
    Logs an explanation of what an execution will do.

    Args:
        logger: Logger. One will be created if not specified.
    """
    return _explain(logger=logger or get_logger(), state=state or State())
예제 #5
0
    def __init__(self, store: Dict[str, Any]) -> None:
        self.logger = get_logger()

        # Don't log the store; it probably contains confidential information.
        self.logger.debug("Initialising new Resolution.")

        if not isinstance(store, dict):
            raise ValueError(
                'Resolution "store" is not a dictionary: %s',
                type(store),
            )
        self.store = store
예제 #6
0
    def __init__(self, args: Optional[List[str]] = None) -> None:
        self.logger = get_logger()

        wev = Style.BRIGHT + "wev" + Style.RESET_ALL

        with_env_vars = (Style.BRIGHT + "w" + Style.RESET_ALL + "ith " +
                         Style.BRIGHT + "e" + Style.RESET_ALL + "nvironment " +
                         Style.BRIGHT + "v" + Style.RESET_ALL + "ariables")

        description = f"{wev} runs shell commands {with_env_vars}."

        epilog = (
            "examples:\n" + "  wev --explain\n" + "  wev --version\n" +
            "  wev pipenv install\n" +
            "  wev --log-level DEBUG pipenv install\n" + "\n" +
            "Built with ❤️ by Cariad Eccleston: https://github.com/cariad/wev")

        self.arg_parser = ArgumentParser(
            "wev",
            description=description,
            epilog=epilog,
            formatter_class=RawDescriptionHelpFormatter,
        )

        self.arg_parser.add_argument(
            "command",
            nargs="...",
            help=f"shell command and arguments to run {with_env_vars}.",
        )

        self.arg_parser.add_argument(
            "--explain",
            action="store_true",
            help=f"explain what {wev} will do",
        )

        self.arg_parser.add_argument(
            "--log-level",
            default="INFO",
            help="log level (default is INFO)",
            metavar="LEVEL",
        )

        self.arg_parser.add_argument(
            "--version",
            action="store_true",
            help="print the version",
        )

        self.args = self.arg_parser.parse_args(args)
        set_level(self.args.log_level.upper())
예제 #7
0
def execute(command: List[str]) -> int:
    """
    Executes the command with environment variables.

    Args:
        command: Shell command.

    Returns:
        Shell return code.
    """
    logger = get_logger()
    variables = resolve()
    logger.debug("Starting %s...", command[0])
    return run(command, env=variables).returncode
예제 #8
0
파일: variable.py 프로젝트: cariad/wev
    def __init__(self, names: Tuple[str, ...], store: Dict[str, Any]) -> None:
        self.logger = get_logger()

        # Don't log the values; they're probably confidential.
        self.logger.debug('Variable: name="%s"', names)

        if "resolution" in store and not isinstance(store["resolution"], dict):
            raise ValueError(
                '"resolution" is not a dictionary: %s',
                type(store["resolution"]),
            )

        self.names = names
        self.store = store
예제 #9
0
 def __init__(
     self,
     context: str,
     logger: Optional[Logger] = None,
     path: Optional[Path] = None,
 ) -> None:
     self.logger = logger or get_logger()
     self.context = context
     self.path = path or Path.home().absolute().joinpath(".wevcache")
     self.resolutions: Dict[Tuple[str, ...], Dict[str, Any]] = {}
     self.logger.debug(
         'ResolutionCache: context="%s" path="%s"',
         self.context,
         self.path,
     )
예제 #10
0
파일: explainer.py 프로젝트: cariad/wev
def _explain(logger: Logger, state: BaseState) -> None:
    """
    Logs an explanation of what an execution will do.

    Args:
        logger: Logger.
    """
    logger.info(
        "%s (%s) execution plan generated at %s:",
        bold("wev"),
        get_version(),
        bold(get_now()),
    )

    list_index_padding = 2
    list_index_suffix = "."

    logger.info("")

    for index, variable in enumerate(state.get_variables()):
        logger.info(
            "%s%s %s will be resolved by the %s plugin.",
            str(index + 1).rjust(list_index_padding),
            list_index_suffix,
            bold(displayable(variable.names)),
            bold(variable.plugin.id),
        )

        margin = "    "

        if variable.resolution:
            logger.info("")
            logger.info("%s%s", margin, dim(variable.resolution.explain_cache))

        if not variable.should_read_from_cache:
            logger.info("")
            plugin = get_plugin(variable.plugin)
            for line in plugin.explain(logger=get_logger(name=variable.plugin.id)):
                logger.info("%s%s", margin, dim(line))

        logger.info("")
예제 #11
0
 def __init__(self) -> None:
     self.logger = get_logger()
     self.logger.debug("Creating a new state.")
예제 #12
0
def logger() -> Logger:
    basicConfig()
    logger = get_logger()
    logger.setLevel(level="DEBUG")
    return logger
예제 #13
0
def resolution_support() -> ResolutionSupport:
    return ResolutionSupport(
        logger=get_logger(),
        confidential_prompt=confidential_prompt,
    )