Extract from YAML values dynamically - yaml

Consider the following yaml file:
topics:
topicA:
bins:
type: multi-bins
map:
FirstBin:
source: value-field
field-name: ServiceID
SecondBin:
source: value-field
field-name: ServiceID
message-transformer:
class: com.aerospike.connect.inbound.transforms.TombstoneMessageTransformer
params:
shouldDeleteOnNull: "FirstBin, SecondBin"
topicB:
...
As you can see there duplication between the topics.<topic-name>.bins.map.keys and topics.<topic-name>.message-transformer.params.shouldDeletedOnNull
Is there a way to extract the values dynamically? I want to send to shouldDeleteOnNull all the keys of topics.<topic-name>.bins.map
Note: I don't want to create an env variable for that and use Yaml anchors.

YAML is not a programming language. It doesn't let you extract anything and it doesn't let you send anything, because it does not provide you with any kind of actions or processing instructions.
While you can reference nodes multiple times with anchors, that doesn't help you here because you would need to concatenate values, which is not something that is possible in YAML. A solution that would work looks like this (shortened for clarity):
map:
&a FirstBin:
source: value-field
field-name: ServiceID
&b SecondBin:
source: value-field
field-name: ServiceID
shouldDeleteOnNull: [*a, *b]
As you can see, I needed to make shouldDeleteOnNull a sequence rather than a scalar to make this work. This does not seem like much of an improvement.
Anything more sophisticated would need to be implemented in the code loading the file and therefore does not make sense to be discussed in a pure YAML context.

Related

In YAML how to use value from another key

I have a YAML file like below.
Details:
Name: Jack
Location: USA
ABC: TestValue
Refer:
Test1: %Details.Name%
Test2: %Details.Location%
Wanted to check if the value given in Test1 works? As I know if %Details.Name% is present under parameters. But above keys are not parameters. So, is there any way to refer the values from another key?
There are anchors and aliases, which you can use like this:
Details:
Name: &name Jack
Location: &location USA
ABC: TestValue
Refer:
Test1: *name
Test2: *location
However, there is no way to refer to other values via their „paths“. Applications using YAML may support pre- or postprocessing to do this (often via templating engines like Jinja), but plain YAML doesn't implement this feature.
If you are under the impression that %Details.Name% would work in some context, you are already using a pre- or postprocessing feature that is not plain YAML.

Helm split global section

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.

How to encode "structure contains one or more X" in a .yaml file

I'm trying to define an OpenAPI interface in a .yaml file.
The interface specification says that one of the parameters will be a data structure foo containing, among other things, one or more members of a second data structure bar. In C++ I would define a structure containing a vector of bar but I cannot figure out how to encode this in YAML. I tried making a list with just one item, and Swagger Editor complained:
# ... details omitted
- in: header
name: foo
required: true
schema:
type: object
properties:
identity:
type: integer
version:
type: integer
- bar: # <<<<< bad indentation of a mapping entry
type: object
properties:
key:
type: string
value:
type: string
This doesn't look like it's really an indentation problem. I say this because the only level of indentation that removes the error is back out level with the in. Note that, if I remove the dash and following space, the editor shows no errors, so I'm pretty sure there are no other issues here.
main question How can I define this parameter as something containing one or more structures of type bar?
bonus question Is there a way to name the structures I'm defining, e.g. KeyValuePair for bar?
This doesn't look like it's really an indentation problem.
No, but it is a syntactic error that has nothing to do with OpenAPI (or JSON Schema, which is what is most relevant for your problem). YAML defines the structure of your document, and you have made a structural error.
This snippet is enough to see the error:
version:
type: integer
- bar:
The key version: shows that we are inside a mapping here, i.e. a list of key-value pairs. What follows is a nested mapping with the single entry type: integer. Then however, a - follows on the indentation level of version:. In YAML, this is a sequence indicator. But there is no sequence at the indentation level of version:; instead, this indentation level holds a mapping. You cannot have a sequence item as part of a mapping, since a mapping contains key-value pairs. The error message could of course be a better one.
Now what you actually want to do is to define an array of bar (JSON term for a vector of bar):
version:
type: integer
bar:
type: array
items:
type: object
properties:
key:
type: string
value:
type: string
Be aware of the important difference between the syntactic YAML structure of your input, and the desired semantic of your input. You want to describe an array in your JSON Schema, but that does not mean that you have to use a YAML sequence for that. You also don't write 42 if you want to define an integer-type field.
Also be aware that bar is the name of the field, not the name of the type. You can reference previously defined types with $ref.

How can I set/get a HashMap in a YAML configuration file?

I am making my first bukkit plugin. I would like to programmatically create a YAML file that represents a HashMap. How can I set and get this data structure?
The HashMap parameters look like <Signature, Location>, where Signature is my class that stores 4 integers, and Location is org.bukkit.Location
I think I would like the YAML file to look like this, but I am not sure if this structure is best:
MyPlugin:
ListOfData:
- signature: [1,2,3,4] # this is a unique set of 4 integers
location: [122,64,254] # non-unique set of 3 integers
- signature: [4,2,1,2]
location: [91,62,101]
- signature: [3,3,1,3]
location: [190,64,321]
Signature can be modified as necessary, and I can create a wrapper for Location if necessary.
Thanks!
This is a suggested solution. I don't know if it is the best way...:)
You may want to consider this as your yaml structure:
MyPlugin:
ListOfData:
'[1,2,3,4]': '[122,64,254]'
'[4,2,1,2]': '[91,62,101]'
'[3,3,1,3]': '[190,64,321]'
anothersignature:anotherlocation
...
This will let you read the "ListOfData" in using the normal technique for reading hash map from a YAMLConfiguration (see below).
You'll have to treat the incoming information from the file as a HashMap of <String, String> and do any translation (e.g. turn 122,64,254 into a location) you need from there.
For reading a HashMap:
this.getConfig().getConfigurationSection("path.to.map").getValues(false)
For writing a HashMap (saveConfig() will still need to be called to write to disk):
this.getConfig().createSection("path.to.map", MyMap)
There's some details and subtleties here, its worth reading these carefully (same page, but different non-contiguous sections):
http://wiki.bukkit.org/Configuration_API_Reference#HashMaps
http://wiki.bukkit.org/Configuration_API_Reference#HashMaps_2

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.

Resources