8. Configuration
As per urfave/cli’s doc:
The precedence for flag value sources is as follows (highest to lowest):
- Command line flag value from user
- Environment variable (if specified)
- Configuration file (if specified)
- Default defined on the flag
An issue arises in point 2, in the sense that configuration file refers to a single file containing the value for the env variable. The CLI framework we use for flag parsing does not support merging config structs with CLI flags. This introduces an inconsistency with the framework: config structs are not supported, and we cannot hook to the lifecycle of the flags parsing to use a file as source and conform to these rules.
Because we solely rely on structured configuration we need a way to modify values in this struct using the provided means urfave/cli gives us (flags, env variables, config files and default value), but since we have different modes of operation (supervised Vs. unsupervised) we have to define a clear line.
- Improve experience for the end user.
- Improve experience for developers.
- Sane defaults.
- Sane overrides.
- Extend FlagInputSourceExtension interface
- Feature request: support for structured configuration (urfave/cli).
- Clearly defined boundaries of what can and cannot be done.
- Expose structured field values as CLI flags
- Drop support for structure configuration
- Adapt the “structured config files have the highest priority” within oCIS
[STILL UNDECIDED]
[TBD, depends on Decision Outcome]
- Good, because we could still use Viper to load from config files here and apply values to the flags in the context.
- Bad, because urfave/cli team are actively working on v3 of altsrc and we don’t want to maintain yet another slice of the codebase.
notes: source is FlagInputSourceExtension interface
- Good, because we could remove Viper off the codebase and solely rely on urfave/cli’s native code.
- Bad, because there are no plans to support this upstream.
- Good, because no changes to the codebase required (not drastic changes.)
- Bad, because we’re limited by the framework
- Good, because it has been already taken into account on large projects (kubernetes) here. in point 5.
- Bad, because it requires quite a bit1 of custom logic.
- Bad, because how should these flags be present in the
-h
menu of a subcommand? Probably some code generation needed.
*[1] this is a big uncertainty.
- Good, because it makes the integration with the cli framework easier to grasp.
- Good, because it is not encouraged by the 12factor app spec.
- Bad, because we already support if and users make active use of it. At least for development.
- Good, because that would mean little structural changes to the codebase since the Viper config parsing logic already uses the
Before
hook to parse prior to the command’s action executes.
- Use a global config file (ocis.yaml) to configure an entire set of services:
> ocis --config-file /etc/ocis.yaml service
- Use a global config file (ocis.yaml) to configure a single extension:
> ocis --config-file /etc/ocis/yaml proxy
- When running in supervised mode, config files from extensions are NOT evaluated (only when running
ocis server
, runs withocis run extension
do parse individual config files)- i.e: present config files:
ocis.yaml
andproxy.yaml
; only the contents ofocis.yaml
are loaded1.
- i.e: present config files:
- Flag parsing for subcommands are not allowed in this mode, since the runtime is in control. Configuration has to be done solely using config files.
*[1] see the development section for more on this topic.
> ocis --config-file /etc/ocis/ocis.yaml server
does not work. It currently only supports reading global config values from the predefined locations.
ocis.yaml
is parsed first (sinceproxy
is a subcommand ofocis
)proxy.yaml
is parsed if present, overriding values fromocis.yaml
and any cli flag or env variable present.
- Configure via env + some configuration files like WEB_UI_CONFIG or proxy routes
- Configure via flags + some configuration files like WEB_UI_CONFIG or proxy routes
- Configure via global (single file for all extensions) config file + some configuration files like WEB_UI_CONFIG or proxy routes
- configure via per extension config file + some configuration files like WEB_UI_CONFIG or proxy routes
Each individual use case DOES NOT mix sources (i.e: when using cli flags, do not use environment variables nor cli flags).
Limitations on urfave/cli prevent us from providing structured configuration and framework support for cli flags + env variables.
Sometimes is desired to decouple the main series of services from an individual instance. We want to use the runtime to startup all services, then do work only on a single service. To achieve that one could use ocis server && ocis kill proxy && ocis run proxy
. This series of commands will 1. load all config from ocis.yaml
, 2. kill the supervised proxy service and 3. start the same service with the contents from proxy.yaml
.
Flag parsing on subcommands in supervised mode is not yet allowed. The runtime will first parse the global ocis.yaml
(if any) and run with the loaded configuration. This use case should provide support for having 2 different proxy config files and making use of the runtime start 2 proxy services, with different values.
For this to work, services started via Service.Start
need to forward any args as flags:
if err := client.Call("Service.Start", os.Args[2], &reply); err != nil {
log.Fatal(err)
}
This should provide with enough flexibility for interpreting different config sources as: > bin/ocis run proxy --config-file /etc/ocis/unexpected/proxy.yaml
Let’s develop further the following concept: Adapt the “structured config files have the highest priority” within oCIS.
Of course it directly contradicts urfave/cli priorities. When a command finished parsing its cli args and env variables, only after that Before
is called. This mean by the time we reach a command Before
hook, flags have already been parsed and its values loaded to their respective destinations within the Config
struct.
This should still not prevent a developer from using different config files for a single service. Let’s analyze the following use case:
- global config file present (ocis.yaml)
- single proxy.yaml config file
- another proxy.yaml config file
- running under supervision mode
The outcome of the following set of commands should be having all bootstrapped services running + 2 proxies on different addresses:
> ocis server
> ocis kill proxy
> ocis run proxy --config-file proxy.yaml
> ocis run proxy --config-file proxy2.yaml
This is a desired use case that is yet not supported due to lacking of flags forwarding.
- Variadic runtime extensions to run (development mostly)
- Arg forwarding to command (when running in supervised mode, forward any –config-file flag to supervised subcommands)
- Ability to set
OCIS_URL
from a config file (this would require to extend the ocis-pkg/config/config.go file).
OCIS_URL
is a jack-of-all trades configuration. It is meant to ease up providing defaults and ensuring dependant services are well configured. It is an override to the following env vars:
OCIS_IDM_ADDRESS
PROXY_OIDC_ISSUER
STORAGE_OIDC_ISSUER
STORAGE_FRONTEND_PUBLIC_URL
STORAGE_LDAP_IDP
WEB_UI_CONFIG_SERVER
WEB_OIDC_AUTHORITY
OCIS_PUBLIC_URL
Because this functionality is only available as an env var, there is no current way to “normalize” its usage with a config file. That is, there is no way to individually set OCIS_URL
via config file. This is clear technical debt, and should be added functionality.
- Kubernetes proposal on this very same topic
- Configuration | Pulumi
- Configuration can be altered via setters through the CLI.