diff options
Diffstat (limited to 'doc/rtd/topics/merging.rst')
-rw-r--r-- | doc/rtd/topics/merging.rst | 288 |
1 files changed, 0 insertions, 288 deletions
diff --git a/doc/rtd/topics/merging.rst b/doc/rtd/topics/merging.rst deleted file mode 100644 index a1422fe3..00000000 --- a/doc/rtd/topics/merging.rst +++ /dev/null @@ -1,288 +0,0 @@ -************************** -Merging User-Data Sections -************************** - -Overview -======== - -This was implemented because it has been a common feature request that there be -a way to specify how cloud-config YAML "dictionaries" provided as user-data are -merged together when there are multiple YAML files to merge together (say when -performing an #include). - -Since previously the merging algorithm was very simple and would only overwrite -and not append lists, or strings, and so on it was decided to create a new and -improved way to merge dictionaries (and their contained objects) together in a -way that is customizable, thus allowing for users who provide cloud-config -user-data to determine exactly how their objects will be merged. - -For example. - -.. code-block:: yaml - - #cloud-config (1) - runcmd: - - bash1 - - bash2 - - #cloud-config (2) - runcmd: - - bash3 - - bash4 - -The previous way of merging the two objects above would result in a final -cloud-config object that contains the following. - -.. code-block:: yaml - - #cloud-config (merged) - runcmd: - - bash3 - - bash4 - -Typically this is not what users want; instead they would likely prefer: - -.. code-block:: yaml - - #cloud-config (merged) - runcmd: - - bash1 - - bash2 - - bash3 - - bash4 - -This way makes it easier to combine the various cloud-config objects you have -into a more useful list, thus reducing duplication necessary to accomplish the -same result with the previous method. - - -Built-in Mergers -================ - -Cloud-init provides merging for the following built-in types: - -- Dict -- List -- String - -The ``Dict`` merger has the following options which control what is done with -values contained within the config. - -- ``allow_delete``: Existing values not present in the new value can be - deleted, defaults to False -- ``no_replace``: Do not replace an existing value if one is already present, - enabled by default. -- ``replace``: Overwrite existing values with new ones. - -The ``List`` merger has the following options which control what is done with -the values contained within the config. - -- ``append``: Add new value to the end of the list, defaults to False. -- ``prepend``: Add new values to the start of the list, defaults to False. -- ``no_replace``: Do not replace an existing value if one is already present, - enabled by default. -- ``replace``: Overwrite existing values with new ones. - -The ``Str`` merger has the following options which control what is done with -the values contained within the config. - -- ``append``: Add new value to the end of the string, defaults to False. - -Common options for all merge types which control how recursive merging is -done on other types. - -- ``recurse_dict``: If True merge the new values of the dictionary, defaults to - True. -- ``recurse_list``: If True merge the new values of the list, defaults to - False. -- ``recurse_array``: Alias for ``recurse_list``. -- ``recurse_str``: If True merge the new values of the string, defaults to - False. - - -Customizability -=============== - -Because the above merging algorithm may not always be desired (just as the -previous merging algorithm was not always the preferred one), the concept of -customized merging was introduced through 'merge classes'. - -A merge class is a class definition which provides functions that can be used -to merge a given type with another given type. - -An example of one of these merging classes is the following: - -.. code-block:: python - - class Merger: - def __init__(self, merger, opts): - self._merger = merger - self._overwrite = 'overwrite' in opts - - # This merging algorithm will attempt to merge with - # another dictionary, on encountering any other type of object - # it will not merge with said object, but will instead return - # the original value - # - # On encountering a dictionary, it will create a new dictionary - # composed of the original and the one to merge with, if 'overwrite' - # is enabled then keys that exist in the original will be overwritten - # by keys in the one to merge with (and associated values). Otherwise - # if not in overwrite mode the 2 conflicting keys themselves will - # be merged. - def _on_dict(self, value, merge_with): - if not isinstance(merge_with, (dict)): - return value - merged = dict(value) - for (k, v) in merge_with.items(): - if k in merged: - if not self._overwrite: - merged[k] = self._merger.merge(merged[k], v) - else: - merged[k] = v - else: - merged[k] = v - return merged - -As you can see there is a '_on_dict' method here that will be given a source -value and a value to merge with. The result will be the merged object. This -code itself is called by another merging class which 'directs' the merging to -happen by analyzing the types of the objects to merge and attempting to find a -know object that will merge that type. I will avoid pasting that here, but it -can be found in the `mergers/__init__.py` file (see `LookupMerger` and -`UnknownMerger`). - -So following the typical cloud-init way of allowing source code to be -downloaded and used dynamically, it is possible for users to inject there own -merging files to handle specific types of merging as they choose (the basic -ones included will handle lists, dicts, and strings). Note how each merge can -have options associated with it which affect how the merging is performed, for -example a dictionary merger can be told to overwrite instead of attempt to -merge, or a string merger can be told to append strings instead of discarding -other strings to merge with. - -How to activate -=============== - -There are a few ways to activate the merging algorithms, and to customize them -for your own usage. - -1. The first way involves the usage of MIME messages in cloud-init to specify - multipart documents (this is one way in which multiple cloud-config is - joined together into a single cloud-config). Two new headers are looked - for, both of which can define the way merging is done (the first header to - exist wins). These new headers (in lookup order) are 'Merge-Type' and - 'X-Merge-Type'. The value should be a string which will satisfy the new - merging format definition (see below for this format). - -2. The second way is actually specifying the merge-type in the body of the - cloud-config dictionary. There are 2 ways to specify this, either as a - string or as a dictionary (see format below). The keys that are looked up - for this definition are the following (in order), 'merge_how', - 'merge_type'. - -String format -------------- - -The string format that is expected is the following. - -:: - - classname1(option1,option2)+classname2(option3,option4).... - -The class name there will be connected to class names used when looking for the -class that can be used to merge and options provided will be given to the class -on construction of that class. - -For example, the default string that is used when none is provided is the -following: - -:: - - list()+dict()+str() - -Dictionary format ------------------ - -A dictionary can be used when it specifies the same information as the -string format (i.e. the second option above), for example: - -.. code-block:: python - - {'merge_how': [{'name': 'list', 'settings': ['append']}, - {'name': 'dict', 'settings': ['no_replace', 'recurse_list']}, - {'name': 'str', 'settings': ['append']}]} - -This would be the equivalent format for default string format but in dictionary -form instead of string form. - -Specifying multiple types and its effect -======================================== - -Now you may be asking yourself, if I specify a merge-type header or dictionary -for every cloud-config that I provide, what exactly happens? - -The answer is that when merging, a stack of 'merging classes' is kept, the -first one on that stack is the default merging classes, this set of mergers -will be used when the first cloud-config is merged with the initial empty -cloud-config dictionary. If the cloud-config that was just merged provided a -set of merging classes (via the above formats) then those merging classes will -be pushed onto the stack. Now if there is a second cloud-config to be merged -then the merging classes from the cloud-config before the first will be used -(not the default) and so on. This way a cloud-config can decide how it will -merge with a cloud-config dictionary coming after it. - -Other uses -========== - -In addition to being used for merging user-data sections, the default merging -algorithm for merging 'conf.d' YAML files (which form an initial YAML config -for cloud-init) was also changed to use this mechanism so its full -benefits (and customization) can also be used there as well. Other places that -used the previous merging are also, similarly, now extensible (metadata -merging, for example). - -Note, however, that merge algorithms are not used *across* types of -configuration. As was the case before merging was implemented, -user-data will overwrite conf.d configuration without merging. - -Example cloud-config -==================== - -A common request is to include multiple ``runcmd`` directives in different -files and merge all of the commands together. To achieve this, we must modify -the default merging to allow for dictionaries to join list values. - - -The first config - -.. code-block:: yaml - - #cloud-config - merge_how: - - name: list - settings: [append] - - name: dict - settings: [no_replace, recurse_list] - - runcmd: - - bash1 - - bash2 - -The second config - -.. code-block:: yaml - - #cloud-config - merge_how: - - name: list - settings: [append] - - name: dict - settings: [no_replace, recurse_list] - - runcmd: - - bash3 - - bash4 - - -.. vi: textwidth=79 |