diff --git a/README.md b/README.md index 860b357..6fe1da1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # wev: run shell commands with environment variables -**This is prerelease software. Don't use it. :)** - `wev` is a command line tool for resolving environment variables then running shell commands. For example: diff --git a/example.wev.yml b/example.wev.yml new file mode 100644 index 0000000..729af5a --- /dev/null +++ b/example.wev.yml @@ -0,0 +1,8 @@ +MY_NAME: + plugin: + id: wev-echo + value: Bobby Pringles + +[AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN]: + plugin: + id: wev-awsmfa diff --git a/wev/mock_plugin.py b/wev/mock_plugin.py index 5bf9bc8..949fe5d 100644 --- a/wev/mock_plugin.py +++ b/wev/mock_plugin.py @@ -18,6 +18,7 @@ class MockPlugin(PluginBase): `return_expires_at` will cause resolution to return an expiry date 60 seconds in the future. """ + def __init__( self, values: dict, diff --git a/wev/resolution_cache.py b/wev/resolution_cache.py index ccfa87f..fdce4e6 100644 --- a/wev/resolution_cache.py +++ b/wev/resolution_cache.py @@ -56,12 +56,13 @@ def read_all(self) -> Dict[str, Dict[Tuple[str, ...], Dict[str, Any]]]: Raises `CacheReadError` if the cache cannot be read. """ everything: Dict[str, Dict[Tuple[str, ...], Dict[str, Any]]] = {} - self.logger.debug("Reading cache: %s", self.path) + self.logger.debug("Reading entire cache: %s", self.path) try: with open(self.path, "r") as stream: if content := stream.read().strip(): everything = YAML(typ="safe").load(content) - self.logger.debug("Read cache: %s", everything) + # Don't log the cache; it contains confidential information. + self.logger.debug("Successfully read the entire cache.") else: self.logger.debug("Cache is empty: %s", self.path) except FileNotFoundError: @@ -100,10 +101,11 @@ def save(self) -> None: self.logger.debug("Writing cache: %s", self.path) everything = self.read_all() everything[self.context] = self.resolutions - self.logger.debug("Saving entire cache: %s", everything) + # Don't log the cache; it contains confidential information. + self.logger.debug("Saving the entire cache: %s", self.path) with open(self.path, "w") as stream: - yaml = YAML(typ="safe") - yaml.dump(everything, stream) + YAML(typ="safe").dump(everything, stream) + self.logger.debug("Successfully saved the entire cache.") def update(self, names: Tuple, resolution: Resolution) -> None: """ @@ -113,5 +115,6 @@ def update(self, names: Tuple, resolution: Resolution) -> None: var_name: Name of the environment variable. resolution: Resolution. """ - self.logger.debug("Updating %s in cache: %s", names, resolution.store) + # Don't log the store; it's confidential. + self.logger.debug("Updating %s in cache.", names) self.resolutions[names] = resolution.store diff --git a/wev/sdk/resolution.py b/wev/sdk/resolution.py index 6d02941..a8110e4 100644 --- a/wev/sdk/resolution.py +++ b/wev/sdk/resolution.py @@ -9,9 +9,15 @@ class Resolution: def __init__(self, store: Dict[str, Any]) -> None: self.logger = get_logger() - self.logger.debug("Resolution: store=%s", store) + + # Don't log the store; it probably contains confidential information. + self.logger.debug("Initialising new Resolution.") + if not isinstance(store, dict): - raise ValueError('"store" is not a dictionary: %s', type(store)) + raise ValueError( + 'Resolution "store" is not a dictionary: %s', + type(store), + ) self.store = store def __eq__(self, other: Any) -> bool: @@ -48,7 +54,9 @@ def make( store.update({"values": value}) if expires_at: store.update({"expires_at": expires_at.isoformat()}) - get_logger().debug('"Resolution.make" created store: %s', store) + + # Don't log the store; it contains confidential information. + get_logger().debug('"Resolution.make" created a new store.') return Resolution(store=store) @property @@ -98,7 +106,8 @@ def time_until_expiry(self) -> str: @property def values(self) -> Tuple[str, ...]: - self.logger.debug("Reading the resolved value: %s", self.store) + # Don't log the store; it's confidential. + self.logger.debug("Reading the resolved value.") values = self.store.get("values", []) if isinstance(values, tuple): return values diff --git a/wev/state/state.py b/wev/state/state.py index 842c6bb..d825e4c 100644 --- a/wev/state/state.py +++ b/wev/state/state.py @@ -42,9 +42,9 @@ def get_variables(self) -> Iterator[Variable]: store = self.config[key] if cached_resolution := self.resolution_cache.get(names): - self.logger.debug( - "Adding cached resolution: %s", cached_resolution.store - ) + # Don't log the resolution; the values are probably + # confidential. + self.logger.debug("Adding cached resolution.") store.update({"resolution": cached_resolution.store}) yield Variable(names=names, store=store) diff --git a/wev/variable.py b/wev/variable.py index 1be0cad..7007f7a 100644 --- a/wev/variable.py +++ b/wev/variable.py @@ -11,11 +11,14 @@ class Variable: def __init__(self, names: Tuple[str, ...], store: Dict[str, Any]) -> None: self.logger = get_logger() - self.logger.debug('Variable: name="%s" values="%s"', names, store) + + # 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"]) + '"resolution" is not a dictionary: %s', + type(store["resolution"]), ) self.names = names