Package: yamlmaker
YAMLmakerStop Templating. Start Generating.YAMLmaker is a simple utility module to help yougenerateYAML using pure python.Hello Worldpip install yamlmaker#hello_world.pyfromyamlmakerimportgenerateconfig={"hello":"world"}generate(config)python hello_world.py#hello_world.ymlhello:worldBackgroundYAML is being used for many of the cool new tools in technology. Kubernetes, Concourse, Salt, Ansible etc. all use YAML but building complex configurations can be a real pain.It all starts with trying to interpolate a few variables into a YAML config, but before you know it, you're adding if statements, looping, and developing custom macros. perhaps you're using jinja2, YTT, spruce, or kustomize. These tools all try to work within the YAML document itself and can only offer so much control.At some point you need to ask yourself if it's easier adding a programing language within a data structure or working with a data structure within a programing language.  That's where YAMLmaker's approach comes from.  This isn't a new tool or CLI utility, it's simply a helper module which allows you to simply generate YAML using pure python.Python 3.9 CaveatThis module strictly works with python3.9+. The reason being is that 3.9 introduces the newdictionary union operatordict1 | dict2.Utilitiesgenerate(list | dictionary)Thegenerate()function is the core of YAMLmaker. Under the cover it wraps the PyYAML'syaml.dump()function. In addition to just dumping to a YAML file it does the following.Creates a YAML file with the same name as the Python file. i.e.python my_config.pywill produce a file calledmy_config.ymlHandles multiline content using the yaml|-. For example, if you include a certificate it will display nicely on multiple lines.Does not include anchors or pointers in the yaml& and *etc.Preserves Sorting.  The order in which you write your configuration will be the same order when generated into YAML.Example#example.pyfromyamlmakerimportgenerateconfig={"hello":"world"}generate(config)#example.ymlhello:worldenv(string)Theenv()utility returns the value of the name of an environment variable, passed, as a string.If the environment variable does not exist it will return an empty string,"".Example#bashexportFOO="bar"#example.pyfromyamlmakerimportgenerate,envconfig={"foo":env("FOO")}generate(config)#example.ymlfoo:barcmd(string)Thecmd()function will return the value for the command passed to it as string. For instance, if you have to pluck a secret from vault or require the output of a bash/powershell command.Powershell Example#example.pyfromyamlmakerimportgenerate,cmdconfig={"foo":cmd("powershell Write-Host 'bar'")}generate(config)#example.ymlfoo:barBash Example#example.pyfromyamlmakerimportgenerate,cmdconfig={"foo":cmd("echo bar")#*}generate(config)#example.ymlfoo:bar*Note:When passing bash commands, remove the quotes around strings to ensure the value is returned properly.  If you need to use an environment variable within the command you can do this:cmd("echo "+env('FOO'))Sources(dictionary)TheSourcesclass helps with sourcing in external YAML data, such as variable files.Sources works by instantiating a new instance ofSourcesand passing it dictionary where the labels are the keys and the file_paths are the values.  This ensures that the files are only loaded once, and it provides an alias that the file can go by to avoid referencing long path names every time a value is needed from the source file.ExamplefromyamlmakerimportSourcessources=Sources({"foo-vars":"some/path/to/foo-vars.yml","bar-vars":"some/path/to/bar-vars.yml"})Sources.grab(label, path)Once an instance of sources has been created, you can then use thegrab()method to pluck out values.The grab method takes the label of the source to grab from and the path to the value using dot notation.  Using integers will reference the position of a list item.Examples# foo-vars.ymlfoo:barbiz:bazbuz:-boo-goo-doo# bar-vars.ymlalpha:beta:-one:1two:2three:3-ten:10twenty:20#example.pyfromyamlmakerimportgenerate,Sourcessources=Sources({"foo-vars":"some/path/to/foo-vars.yml","bar-vars":"some/path/to/bar-vars.yml"})config={"thing-one":sources.grab("foo-vars","biz"),"thing-two":sources.grab("foo-vars","buz.1")"thing-three":sources.grab("bar-vars","alpha.beta.1.ten")}generate(config)#example.ymlthing-one:bazthing-two:goothing-three:10Files(dictionary)TheFilesclass helps with sourcing in external file content, such as pem files, etc.Sources works by instantiating a new instance ofFilesand passing it dictionary where the labels are the keys and the file_paths are the values.  This ensures that the files are only loaded once, and it provides an alias that the file can go by to avoid referencing long path names every time the contents of a file is needed.ExamplefromyamlmakerimportFilesfiles=Files({"pub-key":"some/path/to/pubkey.cert"})Files.grab(label)Once an instance of files has been created, you can then use thegrab()method to retrieve the contents of a file by its label.Examples#pubkey.cert

-----BEGIN CERTIFICATE-----
MIINMDCCDBigAwIBAgIQFwVvbgUxBFQFAAAAAIfenTANBgkqhkiG9w0BAQsFADBC
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMRMw
EQYDVQQDEwpHVFMgQ0EgMU8xMB4XDTIxMDUyNDAxMzYwMFoXDTIxMDgxNjAxMzU1
OVowZjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
DU1vdW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBMTEMxFTATBgNVBAMMDCou
...
U2mUhTIelGiXuuurDjTQD11oCR2jrp6hi4aToQ+yG3b1Kv82JBcZxRhUggLVbGJM
ktsuqVkVli8n7gmjGH5pP27T/JqAam4ej/Gqd+6SklI9xE0+DHI2bkB0IGTdzTPR
8dt6A10e5tlmXsAb/8HCyUuNwqtUrgQN4zKmigZG8SdTYqlfy1mXHvPO6b/qZ0jF
ZUKuCmlAJryMEsyJdcX6yl4Hvub6/O7QUoaxr6L3Kr3UJ8hIy8GdmqLP2YWVt9Au
Uz4hDpQ9cE705FYs43M7S/40IeI=
-----END CERTIFICATE-----#example.pyfromyamlmakerimportgenerate,Filesfiles=Files({"pub-key":"some/path/to/pubkey.cert"})config={"some-pub-key":files.grab("pub-key")}generate(config)some-pub-key:|------BEGIN CERTIFICATE-----MIINMDCCDBigAwIBAgIQFwVvbgUxBFQFAAAAAIfenTANBgkqhkiG9w0BAQsFADBCMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMRMwEQYDVQQDEwpHVFMgQ0EgMU8xMB4XDTIxMDUyNDAxMzYwMFoXDTIxMDgxNjAxMzU1OVowZjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxEzARBgNVBAoTCkdvb2dsZSBMTEMxFTATBgNVBAMMDCou...U2mUhTIelGiXuuurDjTQD11oCR2jrp6hi4aToQ+yG3b1Kv82JBcZxRhUggLVbGJMktsuqVkVli8n7gmjGH5pP27T/JqAam4ej/Gqd+6SklI9xE0+DHI2bkB0IGTdzTPR8dt6A10e5tlmXsAb/8HCyUuNwqtUrgQN4zKmigZG8SdTYqlfy1mXHvPO6b/qZ0jFZUKuCmlAJryMEsyJdcX6yl4Hvub6/O7QUoaxr6L3Kr3UJ8hIy8GdmqLP2YWVt9AuUz4hDpQ9cE705FYs43M7S/40IeI=-----END CERTIFICATE-----IncludeTheIncludehelper class acts like an if statement and is useful when you need to append to or merge additional data into a data structure.Include.when(statement, if_block, else_block)The Include class contains one method, called,whenwhich implements an if-else block.Theif_blockandelse_blocksimply return the value passed to them if the expression isTrueorFalse.  By default, theelse_blockwill return an empty for theTypeof the value passed in for theif_block. i.e."",[], or{}.Examples with DictionariesWhen True#example.pyfromyamlmakerimportgenerate,Includeconfig={"foo":"bar"}|Include.when("x"=="x",{"biz":"baz"})generate(config)#example.ymlfoo:barbiz:bazWhen False#example.pyfromyamlmakerimportgenerate,Includeconfig={"foo":"bar"}|Include.when("x"=="y",{"biz":"baz"})generate(config)#example.ymlfoo:barUse of Else Block#example.py - using positional argsfromyamlmakerimportgenerate,Includeconfig={"foo":"bar"}|Include.when("x"=="y",{"biz":"baz"},{"biz":"zub"})generate(config)#example.py - using keyword argsfromyamlmakerimportgenerate,Includeconfig={"foo":"bar"}|Include.when("x"=="y",if_block={"biz":"baz"},else_block={"biz":"zub"})generate(config)#example.ymlfoo:barbiz:zubExamples with Lists#example.pyfromyamlmakerimportgenerate,Includeconfig={"foos":["biz","baz",]+Include.when("x"=="x",["bin","zub"])}generate(config)#example.ymlfoos:-biz-baz-bin-zubIn-line List ExamplesInstead of using the concat operator,+you can also specify the Include to be used in-line within the data-structure.However, this will produce empty values within the list. seeprune_empty()for how to deal with this.When Truefromyamlmakerimportgenerate,Includeconfig={"foos":["biz",Include.when("x"=="x","baz")]}generate(config)#example.ymlfoos:-biz-bazWhen Falsefromyamlmakerimportgenerate,Includeconfig={"foos":["biz",Include.when("x"=="y","baz")]}generate(config)#example.ymlfoos:foos:-biz-""prune_empty()As seen above, when usingInclude.when()in-line, within a list, it will leave a blank value where theIncludestatement was. To solve this you can wrap the list withprune_empty()which will eliminate the empty values.#example.pyfromyamlmakerimportgenerate,Include,prune_emptyconfig={"foos":prune_empty(["biz",Include.when("x"=="y","baz")])}generate(config)#example.ymlfoos:-bizPure Python ExamplesSome examples using just Python and generate.For LoopYAMLmaker doesn't include things like looping etc. But here is an example using Python and theforloop to help generate a list.#example.pyfromyamlmakerimportgenerateconfig={"things":[{"foo":item}foritemin["bar","biz","baz"]]}generate(config)#example.ymlthings:-foo:bar-foo:biz-foo:bazMerging DictionariesIn Python 3.9 and in the examples above, the union operator|is used heavily to merge multiple dictionaries together.  Just be aware that the behavior is to always override an existing key rather than perform a deep merge.#example.pyfromyamlmakerimportgenerateconfig_one={"foo":"bar"}config_two={"biz":"bar","things":["one","two"]}config_three={"foo":"notbar","things":["three"]}config=config_one|config_two|config_threegenerate(config)#example.ymlfoo:notbarbiz:barthings:-three