Last active
December 19, 2015 16:29
-
-
Save reidmv/5983815 to your computer and use it in GitHub Desktop.
Hiera Custom Backend Annotated Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Hiera | |
| module Backend | |
| class File_backend | |
| def initialize | |
| Hiera.debug("Hiera File backend starting") | |
| end | |
| def lookup(key, scope, order_override, resolution_type) | |
| answer = nil | |
| Hiera.debug("Looking up #{key} in File backend") | |
| Backend.datasources(scope, order_override) do |source| | |
| Hiera.debug("Looking for data source #{source}") | |
| file = Backend.datafile(:file, scope, source, "d") or next | |
| path = File.join(file, key) | |
| next unless File.exist?(path) | |
| data = File.read(path) | |
| next unless data | |
| break if answer = Backend.parse_answer(data, scope) | |
| end | |
| return answer | |
| end | |
| end | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This is an annotated walkthrough of a *very* simple custom backend for Hiera, | |
| # which uses a directory-based file lookup scheme. | |
| # | |
| # First: skip the rest of this comment block and take a dive into the code. | |
| # | |
| # Then: read the following note about additional resources | |
| # | |
| # Additional Resources: | |
| # | |
| # If after reading through this you would like some additional resources, example | |
| # code is currently pretty good documentation since Hiera is fundamentally simple | |
| # under the hood. If you check out a few existing backends you're quite likely to | |
| # pick up other tricks and ideas. A few good backends to take a look at include: | |
| # | |
| # https://github.com/puppetlabs/hiera/blob/master/lib/hiera/backend/yaml_backend.rb | |
| # https://github.com/puppetlabs/puppet/blob/master/lib/hiera/backend/puppet_backend.rb | |
| # https://github.com/adrienthebo/hiera-file (the grown-up version of this example) | |
| # https://github.com/binford2k/hiera-rest | |
| # | |
| # See also https://github.com/puppetlabs/hiera/blob/master/lib/hiera/backend.rb for | |
| # other included utility functions. | |
| # | |
| # Filename: lib/hiera/backend/file_backend.rb | |
| class Hiera | |
| module Backend | |
| # The naming convention here is the backend name ("file"), first letter | |
| # upcased, and given the suffix "_backend". | |
| class File_backend | |
| # This method contains any code necessary to start using the backend. In | |
| # a network-based backend perhaps this method might contain code to | |
| # establish a connection to a database or API, for example. | |
| def initialize | |
| Hiera.debug("Hiera File backend starting") | |
| end | |
| # The lookup function is the most important part of a custom backend. | |
| # The lookup function takes four arguments which are: | |
| # | |
| # @param key [String] The lookup key specified by the user | |
| # | |
| # @param scope [Hash] The variable scope. Contains fact | |
| # values and all other variables in | |
| # scope when the hiera() function was | |
| # called. Most backends will not make | |
| # use of this data directly, but it will | |
| # need to be passed in to a variety of | |
| # available utility methods within Hiera. | |
| # | |
| # @param order_override [String] Like scope, a parameter that is | |
| # primarily simply passed through | |
| # to Hiera utility methods. | |
| # | |
| # @param resolution_type [Symbol] Hiera's default lookup method is | |
| # :priority, in which the first value | |
| # found in the hierarchy should be | |
| # returned as the answer. Other lookup | |
| # resolution methods exist. For example, | |
| # :array returns all answers found from | |
| # all levels of the hierarchy in an | |
| # array. This parameter allows the | |
| # backend to implement multiple | |
| # resolution types. This particular | |
| # example does not support multiple | |
| # resolution types, and will only do | |
| # priority lookups. | |
| # | |
| def lookup(key, scope, order_override, resolution_type) | |
| # Set the default answer. Returning nil from the lookup() method | |
| # indicates that no value was found. In this example hiera backend, | |
| # we start by assuming no match, and will then try to find a match at | |
| # each level of the hierarchy. If by the time we complete our | |
| # traversal of the hierarchy and have not found an answer, then this | |
| # default value of nil will be returned. | |
| answer = nil | |
| Hiera.debug("Looking up #{key} in File backend") | |
| # Calling this method constructs a list of data sources to search. By | |
| # default, for a hierarchy such as: | |
| # | |
| # --- | |
| # :hierarchy: | |
| # - %{clientcert} | |
| # - %{environment} | |
| # - global | |
| # | |
| # The datasources() method, being given the scope parameter and a nil | |
| # order_override parameter, will return something like: | |
| # | |
| # [ "client01.example.com", "development", "global" ] | |
| # | |
| # Note that variable interpolation of the %{clientcert} and | |
| # %{environment} has been performed, using values from the scope | |
| # parameter. If a value is given for order_override that value will | |
| # be inserted at the top of the hierarchy. For example, given an | |
| # order_override value of "custom", the datasources() method will | |
| # return something like: | |
| # | |
| # [ "custom", "client01.example.com", "development", "global" ] | |
| # | |
| # Once we have this hierarchy list, we will perform some kind of | |
| # lookup, starting with the top of the hierarchy and continuing | |
| # until either we find a match or reach the end of the hierarchy | |
| # list. | |
| Backend.datasources(scope, order_override) do |source| | |
| Hiera.debug("Looking for data source #{source}") | |
| # datafile() is a utility method to check for the existence of an | |
| # approprate datafile for the current datasource and if it exists, | |
| # return the full path to it. This utility method may be used if the | |
| # custom backend adheres to a couple of conventions. The datafile() | |
| # method assumes that: | |
| # | |
| # 1. As part of the lookup, the custom backend expects a file to | |
| # exist which corresponds to the current datasource. | |
| # 2. The configuration setting :datadir: is used in the custom | |
| # backend. | |
| # 3. Each datafile, located under :datadir:, has a standard | |
| # extension. | |
| # | |
| # The arguments given to datafile are | |
| # | |
| # @param backend [Symbol] The name of the backend. This is used to | |
| # refer to the appropriate setting key in | |
| # hiera.yaml. | |
| # | |
| # @param scope [Hash] The current scope. Interpolation could | |
| # be performed on the value of :datadir:, | |
| # and interpolation requires a scope. | |
| # | |
| # @param source [String] The current datasource, or level in the | |
| # hierarchy, for which a datafile is being | |
| # searched. | |
| # | |
| # @param extension [String] The extension used by all datafiles. The | |
| # datafile() method will search for the | |
| # file by cat'ing "#{source}.#{extension}". | |
| # | |
| # In example form, the datafile() method works for a custom backend | |
| # "foo" if hiera.yaml contains something like: | |
| # | |
| # --- | |
| # :backends: | |
| # - foo | |
| # :hierarchy: | |
| # - %{clientcert} | |
| # - global | |
| # :foo: | |
| # :datadir: /etc/puppetlabs/puppet/hieradata | |
| # | |
| # And the hieradata directory contains something like: | |
| # | |
| # `-- hieradata | |
| # |-- client1.example.com.foo | |
| # |-- client2.example.com.foo | |
| # |-- client3.example.com.foo | |
| # `-- global.foo | |
| # | |
| # And the datafile() method is called something like: | |
| # | |
| # Backend.datafile(:foo, scope, source, "foo") | |
| # | |
| # In this example backend called "file" we expect there to exist a | |
| # *.d directory for each datasource, and we will use the datafile() | |
| # method to check for and return its path if it exists. Custom | |
| # backends that do not use files for anything will probably not use | |
| # this method. | |
| file = Backend.datafile(:file, scope, source, "d") or next | |
| # For this example file backend we expect every source to be a *.d | |
| # directory, and we will look for a file in the directory with the | |
| # name of the specified key. The next few lines in essence take the | |
| # file name determined by using the datafile() method and check to | |
| # see if that directory contains a file named with the key. E.g. if | |
| # the user passed the key "myvalue1", and for a given datasource | |
| # (level in the hierarchy) the file path was "/hieradata/global.d", | |
| # these next few lines will check to see if | |
| # "/hieradata/global.d/myvalue1" exists and contains data. If any of | |
| # these preconditions fails, we will abort and move on to the next | |
| # datasource. | |
| path = File.join(file, key) | |
| next unless File.exist?(path) | |
| data = File.read(path) | |
| next unless data | |
| # Using the parse_answer() method is important if we want to be able | |
| # to use templated or scoped data. Calling parse_answer() on the data | |
| # will cause Hiera to perform variable interpolation, looking for | |
| # instances of the %{} syntax. Any instance found will be replaced | |
| # with the value from the scope. Finally, the "break" statement is | |
| # used to stop traversing datasources, as we have found and set our | |
| # answer. | |
| break if answer = Backend.parse_answer(data, scope) | |
| end | |
| # The return value of this method is the result of the hiera lookup. | |
| return answer | |
| end | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment