Values storage
The Addon-operator provides the storage for the values that will be passed to the Helm chart. You may find out more about the chart values concept in the Helm documentation: values files. Global and module hooks have access to the values in the storage and can change them.
The storage is a hash-like data structure. The global
key contains all global values – they are passed to every hook and available to all Helm charts. Only global hooks may change global values.
The other keys must match the module’s name converted to camelCase. Each key stores the object with module values. These values are only available to hooks, enabled
script of this module, and to its Helm chart. Only module hooks can change the values of the module.
Note: You cannot get the values of another module within the module hook. Shared values should be global values for now (#9).
Hook receives values via files on execution. These schemas can help you understand the flow of values for a global hook and for a module hook:
The values can be represented as:
- a structure (including empty structure)
- a list (including empty list)
Structures and lists must be JSON-compatible since hooks receive values at runtime as JSON files (see using values in hook).
Note: each module has an additional key with
Enabled
suffix and a boolean value to enable or disable the module (e.g.,ingressNginxEnabled: false
). This key is handled by modules discovery process.
values.yaml
On start-up, the Addon-operator loads values into storage from values.yaml
files:
$MODULES_DIR/values.yaml
values.yaml
files in modules directories — only the values from key with camelCase name of the module
An example of global values in $MODULES_DIR/values.yaml
:
global:
param1: value1
param2: value2
simpleModule:
modParam1: value3
An example of module values in $MODULES_DIR/001-simple-module/values.yaml
:
simpleModule:
modParam1: value1
modParam2: value2
ConfigMap/addon-operator
There is a key global
in the ConfigMap/addon-operator that contains global values and the keys with module values. The values are stored in these keys as the YAML encoded strings. Values in the ConfigMap/addon-operator override the values loaded from values.yaml
files.
The Addon-operator monitors changes in the ConfigMap/addon-operator and starts the ‘reload all modules’ process in case of global values changes or ‘module run’ process if only the module section is changed. See LIFECYCLE.
An example of ConfigMap/addon-operator:
data:
global: | # vertical bar is required here
param1: newValue
param3: valu3
simpleModule: | # module name should be in camelCase
modParam2: newValue2
anotherModule: "false" # `false' value disables a module
Update values
Hooks can update values in the storage. To do that the hook returns a JSON Patch.
A hook can update values in the ConfigMap/addon-operator so that the updated values would be available after restarting the Addon-operator (long-term update). For example, you may store generated passwords or certificates.
Patch for a long-term update is returned via the $CONFIG_VALUES_JSON_PATCH_PATH
file and after hook execution, the Addon-operator immediately applies this patch to the values in ConfigMap/addon-operator.
Another option is to store updated values for a period while the Addon-operator process is running. For example, you may store the results of the discovery of cluster resources or parameters.
Patch for temporary updates is returned via the $VALUES_JSON_PATCH_PATH
file and remains in the Addon-operator volatile memory.
Merged values
When the hook or enabled
script is about to be executed, or a Helm chart is to be installed, the Addon-operator generates a merged set of values. This merged set combines:
- global values from
values.yaml
files and ConfigMap/addon-operator; - module values from the
values.yaml
files and ConfigMap/addon-operator; - patches for the temporary updates are applied.
The merged values are passed as the temporary JSON file to hooks or enabled
script and as the temporary values.yaml
file to the helm install
.
Using values in the hook
When the hook is triggered by an event, the values are passed to it via JSON files. The hook can use environment variables to get paths of those files:
$CONFIG_VALUES_PATH
— this file contains values from the ConfigMap/addon-operator.$VALUES_PATH
— this file contains merged values.
For global hooks, only global values are available.
For module hooks the global values and the module values are available. Also, the enabledModules
field is added to the global
values in the $VALUES_PATH
file. It contains the list of all enabled modules in the order of execution (see module lifecycle).
To change the values, the hook must return JSON patches via the result files. The hook can use environment variables to get paths of those files:
$CONFIG_VALUES_JSON_PATCH_PATH
— hook should write a patch for ConfigMap/addon-operator into this file.$VALUES_JSON_PATCH_PATH
— hook should write a patch for a temporary update of parameters into this file.
Using the values in enabled
scripts
The enabled
script works with values in the read-only mode. It receives values in JSON files. The script can use environment variables to get paths of those files:
$CONFIG_VALUES_PATH
— this file contains values from ConfigMap/addon-operator.$VALUES_PATH
— this file contains merged values.
The enabledModules
field with the list of previously enabled modules is added to the global
key in the $VALUES_PATH
file.
Using values in Helm charts
Helm chart of the module has access to the merged values similar to the $VALUES_PATH
but without enabledModules
field.
The Helm template’s variable .Values
allows you to use values in the templates:
{{ .Values.global.param1 }}
{{ .Values.moduleName.modParam2 }}
Example
Let’s assume the following values are defined:
$ cat modules/values.yaml:
global:
param1: 100
param2: "Yes"
$ cat modules/01-some-module/values.yaml
someModule:
param1: "String"
$ kubectl -n addon-operator get cm/addon-operator -o yaml
data:
global: |
param1: 200
someModule: |
param1: "Long string"
param2: "FOO"
The Addon-operator generates the following files with values:
$ cat $CONFIG_VALUES_PATH
{"global":{
"param1":200
}, "someModule":{
"param1":"Long string",
"param2": "FOO"
}}
$ cat $VALUES_PATH
{"global":{
"param1":200,
"param2": "YES"
}, "someModule":{
"param1":"Long string",
"param2": "FOO"
}}
A hook adds a new value with the help of a JSON patch:
$ cat /modules/001-some-module/hooks/hook.sh
#!/usr/bin/env bash
...
cat > $CONFIG_VALUES_JSON_PATCH_PATH <<EOF
[{"op":"add", "path":"/someModule/param3", "value":"newValue"}]
EOF
...
Now the ConfigMap/addon-operator has the following content:
data:
global: |
param1: 200
someModule: |
param1: "Long string"
param2: "FOO"
param3: "newValue"
Next time the hook is executed, the Addon-operator would generate the following files with values:
$ cat $CONFIG_VALUES_PATH
{"global":{
"param1":200
},
"someModule":{
"param1":"Long string",
"param2": "FOO",
"param3": "newValue"
}}
$ cat $VALUES_PATH
{"global":{
"param1":200,
"param2": "YES"
}, "someModule":{
"param1":"Long string",
"param2": "FOO",
"param3": "newValue"
}}
Helm chart template
replicas: {{ .Values.global.param1 }}
would generate the string replicas: 200
. As you can see, the value “100” from the values.yaml is replaced by “200” from the ConfigMap/addon-operator.
Validation
The addon-operator supports OpenAPI schemas for config values and for effective values. These schemas should be stored in the $GLOBAL_HOOKS_DIR/openapi
directory for global values and in the $MODULES_DIR/<module-name>/openapi
directories for modules.
openapi/config-values.yaml
is a schema for values merged from values.yaml, modules/values.yaml and the ConfigMap.
openapi/values.yaml
is a schema for values merged from values.yaml, modules/values.yaml and the ConfigMap with applied values patches.
Validation occurs on startup, on ConfigMap changes, and after hook executions. If validation fails after hook execution, hook is restarted. If validation fails on startup, the addon-operator stops. If validation fails on ConfigMap changes, error is logged and no new tasks are queued.
Note: Unlike the default behavior, the addon-operator sets
additionalProperties: false
ifadditionalProperties
is not set.
Example
# /global/openapi/config-values.yaml
type: object
additionalProperties: false
required:
- project
- clusterName
minProperties: 2
properties:
project:
type: string
clusterName:
type: string
clusterHostname:
type: string
discovery:
type: object
This schema defines 2 required fields for ‘global’ values: project
and clusterName
. clusterHostname
field is an optional string. discovery
is an optional object with no restrictions on keys.
Consider this ConfigMap/addon-operator
content:
metadata:
...
data:
global: |
project: myProject
moduleOne: |
param1: value1
...
This ConfigMap has invalid ‘global’ values, and the addon-operator stops with an error on startup.
Consider valid ConfigMap/addon-operator
and this config patch from global hook:
[{"op":"add", "path":"/global/clusterHostname", "value":"{}"}]
This patch sets clusterHostname
field in the ‘global’ section. It is not allowed because schema defines clusterHostname
as a string. This situation is handled like a hook execution error, the hook stays in queue and restarts with exponential backoff (see LIFECYCLE.
Extending
Values are config values with applied patches, so schema in values.yaml should contain duplicates of properties from config-values.yaml schema. There is a technique with allOf
to reduce duplicates, but it will not eliminate duplicates when additionalProperties: false
. To overcome this problem, we implement custom property x-extend
for values.yaml schema.
If values.yaml schema contains x-extend
field, shell-operator extends fields in values.yaml schema with fields from config-values.yaml schema:
- definitions
- required
- properties
- patternProperties
- title
- description
Also, “x-*“ properties copied from config-values.yaml schema.
Example
Consider these OpenAPI schemas:
# /global/openapi/config-values.yaml
type: object
additionalProperties: false
required:
- project
- clusterName
properties:
project:
type: string
clusterName:
type: string
clusterHostname:
type: string
# /global/openapi/values.yaml
x-extend:
schema: config-values.yaml
type: object
additionalProperties: false
required:
- discovery
- param1
properties:
discovery:
type: object
param1:
type: string
The addon-operator will validate values with this effective schema:
# effective schema for values
type: object
additionalProperties: false
required:
- project
- clusterName
- discovery
- param1
properties:
project:
type: string
clusterName:
type: string
clusterHostname:
type: string
discovery:
type: object
param1:
type: string
Defaults
The addon-operator respects default
key in schemas and apply defaults when merge values.
Example
Consider this schema for global values:
# /global/openapi/values.yaml
x-extend:
schema: config-values.yaml
type: object
additionalProperties: false
required:
- param1
properties:
discovery:
type: object
default:
{}
param1:
type: string
The addon-operator will add discovery
with empty object to values if no discovery
key is present in the ConfigMap, modules/values.yaml
or in patches.
Required fields
There is a problem with required
fields defined in openapi/values.yaml
: values for Helm can be constructed by multiple hooks. Different hooks return different portions of required
fields and validation will fail on hook execution. To define a contract for Helm values in this situation, the addon-operator implements x-required-for-helm
to define required values for Helm. Values are checked before helm execution with x-required-for-helm
array merged with required
.
Example
Suppose we have two hooks: one hook prepares a param1
value and the second hook prepares a param2
value. Helm required both fields, but we can’t require both fields after each hook execution. x-required-for-helm
to the rescue:
# /global/openapi/values.yaml
type: object
x-required-for-helm:
- param1
- param2
properties:
param1:
type: string
param2:
type: string
The addon-operator will validate values after each hook execution with this effective schema:
# effective schema for values
type: object
additionalProperties: false
properties:
param1:
type: string
param2:
type: string
The addon-operator will validate values before Helm execution with this effective schema:
# effective schema for values
type: object
additionalProperties: false
required:
- param1
- param2
properties:
param1:
type: string
param2:
type: string