Execution order notes:
During the KeyStar client initialization--the primary entry point for most use cases--the following will occur:
- KeyStar receives its configuration via the keystar.Options struct which contains things like the connection URI for backend configuration, user authentications modes, etc. KeyStar acts primarily as gatekeeper for these services and does nothing directly parse this information. KeyStar simply delegates requests. This will allow us to support multiple simultaneous backends in the future via the same instance.
- The KeySpace object performs all of the real magic. A single KeySpace instance controls access to account information (which is part of the KeySpace in the current implementation; future versions may offload accounting responsibility to a separate database for global accounts) and key storage. Key storage is further broken down into the global public namespace (
__global__ under the key space root), global namespaces (
__namespaces__/<namespace> under the key space root), user global namespaces (
__accounts__/<account_name>/__global__ under the key space root), and user namespaces (
__accounts__/<account_name>/__namespaces__/<namespace> under the key space root). The KeySpace instance will initialize the backend loader as appropriate for each namespace and for accounting information.
- Individual storage backends for key data must implement the SecretHandler. They must also implement the Loader interface for dynamically configuring key storage. Implementors of Loader are not required to create distinct instances of SecretHandler implementations but doing so helps create a separation of concerns and will make implementations easier to write.
- The Loader interface also provides a means for initializing accounting information via implementations of AccountHandler. However, AccountHandler may not be available for all configurations, and authentication may be disabled.
- The Loader interface, in addition to SecretHandler and AccountHandler, may also provide an APIAccessHandler, which provides separate handling routines for API keys. This provides a pseudo-account for API access for different applications without requiring multi-user occupancy mode while simultaneously providing limitiations to what those API keys may access (global + specified namespace(s), global only, specified namespace(s) only, or all namespaces). APIAccessHandler is not likely to be implemented until after AccountHandler is available.
- When the Loader interface is called, it must return a function that is used to initialize the appropriate storage backends and may retain some state as required (such as connection strings to initialize things like encryption). The Loader also receives the Options object.
- When a SecretHandler is initialized, it will be provided with a Namespace instance that contains the namespace name, whether it is a global namespace, whether it is a user namespace, and the parent namespace chain. This will probably be managed by the KeySpace instance as it requires some awareness of namespace availability. KeySpace will internally cache namespaces, probably with an LRU implementation, that will slowly evict namespaces that aren't frequently used.
- The current API is likely going to be changed such that the KeyStar instance only returns points to a KeySpace. Originally, I had intended to make secrets and namespaces available via KeyStar, but I'm beginning to think this is impractical. Tying secret access into the KeyStar instance would create too many layers of indirection and interfer with a clearer execution path. Part of the intent with this rewrite is to make clear some facets of the application and include extra features (authentication) that were missing in the previous implementation.
- Likewise, namespaces may or may not be selectable from the KeyStar instance. Partially, this is because there will be no way to determine which KeySpace contains the requested namespace unambiguously. If we implement multiple key spaces (and their backends) as an option per-KeyStar instance, this further complicates direct access for a given namespace. At best, we might provide a means for extricating a key space based on its connection string, or an identifying hash of the connection string, or will allow callers to attach a symbolic name with the key space.
Not sure if this would work (it should; just tested; requires import _ for side effects)
examine database/sql for evidence if you don't believe me or:
Use build tags in files that import other packages
packages have build tags + init() functions
fs_loader.go: +build filesystem
fs package: func init().go