Internal architecture: Inputs, Outputs and resolving
Novops relies around the following concepts:
Modules, Inputs, resolving & Outputs
Inputs are set in .novops.yml
to describe how to load value. They usually reference an external secret/config provider or a clear-text value.
Modules are responsible for generating outputs from inputs by resolving them. For example, hvault_kv2
module load secrets from Hashicorp Vault KV2 engine:
# hvault_k2 input: reference secrets to load
hvault_kv2:
path: myapp/creds
key: password
Outputs are objects based obtained from Inputs when they are resolved. Currently only 2 types of Output exists:
- Files
- Environment variables (as a sourceable file)
hvault_kv2
example would output a String
value such as
myPassw0rd
Inputs can be combined with one-another, for example this .novops.yml
config is a combination of Inputs:
environments:
# Each environment is a complex Input
# Providing both Files and Variable outputs
dev:
# variables is itself an Inputs containing a list of others Inputs
# Each Variables Inputs MUST resolve to a String
variables:
# variable input and it's value
# It can be a plain string or any Input resolving to a string
- name: APP_PASS
value:
hvault_kv2:
path: myapp/creds
key: password
- name: APP_HOST
value: localhost:8080
# files is an Inputs containing a list of other Inputs
# Each file input within resolve to two outputs:
# - A file output: content and path
# - A variable output: path to generated file
files:
- name: APP_TOKEN
# Content takes an Input which must resolve to a string or binary content
content:
hvault_kv2:
path: myapp/creds
key: api_token
When running, novops load
will:
- Read config file and parse all Inputs
- Resolve all Inputs to their concrete values (i.e. generate Outputs from Inputs)
- Export all Outputs to system (i.e. write file and provide environment variable values)
Resolving mechanism is based on ResolveTo
trait implemented for each Input. An example implementation for HashiVaultKeyValueV2
into a String
can be:
#![allow(unused)] fn main() { // Example dummy implementation resolving HashiVaultKeyValueV2 as a String impl ResolveTo<String> for HashiVaultKeyValueV2 { async fn resolve(&self, _: &NovopsContext) -> Result<String, anyhow::Error> { let vault_client = build_vault_client(ctx); return Ok( vault_client.kv2.read(&self.mount, &self.path, &self.key).unwrap().to_string() ) } } }
See src/core.rs
for details.
Novops config schema and internal structure
Novops config is generated directly from internal Rust structure of Inputs deriving JsonSchema
from the root struct core::NovopsConfigFile
For instance:
#![allow(unused)] fn main() { #[derive(/* ... */ JsonSchema)] pub struct NovopsConfigFile { pub environments: HashMap<String, NovopsEnvironmentInput>, pub config: Option<NovopsConfig> } }
Define top-level Novops config schema:
environments:
dev: # ...
prod: # ...
config: # ...
Top level structure of config (leaf are plain values):
graph LR;
name;
environments --> variables
variables --> varName("name")
variables --> varValue("value")
varValue --> anyStringInputVar("<i>any String-resolvable input</i>")
environments --> files
files --> fileName(name)
files --> fileVar(variable)
files --> fileContent(content)
environments --> aws
aws --> awsmoduleinput(<i>AWS module input...</i>)
environments --> otherModule
otherModule("<i>Other module name...</i>") --> otherModuleInput(<i>Other module input...</i>)
fileContent --> anyStringInputFile("<i>any String-resolvable input</i>")
config --> default
config --> hvaultconfig(hashivault)
config --> othermodconf2(Other modules config...)