Skip to content

Latest commit

 

History

History
116 lines (95 loc) · 4.74 KB

README.md

File metadata and controls

116 lines (95 loc) · 4.74 KB

Configuration Data

This is the key to making DSC scalable and secure while raising cattle and not pets.

Using the Datum module, we create a hierarchical data store that contains generic values (i.e. in roles), and specific overrides per layer in an order of precedence that makes sense to your context. A common scenario, is that of a role defining a global settings for an IIS application, but with specifc being overriden either per location, environment, or for a specific node.

Datum exposes a lookup function that accepts the current $Node data (when iterating through AllNodes of a $configurationData), the Property Path of the value in the hierarchical data store, and a default value to use if no value is found in the hierarchical data store.

Lookup $Node 'Base1\Property1'

This would do a lookup through the hierarchy as defined in the Datum definition file, following the order of precedence defined under the Resolution precedence key.

ResolutionPrecedence:
  - 'AllNodes\$($Node.Name)'
  - 'AllNodes\<%= $CurrentNode.PSObject.Properties.where{$_.Name -eq $Node.Name}.Value%>\Roles' #script block execution
  - 'AllNodes\All\Roles'
  - 'SiteData\$($Node.Location)'
  - 'SiteData\$($Node.Location)\Roles' #variable expansion
  - 'SiteData\All'
  - 'SiteData\All\Roles'
  - 'Roles'
  - 'Roles\$($Node.Role)' #if Node has unique role, otherwise use <%= $CurrentNode.PSObject.Properties.where{$_.Name -in $Node.Role}.Value %>
  - 'Roles\All'

The Lookup also supports different merge behaviour (currently 2, and soon a few more), but the default is to return the most specific data, that is the one defined the closer to the top of our layers of precedences.

In this case, it would start the lookup by looking in the path: "AllNodes\$($Node.Name)\Base1\Property1" If no value is found there, it'll try the next layer: "AllNodes\<%= $CurrentNode.PSObject.Properties.where{$_.Name -eq $Node.Name}.Value%>\Roles\Base1\Property1"

As a side note, those two example show variable substitution/expansion $() and script execution <%= %>. The latter could have been written more simply with $($Node.Name).

It would then continue for each of the layer defined until something (!$null) is returned, or at the end, if specified return the Default value.

When the property path is evaluated, Datum is walking through the objects so that it translates like: $ConfigurationData.Datum.AllNodes.($Node.Name).Base1.Property1.

This abstracts away the method and technology used to store the actual data:

DatumStructure:
  - StoreName: AllNodes
    StoreProvider: Datum::File
    StoreOptions:
      DataDir: "./AllNodes"
 
  - StoreName: SiteData
    StoreProvider: Datum::File
    StoreOptions:
      DataDir: "./SiteData"

  - StoreName: Environments
    StoreProvider: Datum::File
    StoreOptions:
      DataDir: "./Environments"

  - StoreName: Roles
    StoreProvider: Datum::File
    StoreOptions:
      DataDir: "./Roles"

This structure above show the way the $Datum object is constructed, by mounting 'Stores' to it, in this case using the built-in File provider, that loads json, yml, psd1 files into hashtables.

You can look at the RootMetaMOF.ps1 to see how the Lookup is used to generate Meta MOFs, and All.yml to see what data is returned (no override in this case, it's directly the last layer).

In comparison, the RootConfiguration.ps1 has this block:

    (Lookup $Node 'Configurations') | % {
        $ConfigurationName = $_
        $(Write-Warning "Looking up params for $ConfigurationName")
        $Properties = $(lookup $Node $ConfigurationName -Verbose -DefaultValue @{})
        #x $ConfigurationName $ConfigurationName $Properties
        Get-DscSplattedResource -ResourceName $ConfigurationName -ExecutionName $ConfigurationName -Properties $Properties
    }

Side note, this the principle used by Puppet's Hiera and Roles & Profiles approach, great source of inspiration (and documentation)!

It is looking for the Configurations keys' values, and iterate through them. For each of those Configuration Name, it looks up for values defined with the Configuration Name as key, and finally 'splat' the DSC resource with the retrieved parameters. So for data looking like:

Configuration:
  - Test
Test:
  Param1: Value1
  Param2: Value2

It calls the DSC:

Test Test {
    Param1 = 'Value1'
    Param2 = 'Value2'
}

Note that this method does not yet handle 'execution names' from config Data (so calling Test twice would not be supported). Suggestions welcome, but I haven't encountered the need yet, as Configurations tends to have a unique instance per role.