# Aspect Cli Config Structure
###### tags: `cli`
Status: **DRAFT**<br>
Date: _April 20, 2022_<br>
Authors: _Dylan Martin \<dylan@aspect.dev\>_<br>
Reviewers: _Alex Eagle, Thulio Ferraz Assis, Matt Mackay_, _Greg Magolan_
Summary: _How to structure the config files and their layout for the aspect cli._<br>
---
**This document follows the conventions from [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt).**
## Background
Aspect CLI plugins are configurable. Users want to share configurations with their team and CI systems, while also being able to express personal preferences in a non-destructive way. This is desired for a single run of the tool or to change values in CI consistently.
This means it must be clear and quick to reason about configuration and the precedence of how conflicting settings interact. It must also work well with source control.
We will expose configuration files and command line flags for these purposes.
Relevant github issues:
https://github.com/aspect-build/aspect-cli/issues/75
## Goals
Users who understand Bazel should be able to reason easily about how our config flags work. So the default behavior should be consistent with how bazel interacts with bazelrc files.
If a plugin is moved from being in a plugin bundle to standalone, or moved from standalone to be in a bundle, then user's config files shouldn't have to change.
## Config File Locations
There will only be four places to set the configuration of the Aspect CLI. In order of precedence from highest to lowest (highest precedence overriding lowest).
1. Individual flags pass to the CLI `--aspect:some_flag=some_value`
3. Pass a custom config file location directly to the CLI ex. --aspect:config=.aspect/cli/config.yaml
4. The users home directory `$HOME/.aspect/cli/config.yaml`
5. Base of the workspace `$WORKSPACE/.aspect/cli/config.yaml`
## Config File Format
Configuration files must be .yaml.
Example:
`$HOME/.aspect/cli/config.yaml`
```yaml=
- other_configs: ...
- plugins:
- name: error-augmentor
from: ../../bazel-bin/plugins/error-augmentor#error_augmentor
depends_on: ["plugin-1", "plugin-2"]
log_level: OFF
error_mappings:
"was not created by genrule": "Your genrule is broken."
"declared output '(.*)' was not created by genrule.": "File not created by genrule: $1."
- name: fix-visibility
from: //:cli-plugins-pro#fix_visibility
log_level: debug
```
It is also relevant to know that all features available through the config file should also be flags that can be passed to the aspect cli. One should also consider that features and settings in the config file should be modifiable through a command in the aspect cli that writes to the config file. This is just to say take care in naming and structuring your namespace so that commands are readable on their own.
## Config File Resolution
The workspace should have an `.aspect/cli/` folder at the base and then a config.yaml file inside which will contain the configuration for aspect cli.
`$WORKSPACE/.aspect/cli/config.yaml`
The user may have their own .aspect/cli/ folder in their $HOME directory and a config.yaml file inside which will contain the user settings that will take precedence.
`$HOME/.aspect/cli/config.yaml`
A user will also be able to use a single config file by specifying it's location through a aspect cli flag `--aspect:config`.
The aspect cli will automatically look for configuration files in these locations. The user file (`$HOME/.aspect/cli/config.yaml`) will take precedence over the workspace file (`$WORKSPACE/.aspect/cli/config.yaml`) but flags passed to the aspect cli will still be highest prority and override anything else. The interaction between the files should be similar to how bazel deals with .bazelrc files so it will be comfortable for those already familiar. Unless a flag is passed to the aspect cli with an exact location and in that case only that file will be used.
When a user wants to make a change programmatically, the default file that will be changed should be made in user file. There should also be a command to update the config. `aspect config {}` for example, should be responsible to actually update a file with a new desired config. This should be printed or prompted for the user to run.
To make it easy to understand what the current merged configuration file looks like there will be a flag that writes the resolved file to a user specified location.
`--aspect:config_out=/some/path/config.yaml`
In the future this could also be used to diff configuration files if they become very long.
For setting that will replace variables vs adding or appending more information to the structure, there must be some way to signal this to the core aspect cli.
### We may delay adding the ability to append structures together until a later date.
The two initial ideas in this area would be to use a flag or to use the data structure of the field itself. These both come with their own positives and negatives.
Flag:
Verbose, easy to read and could be checked by aspect cli. Also means that aspect cli core has to have some knowlege about the plugin or else this flag would lose alot of its value. This could make things more complicated between the core cli and the plugins.
Data structure:
Leave it up to the documentation of the plugins to ensure users pass the correct data structure to the plugin. This would also mean the plugins would be responsible for better error messages. This could make it easier and cleaner for the apsect cli core to replace, merge or append settings simply based on what is set as the structure. Then error handeling and error messages should be the responsibility of the plugin as it will have the relevant infomation to provide a better message.
For the initial implementation simply using the data structures and passing those along to the plugin would seem to be the cleanest and allow for the most options going forward.
## Config Flags
`aspect --aspect:plugins.error-augmentor.error_mappings=foo=bar`
Mapping from config file to cli:
`$HOME/.aspect/cli/config.yaml`
```yaml=
- other_configs: ...
- plugins:
- name: error-augmentor
from: ../../bazel-bin/plugins/error-augmentor#error_augmentor
depends_on: ["plugin-1", "plugin-2"]
log_level: OFF
attributes:
error_mappings:
"was not created by genrule": "Your genrule is broken."
"declared output '(.*)' was not created by genrule.": "File not created by genrule: $1."
- name: fix-visibility
from: //:cli-plugins-pro#fix_visibility
log_level: debug
```
would turn in to aspect cli command:
``
aspect --aspect:plugins[name="error-augmentor"].from="../../bazel-bin/plugins/error-augmentor#error_augmentor" --aspect:plugins[name="error-augmentor"].depends_on=["plugin-1","plugin-2"] --aspect:plugins[name="error-augmentor"].log_level=OFF --aspect:plugins[name="error-augmentor"].error_mappings=["was not created by genrule": "Your genrule is broken."
"declared output '(.*)' was not created by genrule.": "File not created by genrule: $1."] --aspect:plugins[name=fix-visibility].from"//:cli-plugins-pro#fix_visibility" --aspect:plugins[name="fix-visibility"].log_level=debug
``
The plugin name and location (from) need to be specified together so that the cli can understand their relationship.
## Plugin Registration
Plugins are distributed independently and have their own gRPC server. However a plugin can be "bundled" into another plugin, to simplify the distribution and reduce resources at runtime. For example, the aspect-build/cli-plugins-pro repo distributes a single plugin which is a bundle of multiple plugins. The configuration format does not distinguish between a standalone plugin and one that is bundled.
Plugin registration will occur in the same file as the the configuration. (this will require rework of how things are currently configured in the ascpect cli as registration is done through the .aspectplugins file currently.) The end goal should be to make it clear and easy to reason about current configuration of the Aspect CLI and its expected behaviour. With this in mind combining registration and configuration into one file would achive this goal. Especially if one wants to change registration of plugins locally.
Each plugin in the aspect cli will have it's own top level entry no matter if it comes bundled with other plugins or not.
This top level entry will be responsible for registering the plugin, setting its configuration and declaring that plugins dependencies on other plugins.
We will allow dependencies on multiple plugins but they will not be started in any order. The only guarentee is that they have all been started before you plugin starts.
For circualr dependencies the core aspect cli resolver will provide warnings about circular dependencies.
All other error handling and useful error messages should be handled by the individual plugins. Provided they use the recomended error handling and message passing that is supported in the aspect cli core.
### How to deal with plugin ordering and dependencies
Plugins interact with each other:
- by observing side-effects like build event protocol messages
- any other ways? can they share memory or call each other's library code?
For this reason, the order that the plugins are installed in the CLI can matter.
The plugin registration file could have a `depends_on` key and/or a `depended_by` key. The depended_by key is useful for registering a plugin in your local configuration that you want to run before one registered in source control.
```yaml=
- name: foo
from: gitorg/some-repo
depends_on: bar
- name: bar
from: gitorg/plugin1
```
or written with depended_by it would be:
```yaml=
- name: foo
from: gitorg/some-repo
- name: bar
from: gitorg/plugin1
depended_by: foo
```
The plugins should be installed in the partial ordering indicated.
Cycles in the dependency graph are not permitted.
## Schema Validation
We should use https://json-schema.org/ to set the configuration file so that it can be validated. Validation should be used for everything except the attributes section of each plugin as that will be whatever the creator of the plugin wants.