Helm split global section - yaml

I have a helm values.yaml file like below
global:
foo: bar
foo1: bar1
random-chart:
fooo: baar
My use case is to append values in both global and random-chart during run time.
After appending values, the chart looks like this.
global:
foo: bar
foo1: bar1
random-chart:
fooo: baar
global:
secret: password
random-chart:
secret1: password1
Since there's 2 different global and random-chart keys. Will it work as intended and is it a good practice to do that?

This probably won't work as intended.
The YAML 1.2.2 spec notes (emphasis from original):
The content of a mapping node is an unordered set of key/value node pairs, with the restriction that each of the keys is unique.
And in discussing loading errors continues:
... mapping keys may not be unique ....
So the YAML file you show has a mapping with two keys both named global and two keys both named random-chart, and that's not valid. Depending on the specific YAML library that's being used, this might be interpreted as a loading error, or the library might just pick the last value of global.
In general, it's hard to work with YAML files using line-oriented shell tools, since there are so many syntactic variations. A dedicated library in a higher-level language will usually work better. For example, using the Python PyYAML library:
import yaml
with open('values.in.yaml', 'r') as f:
values = yaml.safe_load(f)
values['global']['secret'] = 'password'
values['random-chart']['secret-1'] = 'password1'
with open('values.out.yaml', 'w') as f:
yaml.dump(values. f)
Two other possibilities to consider: you can have multiple helm install -f options, so it's possible to write out a file with just the values you're adding, and those will be merged with other settings (you do not need to repeat the values from the chart's values.yaml file). Depending on your environment, you also may find it easier to dynamically write out JSON files, particularly if you don't need to re-read the base chart; setups like Jenkins or Javascript applications will have built-in JSON support, and valid JSON turns out to be valid YAML.

Related

Combining anchor and alias in one line in triggers parserError: 'Expected <block end>, but found '<alias>'

I am trying to combine anchors and aliases in order to reuse values into several containers, under different names.
I tried the following code:
FWL_GV_NANSEN: &fwl_gv_nansen
dtype: float
value: 2715.0
FWL_GV_E3_2: &fwl_gv_e32 *fwl_gv_nansen
the goal is simply to have another variable FWL_GV_E3_2 containing the same information than FWL_GV_NANSEN that I could refer to later on.
Just the same than defining in Python (or other):
a = 5.0
b = a
c = b
But this triggers the following error message:
yaml.parser.ParserError: while parsing a block mapping
in "fwl_2.yml", line 7, column 3
expected < block end >, but found '< alias >'
Is there no way to assign the content of aliases to variable used to defined a new anchor (propagating the initial values through different variables?
PS: maybe YAML is not the best language for this since it would be trivial using python's variables for example but I have to use YAML
The YAML node properties (i.e. tags and anchors) can only occur on "real" nodes: collections (block or flow) or scalars.
You can see from the production rules that c-ns-properties can be used only there, but section for aliases also states explicitly:
Note that an alias node must not specify any properties [...]
What is possible if your YAML parser gives you access to the original anchor/aliases, or some anchor-to-node/object mapping (such as my ruamel.yaml package for Python when used for round-tripping), is to use a tagged scalar:
FWL_GV_NANSEN: &fwl_gv_nansen
dtype: float
value: 2715.0
FWL_GV_E3_2: &fwl_gv_e32 !ref fwl_gv_nansen
with the constructor of the !ref object resolving the scalar fwl_gv_nansen, with some application specific code. Normally the anchor and alias events are resolved by the composer loader step before composing (and if so that information is no longer available).

How to Reference an aliased map value in YAML

I have a feeling this isn't possible, but I have a snippet of YAML that looks like the following:
.map_values: &my_map
a: 'D'
b: 'E'
a: 'F'
section:
stage: *my_map['b']
I would like stage to have the value of E.
Is this possible within YAML? I've tried just about every incarnation of substitution I can think of.
Since there is a duplicate key in your mapping, which is not allowed
in YAML 1.2 (and should at least throw a warning in YAML 1.1) this is
not going to work, but even if you correct that, you can't do that
with just anchors and aliases.
The only substitution like replacement that is available in YAML is the "Merge Key Language-Independent Type". That is indirectly referenced in the YAML spec, and not included in it, but available in most parsers.
The only thing that allows it to do is "update" a mapping with key value pairs of one or more other mappings, if the key doesn't already exist in the mapping. You use the special key << for that, which takes an alias, or a list of aliases.
There is no facility, specified in the YAML specification, to dereference particular keys.
There are some systems that use templates that generate YAML, but there are two main problems to apply these here:
the template languages themselves often are clashing with the indicators in the YAML syntax,
making the template not valid YAML
even if the template could be loaded as valid YAML, and the values extracted that are needed to
update other parts of the template, you would need to parse the input twice (once to get the
values to update the template, then to parse the updated template). Given the potential
complexity of YAML and the relative slow speed of its parsers, this can be prohibitive
What you can do is create some tag (e.g. !lookup) and have its constructor interpret that node.
Since the node has to be valid YAML again you have to decide on whether to use a sequence or a mapping.
You'll have to include some special syntax for the values in both cases, and also for the key
(like the << used in merges) in the case of mappings.
In the examples I left out the spurious single quotes, depending on
your real values you might of course need them.
Example using sequence :
.map_values: &my_map
a: D
b: E
c: F
section: !Lookup
- *my_map
- stage: <b>
Example using mapping:
.map_values: &my_map
a: D
b: E
c: F
section: !Lookup
<<: *my_map
stage: <b>
Both can be made to construct the data on the fly (i.e. no past
loading processing of your data structure necessary). E.g. using Python and
the sequence "style" in input.yaml:
import sys
import ruamel.yaml
from pathlib import Path
input = Path('input.yaml')
yaml = ruamel.yaml.YAML(typ='safe')
yaml.default_flow_style = False
#yaml.register_class
class Lookup:
#classmethod
def from_yaml(cls, constructor, node):
"""
this expects a two entry sequence, in which the first is a mapping X, typically using
an alias
the second entry should be an mapping, for which the values which have the form <key>
are looked up in X
non-existing keys will throw an error during loading.
"""
X, res = constructor.construct_sequence(node, deep=True)
yield res
for key, value in res.items():
try:
if value.startswith('<') and value.endswith('>'):
res[key] = X[value[1:-1]]
except AttributeError:
pass
return res
data = yaml.load(input)
yaml.dump(data, sys.stdout)
which gives:
.map_values:
a: D
b: E
c: F
section:
stage: E
There are a few things to note:
using <...> is arbitrary, you don't need a both beginning and an
end marker. I do recommend using some character(s) that has no
special meaning in YAML, so you don't need to quote your values. You can e.g. use some
well recognisable unicode point, but they tend to be a pain to type in an editor.
when from_yaml is called, the anchor is not yet fully constructed. So X is an empty dict
that gets filled later on. The constructed with yield implements a two step process: we first
give back res "as-is" back to the constructor, then later update it. The constructor stage of
the loader knows how to handle this automatically when it gets the generator instead a "normal" value.
the try .. except is there to handle mapping values that are not strings (i.e. numbers, dates, boolean).
you can do substitutions in keys as well, just make sure you delete the old key
Since tags are standard YAML, the above should be doable one way or another in any
YAML parser, independent of the language.

merge multiple YAML files under a new field

I have several YAML files representing the pieces of a whole. I want to merge them under a new field ("guests") that declares the whole.
file1.yml
name: johnny
age: 23
file2.yml
name: sally
age: 21
output.yml
guests:
- name: johnny
age: 23
- name: sally
age: 21
tools like yq make merging/overwriting easy, but I can't find any that helps me nest values under new fields.
The tool you are looking for comes with several different names and
are called programming languages or scripting languages. I recommend
you use Python with ruamel.yaml installed. (disclaimer: I am the author of
that package).
Once you have that you can do:
python -c "import sys, ruamel.yaml; yaml=ruamel.yaml.YAML(); yaml.indent(sequence=4, offset=2); yaml.dump(dict(guest=[yaml.load(open(f)) for f in sys.argv[1:]]), sys.stdout)" file*.yml > output.yml
To get the desired output.
A few notes:
YAML files should have the .yaml extension unless your filesystem doesn't support that.
By default sequence elements are indented two spaces and the dash has no offset within that (i.e. would align with the g of guests. Hence the yaml.indent() call.
Any comments on the key-value files of your input file would be preserved but not automatically pushed out to the right from their original starting column unless necessary because of a mapping value getting in the way. Adjusting that is possible, but I would not recommend trying that in a one-liner.
If you need to preserve quotes add yaml.preserve_quotes = True; in the one-liner
If any of your YAML files contain multiple YAML documents, the above will fail. You would need to think about how to combine the documents, and use a try except clause to fall back to yaml.load_all() for documents that do (it would be a good idea to abandon the one-liner in favour of a multiline Python program at that point).
You can also do the above using the yaml commandline utility (installable with pip install ruamel.yaml.cmd>=0.5.0):
yaml from-dirs --sequence ./*.yml | yaml map --indent 2,4,2 guest - > output.yml
but this is a two step process (first combining multiple yaml files as root level sequence, then pushing that sequence to be a value for mapping), and thus twice as slow as the one-liner.

Is there a way to alias/anchor an array in YAML?

I'm using Jammit to package assets up for a Rails application and I have a few asset files that I'd like to be included in each of a few groups. For example, I'd like Sammy and its plugins to be in both my mobile and screen JS packages.
I've tried this:
sammy: &SAMMY
- public/javascripts/vendor/sammy.js
- public/javascripts/vendor/sammy*.js
mobile:
<<: *SAMMY
- public/javascripts/something_else.js
and this:
mobile:
- *SAMMY
but both put the Sammy JS files in a nested Array, which Jammit can't understand. Is there a syntax for including the elements of an Array directly in another Array?
NB: I realize that in this case there are only two elements in the SAMMY Array, so it wouldn't be too bad to give each an alias and reference both in each package. That's fine for this case, but quickly gets unmaintainable when there are five or ten elements that have a specific load order.
Closest solution I know of is this one:
sammy:
- &SAMMY1
public/javascripts/vendor/sammy.js
- &SAMMY2
public/javascripts/vendor/sammy*.js
mobile:
- *SAMMY1
- *SAMMY2
- public/javascripts/something_else.js
Alternatively, as already suggested, flatten the nested lists in a code snippet.
Note: according to yaml-online-parser, your first suggestion is not a valid use of << (used to merge keys from two dictionaries. The anchor then has to point to another dictionary I believe.
If you want mobile to be equal to sammy, you can just do:
mobile: *SAMMY
However if you want mobile to contain other elements in addition to those in sammy, there's no way to do that in YAML to the best of my knowledge.
Your example is valid YAML (a convenient place to check is YPaste), but it's not defined what the merge does. Per the spec, a merge key can have a value:
A mapping, in which case it's merged into the parent mapping.
A sequence of mappings, in which case each is merged, one-by-one, into the parent mapping.
There's no way of merging sequences on YAML level.
You can, however, do this in code. Using the YAML from your second idea:
mobile:
- *SAMMY
you'll get nested sequences - so flatten them! Assuming you have a mapping of such nested sequences:
data = YAML::load(File.open('test.yaml'))
data.each_pair { |key, value| value.flatten! }
(Of course, if you have a more complicated YAML file, and you don't want every sequence flattened (or they're not all sequences), you'll have to do some filtering.)
This solution is for Symfony/PHP only (considerations for other languages, see below)
Note about array keys from the PHP array manual page:
Strings containing valid decimal ints, unless the number is preceded by a + sign, will be cast to the int type. E.g. the key "8" will actually be stored under 8. [...]
This means that if you actually index your anchor array with integer keys, you can simply add new keys by continuing the initial list. So your solution would look like this:
sammy: &SAMMY
1: public/javascripts/vendor/sammy.js
2: public/javascripts/vendor/sammy*.js
mobile:
<<: *SAMMY
3: public/javascripts/something_else.js
You can even overwrite keys and still add new ones:
laptop:
<<: *SAMMY
1: public/javascripts/sammy_laptop.js
3: public/javascripts/something_else.js
In both cases the end result is a perfectly valid indexed array, just like before.
Other programming languages
Depending on your YAML implementation and how you iterate over your array, this could conceivably also be used in other programming languages. Though with a caveat.
For instance, in JS you can access numerical string keys by their integer value as well:
const sammy = {"1": "public/javascripts/vendor/sammy.js"}
sammy["1"]; // "public/javascripts/vendor/sammy.js"
sammy[1]; // "public/javascripts/vendor/sammy.js"
But you'd need to keep in mind, that your initial array is now an object, and that you would need to iterate over it accordingly, e.g.:
Object.keys(sammy).forEach(key => console.log(sammy[key]))
As it has been suggested, when you need to flatten a list, at least in ruby, it is trivial to add a "!flatten" type specifier to mobile and implement a class that extends Array, adds the yaml_tag and flattens the coder seq on init_with.

Import a YAML (fixtures) file from another

for better organization (for eg: seed and test data), is it
possible to split the yaml file and import the first one from second.
Of course, the variables in file1 should be available to use in the
second. I use Snakeyaml based parser i java, if that matters.
thanks.
Update 1: (example)
Seed file: seed.yaml
Priority(L1E1):
level: 1
priorityCode: E1
description: Escalation
test data file: test-data.yaml
Request(RER1):
priority: L1E1
title: Something
So, I need to split the files, as they becoming huge. Also the variable/data (L1E1 in this case) defined in one file needs to be accessible in the second file.
YAML does not define an "include" directive.
What do you mean by "the variables in file1 should be available to use in the second" ? Do you expect anchors and aliases to work across the files ?

Resources