Skip to content

Instantly share code, notes, and snippets.

@refs
Last active May 3, 2021 11:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save refs/3be44c1d216caff2081e04aeb6ad5d1b to your computer and use it in GitHub Desktop.
Save refs/3be44c1d216caff2081e04aeb6ad5d1b to your computer and use it in GitHub Desktop.

How many ways does ocis load config

The precedence for flag value sources is as follows (highest to lowest):

  1. Command line flag value from user
  2. Environment variable (if specified)
  3. Configuration file (if specified)
  4. 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.

Problem Statement

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, say, load values from a file.

Because of 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.

Considered Options

  • Extend FlagInputSourceExtension interface
    • we could use Viper to load from config files here and apply values to the flags in the context (?)
    • the main drawback for this approach is that the urfave/cli team are actively working on v3 of altsrc
  • Feature request: support for structured configuration (urfave/cli).
    • the intention here is to remove Viper off the codebase and solely rely on urfave/cli native code.
    • drawback: there are no plans to support this
  • Clearly defined boundaries of what can and cannot be done.
    • do we want to support all options and the interactions between them?
    • do we want to implement something on top of the framework or independent of it? We would like to stick to the framework as much as possible since learning yet another exception to config parsing is undesirable.
  • Expose structured field values as CLI flags
    • Something simmilar is depicted here in point 5.
    • It would require quite a bit1 of custom logic.
    • Should these flags be present in the -h menu of a subcommand? Probably some code generation needed.
  • Drop support for structure configuration
    • Since it is not supported by the framework we use
    • Not encouraged by the 12factor app spec
  • Adapt the "structured config files have the highest priority" within oCIS
    • No 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.
    • Will it support all the wanted use-cases?

*[1] this is an uncertainty.

Use Cases and Expected Behaviors

Supervised (ocis server or ocis run extension)

cbad372c.png

  • Use a global config file (ocis.yaml) to configure an entire mesh 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.
    • i.e: present config files: ocis.yaml and proxy.yaml; only the contents of ocis.yaml are loaded1.
  • 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.

Known Bugs

  • > ocis --config-file /etc/ocis/ocis.yaml server does not work. It currently only supports reading global config values from the predefined locations.

Unsupervised (ocis proxy)

  • ocis.yaml is parsed first (since proxy is a subcommand of ocis)
  • proxy.yaml is parsed if present, overriding values from ocis.yaml and any cli flag or env variable present.

Other known use cases

  • 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.

Use Cases for Development

Config Loading

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.

Start an extension multiple times with different configs (in Supervised mode)

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

Developing Considered Alternatives Further

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:

  1. global config file present (ocis.yaml)
  2. single proxy.yaml config file
  3. another proxy.yaml config file
  4. 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.

Follow up PR's

  • Variadic runtime extensions to run (development mostly)
  • Arg forwarding to command (when running in supervised mode, forward any --config-file flag to supervised subcommands)

State of the Art

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment