Giter Site home page Giter Site logo

datum's Introduction

Datum

Build Status Azure DevOps coverage (branch) Azure DevOps tests PowerShell Gallery (with prereleases) PowerShell Gallery

A datum is a piece of information.

Datum is a PowerShell module used to aggregate DSC configuration data from multiple sources allowing you to define generic information (Roles) and specific overrides (i.e. per Node, Location, Environment) without repeating yourself.

To see it in action, I recommend looking at the full day workshop (reduced to 3.5 hours) by Raimund Andree and Jan-Hendrick Peters. The video recording here, and the DSC Workshop repository there.

Datum is working with PowerShell 7.

A new version is in the works, encouragement welcomed. :)

Table of Content

  1. Why Datum?
  2. Getting Started & Concepts
  3. Intended Usage
  4. Under the hood
  5. Origins

1. Why Datum?

This PowerShell Module enables you to easily manage a Policy-Driven Infrastructure using Desired State Configuration (DSC), by letting you organise the Configuration Data in a hierarchy adapted to your business context, and injecting it into Configurations based on the Nodes and the Roles they implement.

This (opinionated) approach allows to raise cattle instead of pets, while facilitating the management of Configuration Data (the Policy for your infrastructure) and provide defaults with the flexibility of specific overrides, per layers, based on your environment.

The Configuration Data is composed in a customisable hierarchy, where the storage can be using the file system, and the format Yaml, Json, PSD1 allowing all the use of version control systems such as git.

Notes

The idea follows the model developed by the Puppet, Chef and Ansible communities (possibly others), in the configuration data management area:

Although not in v1 yet, Datum is currently used in Production to manage several hundreds of machines, and is actively maintained. A stable v1 release is expected for March 2018, while some concepts are thought through, and prototype code refactored.

2. Getting Started & Concepts

Data Layers and Precedence

To simplify the key concept, a Datum hierarchy is some blocks of data (nested hashtables) organised in layers, so that a subset of data can be overridden by another block of data from another layer.

Assuming you have configured two layers of data representing:

  • Per Node Overrides
  • Generic Role Data

If you Define a data block for the Generic data:

# Generic layer
Data1:
  Property11: DefaultValue11
  Property12: DefaultValue12

Data2:
  Property21: DefaultValue21
  Property22: DefaultValue22

You can transform the Data by overriding what you want in the per Node override:

Data2:
  Property21: NodeOverrideValue21
  Property22: NodeOverrideValue22

The resulting data would now be:

# Generic layer
Data1:
  Property11: DefaultValue11
  Property12: DefaultValue12

Data2:
  Property21: NodeOverrideValue21
  Property22: NodeOverrideValue22

The order of precedence you define for your layers define the Most specific (at the top of your list), to the Most generic (at the bottom).

On the file system, this data could be represented in two folders, one per layer, and a Datum configuration file, a Datum.yml

C:\Demo
│   Datum.yml
├───NodeOverride
│       Data.yml
└───RoleData
        Data.yml

The Datum.yml would look like this (the order is important):

ResolutionPrecedence:
  - NodeOverride\Data
  - RoleData\Data

You can now use Datum to lookup the Merged Data, per key:

$Datum = New-DatumStructure -DefinitionFile .\Demo\Datum.yml

Lookup 'Data1' -DatumTree $Datum
# Name                           Value
# ----                           -----
# Property11                     DefaultValue11
# Property12                     DefaultValue12

Lookup 'Data2' -DatumTree $Datum
# Name                           Value
# ----                           -----
# Property21                     NodeOverrideValue21
# Property22                     NodeOverrideValue22

This demonstrate the override principle, but it will always return the same thing. How do we make it relative to a Node's meta data?

Path Relative to $Node

The idea is that we want to apply the override only on certain conditions, that could be expressed like:

  • A node is given the role SomeRole, it's in London, and is named SRV01
  • The Role SomeRole defines default data for Data1 and Data2
  • But because SRV01 is in London, use Data2 defined in the london location instead (leave Data1 untouched).

In this scenario we would create two layers as per the file layout below:

Demo2
│   Datum.yml
├───Locations
│       London.yml
└───Roles
        SomeRole.yml
# SomeRole.yml
Data1:
  Property11: RoleValue11
  Property12: RoleValue12

Data2:
  Property21: RoleValue21
  Property22: RoleValue22
# London.yml
Data2:
  Property21: London Override Value21
  Property22: London Override Value22

Now let's create a Node hashtable that describe our SRV01:

$SRV01 = @{
    Nodename = 'SRV01'
    Location = 'London'
    Role     = 'SomeRole'
}

Let's create SRV02 for witness, which is in Paris (the override won't apply).

$SRV02 = @{
    Nodename = 'SRV02'
    Location = 'Paris'
    Role     = 'SomeRole'
}

And we configure the Datum.yml's Resolution Precedence with relative paths using the Node's properties:

# Datum.yml
ResolutionPrecedence:
  - 'Locations\$($Node.Location)'
  - 'Roles\$($Node.Role)'

We can now mount the Datum tree, and do a lookup in the context of a Node:

Import-Module Datum
$Datum = New-DatumStructure -DefinitionFile .\Datum.yml

lookup 'Data1' -Node $SRV01 -DatumTree $Datum
# Name                           Value
# ----                           -----
# Property11                     RoleValue11
# Property12                     RoleValue12


lookup 'Data2' -Node $SRV01 -DatumTree $Datum

# Name                           Value
# ----                           -----
# Property21                     London Override Value21
# Property22                     London Override Value22

And for our witness, not in the London location, Data2 is not overridden:

lookup 'Data2' -Node $SRV02 -DatumTree $Datum

# Name                           Value
# ----                           -----
# Property21                     RoleValue21
# Property22                     RoleValue22

Magic!

3. Intended Usage

The overall goal, better covered in the book Infrastructure As Code by Kief Morris, is to enable a team to "quickly, easily, and confidently adapt their infrastructure to meet the changing needs of their organization".

To do so, we define our Infrastructure in a set of Policies: human-readable documents describing the intended result (or, Desired State), in structured, declarative aggregation of data, that are also usable by computers: The Configuration Data.

We then interpret and transform the data to pass it over to the platform (DSC) and technology components (DSC Resources) grouped in manageable units (Resources, Configurations, and PowerShell Modules).

Finally, the decentralised execution of the platform can let the nodes converge towards their policy.

The policies and their execution are composed in layers of abstraction, so that people with different responsibilities, specialisations and accountabilities have access to the right amount of data in the layer they operate for their task.

As it simplest, a scalable implementation regroups:

  • A Role defining the configurations to include, along with the data,
  • Nodes implementing that role,
  • Configurations (DSC Composite Resources) included in the role,

The abstraction via roles allows to apply a generic 'template' to all nodes, while enabling Node specific data such as Name, GUID, Encryption Certificate Thumbprint for credentials.

Policy for Role 'WindowsServerDefault'

At a high level, we can compose a Role that will apply to a set of nodes, with what we'd like to see configured.

In this document, we define a generic role we intend to use for Windows Servers, and include the different Configurations we need (Shared1,SoftwareBaseline).

We then provide the data for the parameters to those configurations.

# WindowsServerDefault.yml
Configurations: #Configurations to Include for Nodes of this role
  - Shared1
  - SoftwareBaseline

Shared1: # Parameters for Configuration Shared1
  DestinationPath: C:\MyRoleParam.txt
  Param1: This is the Role Value!

SoftwareBaseline: # Parameters for DSC Composite Configuration SoftwareBaseline
  Sources:
    - Name: chocolatey
      Disabled: false
      Source: https://chocolatey.org/api/v2

  Packages:
    - Name: chocolatey
    - Name: NotepadPlusplus
      Version: '7.5.2'
    - Name: Putty

The Software baseline for this role is self documenting. Its specific data apply to that role, and can be different for another role, while the underlying code would not change. Adding a new package to the list is simple and does not require any DSC or Chocolatey knowledge.

Node Specific data

We define the nodes with the least amount of uniqueness, to avoid snowflakes. Below, we only say where the Node is located, what role is associated to it, its name (SRV01, the file's BaseName) and a unique identifier.

# SRV01.yml
NodeName: 9d8cc603-5c6f-4f6d-a54a-466a6180b589
role: WindowsServerDefault
Location: LON

Excerpt of DSC Composite Resource (aka. Configuration)

This is where the Configuration Data is massaged in usable ways for the underlying technologies (DSC resources).

Here we are creating a SoftwareBaseline by:

  • Installing Chocolatey from a Nuget Feed (using the Resource ChocolateySoftware)
  • Registering a Set of Sources provided from the Configuration Data
  • Installing a Set of packages as per the Configuration data
Configuration SoftwareBaseline {
    Param(
        $PackageFeedUrl = 'https://chocolatey.org/api/v2',
        $Sources = @(),
        $Packages
    )
    
    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Import-DscResource -ModuleName Chocolatey -ModuleVersion 0.0.46
    
    ChocolateySoftware ChocoInstall {
        Ensure = 'Present'
        PackageFeedUrl = $PackageFeedUrl
    }

    foreach($source in $Sources) {
        if(!$source.Ensure) { $source.add('Ensure', 'Present') }
        Get-DscSplattedResource -ResourceName ChocolateySource -ExecutionName "$($Source.Name)_src" -Properties $source
    }

    foreach ($Package in $Packages) {
        if(!$Package.Ensure) { $Package.add('Ensure','Present') }
        if(!$Package.Version) { $Package.add('version', 'latest') }
        Get-DscSplattedResource -ResourceName ChocolateyPackage -ExecutionName "$($Package.Name)_pkg" -Properties $Properties
    }
}

In this configuration example, Systems Administrators do not need to be Chocolatey Software specialists to know how to create a Software baseline using the Chocolatey DSC Resources.

Root Configuration

Finally, the root configuration is where each node is processed (and the Magic happens).

We import the Module or DSC Resources needed by the Configurations, and for each Node, we lookup the Configurations implemented by the policies (Lookup 'Configurations'), and for each of those we lookup for the parameters that applies and splat them to the DSC Resources (sort of...).

This file does not need to change, it dynamically uses what's in $ConfigurationData!

# RootConfiguration.ps1
configuration "RootConfiguration"
{
    Import-DscResource -ModuleName PSDesiredStateConfiguration
    Import-DscResource -ModuleName SharedDscConfig -ModuleVersion 0.0.3
    Import-DscResource -ModuleName Chocolatey -ModuleVersion 0.0.46

    node $ConfigurationData.AllNodes.NodeName {
        (Lookup 'Configurations').Foreach{
            $ConfigurationName = $_
            $Properties = $(lookup $ConfigurationName -DefaultValue @{})
            Get-DscSplattedResource -ResourceName $ConfigurationName -ExecutionName $ConfigurationName -Properties $Properties
        }
    }
}

RootConfiguration -ConfigurationData $ConfigurationData -Out "$BuildRoot\BuildOutput\MOF\"

4. Under the hood

Although Datum has been primarily targeted at DSC Configuration Data, it can be used in other contexts where the hierarchical model and lookup makes sense.

Building a Datum Hierarchy

The Datum hierarchy, similar to Puppet's Hiera, is defined typically in a Datum.yml at the base of the Config Data files. Although Datum comes only with a built-in Datum File Provider (Not SHIPS) supporting the JSON, Yaml, and PSD1 format, it can call external PowerShell modules implementing the Provider functionalities.

Datum Tree's Root Branches

A branch of the Datum Tree would be defined within the DatumStructure of the Datum.yml like so:

# Datum.yml
DatumStructure:
  - StoreName: AllNodes
    StoreProvider: Datum::File
    StoreOptions:
      Path: "./AllNodes"

Instantiating a variable from that definition would be done with this:

$Datum = New-DatumStructure -DefinitionFile Datum.yml

This returns a hashtable with a key 'AllNodes' (StoreName), by using the internal command (under the hood):

Datum\New-DatumFileProvider -Path "./AllNodes"

Should you create a module (e.g. named 'MyReddis'), implementing the function New-DatumReddisProvider you could write the following Datum.yml to use it (as long as it's in your PSModulePath):

# Datum.yml
DatumStructure:
  - StoreName: AllNodes
    StoreProvider: MyReddis::Reddis
    StoreOptions:
      YourParameter: ParameterValue

If you do, please let me know I'm interested :)

You can have several root branches, of different Datum Store Providers, with custom options (but prefer to Keep it super simple).

Store Provider

So, what should those store providers look like? What do they do?

In short, they abstract the underlying data storage and format, in a way that will allow us to consistently do key/value lookups.

The main reason(s) it is not based on SHIPS (or Jim Christopher, aka @beefarino's Simplex module, which I tried and enjoyed!), is that the PowerShell Providers did not seem to provide enough abstraction for read-only key/value pair access. These are still very useful (and used) as an intermediary abstraction, such as the FileSystem provider used in the Datum FileProvider.

In short, I wanted an uniform key, that could abstract the container, storage, and the structure within the Format. Imagine the standard FileSystem provider:

Directory > File > PSD1

Where the file SERVER01.PSD1 is in the folder .\AllNodes\, and has the following data:

# SERVER01.PSD1
@{
    Name = 'SERVER01'
    MetaData = @{
        Subkey = 'Data Value'
    }
}

I wanted that the key 'AllNodes\SERVER01\MetaData\Subkey' returns 'Data Value'.

However, while the notation with Path Separator (\) is used for lookups (more on this later), the provider abstracts the storage+format using the familiar dot notation.

From the example above where we loaded our Datum Tree, we'd use the following to return the value:

$Datum.AllNodes.SERVER01.MetaData.Subkey

So we're just accessing variable properties, and our Config Data stored on the FileSystem, is just mounted in a variable (in case of the FileProvider).

With the dot notation we have access using absolute keys to all values via the root $datum, but this is not much different from having all data in one big hashtable or PSD1 file... This is why we have...

Lookups and overrides in Hierarchy

We can mount different Datum Stores (unit of Provider + Parameters) as branches onto our root variable. Typically, I mount the following structure (with many more files not listed here):

DSC_ConfigData
│   Datum.yml
├───AllNodes
│   ├───DEV
│   └───PROD
├───Environments
├───Roles
└───SiteData

I can access the data with:

$Datum.AllNodes.DEV.SRV01

or

$Datum.SiteData.London

But to be a hierarchy, there should be an order of precedence, and the lookup is a function that resolves a relative path, in the paths defined by the order of precedence.

Datum.yml defines another section for ResolutionPrecedence: this is an ordered list of prefix to use to search for a relative path, from the most specific to the most generic.

Should you do a Lookup for a relative path of property\subkey, and the Yaml would contain the following block:

ResolutionPrecedence:
  - 'AllNodes'
  - 'Environments'
  - 'Location'
  - 'Roles\All'

In this case the lookup function would try the following absolute paths sequentially:

$Datum.AllNodes.property.Subkey
$Datum.Environments.property.Subkey
$Datum.Location.property.Subkey
$Datum.Roles.All.property.Subkey

Although you can configure Datum to behave differently based on your needs, like merging together the data found at each layer, the most common and simple case, is when you only want the 'MostSpecific' data defined in the hierarchy (and this is the default behaviour).

In that case, even if you usually define the data in the roles layer (the most generic layer), if there's an override in a more specific layer, it will be used instead.

But the ordering shown above is not very flexible. How do we apply the relation between the list of roles, the current Node, Environment, location and so on?

Variable Substitution in Path Prefixes

As we've seen that a Node implements a role, is in a location and from a specific Environment, how do we express these relations (or any relation that would make sense in your context)?

We can define the names and values of those information in the Node meta data (SRV01.yml) like so:

# SRV01.yml
NodeName: 9d8cc603-5c6f-4f6d-a54a-466a6180b589
role: WindowsServerDefault
Location: LON
Environment: DEV

And use variable substitution in the ResolutionPrecedence block of the Datum.yml so that the Search Prefix can be dynamic from one Node to another:

# in Datum.yml
ResolutionPrecedence:
  - 'AllNodes\$($Node.Environment)\$($Node.Name)'
  - 'AllNodes\$($Node.Environment)\All'
  - 'Environments\$($Node.Environment)'
  - 'SiteData\$($Node.Location)'
  - 'Roles\$($Node.Role)'
  - 'Roles\All'

The lookup of the Property Path 'property\Subkey' would try the following for the above ResolutionPrecedence:

$Datum.AllNodes.($Node.Environment).($Node.Name).property.Subkey
$Datum.AllNodes.($Node.Environment).All.property.Subkey
$Datum.Environments.($Node.Environment).property.Subkey
$Datum.SiteData.($Node.Location).property.Subkey
$Datum.Roles.($Node.Role).property.Subkey
$Datum.Roles.All.property.Subkey

If you remember the part of the Root Configuration:

 node $ConfigurationData.AllNodes.NodeName {
    # ...
 }

It goes through all the Nodes in $ConfigurationData.AllNodes, so the absolute path is changing based on the current value of $Node.

Enriching the Data lookup

Datum Tree

Regardless of the Datum Store Provider used (there's only the Datum File Provider built-in, but you can write your own), Datum tries to handle the data similarly to an ordered case-insensitive Dictionary, where possible (i.e. PSD1 don't support Ordering). All data is referenced under one variable, so it looks like a big tree with many branches and leafs like the one below.

$Datum
  +
  |
  +--+AllNodes
  |    +  DEV
  |    |  +SRV01
  |    |   ++ NodeName: SRV01
  |    |      role: Role1
  |    |      Location: Lon
  |    |      ExampleProperty1: 'From Node'
  |    |      Test: '[TEST=Roles\Role1\Shared1\DestinationPath
  |    |
  |    +-+PROD
  |
  +--+Environments
  |    +
  |    +-+DEV
  |    |      Description: 'This is the DEV Environment'
  |    +-+PROD
  |           Description: 'This is the PROD Environment'
  |
  +--+Roles
  |    +-+Role1
  |           Configurations
  |               - Shared1
  |
  +--+SiteData
       +-+Lon

If you provide a key, Datum will return All values underneath (to the right):

$Datum.AllNodes.Environments

# DEV                 PROD
# ---                 ----
# {Description, Test} {Description}

Lookup Merging Behaviour

In the Tree described above, the Lookup function iterates through the ResolutionPrecedence's key prefix, and append the provided key suffix:

For the following ResolutionPrecedence:

ResolutionPrecedence:
  - 'AllNodes\$($Node.Environment)\$($Node.Name)'
  - 'Roles\$($Node.Role)'
  - 'Roles\All

Within the $Node block, doing Lookup 'Configurations' will actually look for:

  • $Datum.AllNodes.($Node.Environment).($Node.Name).Configurations
  • $Datum.Roles.($Node.Role).Configurations
  • $Datum.Roles.All.Configurations

By default the merge behaviour is to not merge, which means the first occurence will be returned and the lookup stopped.

The other merge behaviours depends on the (rough) data type of the key to be merged.

Datum identifies 4 main types in whatever matches first the following:

  • Hashtable: Hashtables or Ordered Dictionaries
  • Array of Hashtables: Every IEnumerable (except string) that can be casted -as [Hastable[]]
  • Array of Base type objects: Every other IEnumerable (except string)
  • Base Types: Everything else (Int, String, PSCredential, DateTime...)

Their merge behaviour can be defined in the Datum.yml, either by using a Short name that reference a preset, or a structure that details the behaviour based on the type.

There is a default Behaviour (MostSpecific by default), and you can specify ordered overrides:

default_lookup_options: MostSpecific

This is the recommended setting and also the default, so that any sub-key merge has to be explicitly declared like so:

lookup_options:
  <Key Name>: MostSpecific/First|hash/MergeTopKeys|deep/MergeRecursively
  <Other Key>:
    merge_hash: MostSpecific/First|deep|hash/*
    merge_basetype_array: MostSpecific/First|Sum/Add|Unique
    merge_hash_array: MostSpecific/First|Sum|DeepTuple/DeepItemMergeByTuples|UniqueKeyValTuples
    merge_options:
      knockout_prefix: --
      tuple_keys:
        - Name
        - Version

The key to be used here is the suffix as used in with the Lookup function: e.g. 'Configurations', 'Role1\Data1'.

Each layer will be merged with the result of the previous layer merge:

Precedence 0 +
             |
             +---+ Merge 0+-+
             |              |
Precedence 1 +              |
                            +---+Merge 1 +
                            |            |
Precedence 2  +-------------+            |
                                         +----+Merge 2
                                         |
Precedence 4  +--------------------------+

The Short name presets represent the following:

First, MostSpecific or any un-matched string:
  merge_hash: MostSpecific
  merge_baseType_array: MostSpecific
  merge_hash_array: MostSpecific

hash or MergeTopKeys:
  merge_hash: hash
  merge_baseType_array: MostSpecific
  merge_hash_array: MostSpecific
    merge_options:
      knockout_prefix: '--'

depp or MergeRecursively:
  merge_hash: deep
  merge_baseType_array: Unique
  merge_hash_array: DeepTuple
  merge_options:
    knockout_prefix: --
    tuple_Keys:
      - Name
      - Version

The Lookup Options can also define keys using a (valid) Regex, for this the key has to start with ^, for instance:

lookup_options:
  ^LCM_Config\\.*: deep

The lookup will always favor non-regex exact match, and failing that will then use the first matching regex, before falling back on the default_lookup_option.

If you've been following that far, you might wonder how it works for subkeys.

Say you want to merge a subkey of a configuration where the role defines the following:

Configurations:
  - SoftwareBaseline

SoftwareBaseline:
  PackageFeed: https://chocolatey.org/api/v2
  Packages:
    - Name: Package1
      Version: v0.0.2

And an override file somewhere in the hierarchy:

SoftwareBaseline:
  Packages:
    - Name: Package2
      Version: v4.5.2

You want the packages to have a deep tuple merge (that is, merge the hashtables based on matching key/values pairs, where $ArrayItem.Property1 -eq $otherArrayItem.Property1, more on this later).

If the default Merge behaviour is MostSpecific, and no override exist for SoftwareBaseline, it will never merge Packages, and always return the Most specific.

If you add a Merge behaviour for the key SoftwareBaseline of hash, it will merge the keys PackageFeed and Packages but not below, that means the result for a `Lookup SoftwareBaseline will be (assuming the Role has the lowest ResolutionPrecedence):

SoftwareBaseline:
  PackageFeed: https://chocolatey.org/api/v2
  Packages:
    - Name: Package2
      Version: v4.5.2

The PackageFeed key is present, but only the most specific Package value has been used (there's only 1 package).

To also merge the Packages, you need to also define the Packages Subkey like so:

default_lookup_option: MostSpecific

lookup_options:
  SoftwareBaseline: hash
  SoftwareBaseline\Packages:
    merge_hash_array: DeepTuple
    merge_options:
      Tuple_Keys:
        - Name
        - Version

If you omit the first key (SoftwareBaseline), and the Lookup is only doing a lookup of that root key, it will never 'walk down' the variable to see what needs merging below the top key. This is the default behaviour in DscInfraSample's RootConfiguration.ps1.

However, if you do a lookup directly to the subkey, Lookup 'SofwareBaseline\Packages', it'll now work (as it does not have to 'walk down' the variable).

Lookup Options

  • Default
  • general
  • per lookup override

Data Handlers - Encrypted Credentials

The data typically stored in Datum is usually defined by the Provider and underlying technology. For the Datum File Provider, and Yaml format, that would be mostly Text/strings, Integer, and Boolean, composed in dictionary (ordered, hashtable, or PSCustomObject), or collections.

More complex objects, such as credentials can be stored or referenced by use of Data handler.

(To be Continued)


5. Origins

Back in 2014, Steve Murawski then working for Stack Exchange lead the way by implementing some tooling, and open sourced them on the PowerShell.Org's Github. This work has been complemented by Dave Wyatt's contribution mainly around the Credential store. After these two main contributors moved on from DSC and Pull Server mode, the project stalled (in the Dev branch), despite its unique value.

I refreshed this to be more geared for PowerShell 5, and updated the dependencies as some projects had evolved and moved to different maintainers, locations, and name.

As I was re-writing it, I found that the version offered a very good way to manage configuration data, but in a prescriptive way, lacking a bit of flexibility for some much needed customisation (layers and ordering). Steve also pointed me to Chef's Databag, and later I discovered Puppet's Hiera, which is where I get most of my inspiration.

datum's People

Contributors

dbroeglin avatar gaelcolas avatar lazywinadmin avatar raandree avatar rdbartram avatar stephanevg avatar ykuijs avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

datum's Issues

Knockouts don't work for: Basetype Arrays

It is not documented but one would expect that removing values using the knockout prefix would work with base type arrays.

Windows Features defined in the role:

WindowsFeatures:
  Names:
    - File-Services
    - Feature1

Windows Features defined on the node:

WindowsFeatures:
  Names:
    - --Feature1

Actual RSOP:

WindowsFeatures:
  Names:
  - File-Services                                                                               AllNodes\Dev\DSCFile01
  - --Feature1                                                                                  AllNodes\Dev\DSCFile01
  - Feature1                                                                                          Roles\FileServer
  - -Telnet-Client                                                                                  Baselines\Security

Expected RSOP:

WindowsFeatures:
  Names:
  - File-Services                                                                               AllNodes\Dev\DSCFile01
  - -Telnet-Client                                                                                  Baselines\Security

The datum.yml definition for merging and knockout looks like this:

WindowsFeatures:
  merge_hash: deep
WindowsFeatures\Names:
  merge_basetype_array: Unique
  merge_options:
    knockout_prefix: --

I am currently working on code to make this working.

Knockouts don't work for: Hashtable Arrays

Even more important than knocking out keys would be knowing out hash tables from a hash table array, similar like removing knocking out values from a base type array. This behavior is not available in Puppet.

Lower Level:

HashTables:
  Items:
    - Key1: h3
      Key2: 3
      Key3: v
    - Key1: h4
      Key2: 4
    - Key1: h5
      Key2: 5

Higher Level:

HashTables:
  Items:
    - Key1: h1
      Key2: 1
    - Key1: h2
      Key2: 2
    - Key1: --h3
      Key2: 3
      Key3: v3

Actual RSOP:

HashTables:
  Items:
  - Key1: h1                                                                                  AllNodes\Dev\DSCFile01
    Key2: 1                                                                                   AllNodes\Dev\DSCFile01
  - Key1: h2                                                                                  AllNodes\Dev\DSCFile01
    Key2: 2                                                                                   AllNodes\Dev\DSCFile01
  - Key3: v3                                                                                  AllNodes\Dev\DSCFile01
    Key1: --h3                                                                                AllNodes\Dev\DSCFile01
    Key2: 3                                                                                   AllNodes\Dev\DSCFile01
  - Key3: v                                                                                         Roles\FileServer
    Key1: h3                                                                                        Roles\FileServer
    Key2: 3                                                                                         Roles\FileServer
  - Key1: h4                                                                                        Roles\FileServer
    Key2: 4                                                                                         Roles\FileServer
  - Key1: h5                                                                                        Roles\FileServer
    Key2: 5                                                                                         Roles\FileServer

Expected RSOP:

HashTables:
  Items:
  - Key1: h1                                                                                  AllNodes\Dev\DSCFile01
    Key2: 1                                                                                   AllNodes\Dev\DSCFile01
  - Key1: h2                                                                                  AllNodes\Dev\DSCFile01
    Key2: 2                                                                                   AllNodes\Dev\DSCFile01
  - Key1: h4                                                                                        Roles\FileServer
    Key2: 4                                                                                         Roles\FileServer
  - Key1: h5                                                                                        Roles\FileServer
    Key2: 5                                                                                         Roles\FileServer

Datum.yml

  HashTables:
    merge_hash: deep
  HashTables\Items:
    merge_hash_array: UniqueKeyValTuples
    merge_options:
      tuple_keys:
        - Key1
        - Key2
      knockout_prefix: --

Instead of knocking out the hash table with the with Key1 == h3, Datum treats it as a new hash table to merge as Lower Level->Key1 is different from Higher Level->Key1.

  • How does knockout work with more than two layers / how is it supposed to work?
  • Is it sufficient to allow a knockout only on the first key in merge_options->tuple_keys

Confusing dates in readme

This is really a minor thing:

In the readme it says:

A stable v1 release is expected for March 2018, while some concepts are thought through, and prototype code refactored.

Today it is november 2019, which is faaaar after march 2018 ;)
Perhaps it would make sense to update this to reflect what the reality of today is? (what is the latest version etc...).
Or remove that sentence?

RSOP not including LcmConfig (because it's static)

Usually every bit of config data is visible in the RSOP output. But the LCM settings are missing the pull server URI and the RegistrationKey which is in the Meta MOFs (so it does exist) but not in the RSOP.

A repo is here: https://github.com/raandree/DatumTest

The RSOP output for node PreServer1

Environment: Pre
Description: CoreDC in CSPre
Name: PreServer1
PSDscAllowPlainTextPassword: true
LcmConfig:
  ConfigurationRepositoryWeb:
    Server:
      ConfigurationNames: PreServer1
SoftwarePackages:
  Package:
  - Name: Password Policy Enforcer 9.1
    Ensure: Present
    Path: D:\Packages\PPE\PPE910.msi
    ProductId: 55EBFC31-1031-463C-A870-416E2C312A64
PSDscAllowDomainUser: true
Configurations:
- FilesAndFolders
- SoftwarePackages
- WindowsFeatures
Role: CoreDC
WindowsFeatures:
  Name:
  - -Telnet-Client
FilesAndFolders:
  Items:
  - DestinationPath: C:\Test\Frankfurt
    Type: Directory
Location: Frankfurt
NodeName: PreServer1

The meta.mof with the content that is missing in RSOP.

/*
@TargetNode='PreServer1'
@GeneratedBy=randr
@GenerationDate=08/30/2019 13:24:26
@GenerationHost=RAANDREE0
*/

instance of MSFT_WebDownloadManager as $MSFT_WebDownloadManager1ref
{
SourceInfo = "::6::2::ConfigurationRepositoryWeb";
 ServerURL = "https://PrePull.contoso.com/PSDSCPullServer.svc";
 ResourceID = "[ConfigurationRepositoryWeb]Server";
 RegistrationKey = "fbc6ef09-ad98-4aad-a062-92b0e0327562";
 ConfigurationNames = {
    "PreServer1"
};

};

instance of MSFT_WebReportManager as $MSFT_WebReportManager1ref
{
SourceInfo = "::6::2::ReportServerWeb";
 ServerURL = "https://PrePull.contoso.com/PSDSCPullServer.svc";
 ResourceID = "[ReportServerWeb]ReportServerWeb";
 RegistrationKey = "fbc6ef09-ad98-4aad-a062-92b0e0327562";

};

instance of MSFT_DSCMetaConfiguration as $MSFT_DSCMetaConfiguration1ref
{
RefreshMode = "Pull";
 AllowModuleOverwrite = True;
 ActionAfterReboot = "ContinueConfiguration";
 RefreshFrequencyMins = 30;
 RebootNodeIfNeeded = True;
 ConfigurationModeFrequencyMins = 30;
 ConfigurationMode = "ApplyAndMonitor";

  ReportManagers = {
  $MSFT_WebReportManager1ref  
 };
  ConfigurationDownloadManagers = {
  $MSFT_WebDownloadManager1ref  
 };
};

instance of OMI_ConfigurationDocument
{
 Version="2.0.0";
 MinimumCompatibleVersion = "2.0.0";
 CompatibleVersionAdditionalProperties= { "MSFT_DSCMetaConfiguration:StatusRetentionTimeInDays" };
 Author="randr";
 GenerationDate="08/30/2019 13:24:26";
 GenerationHost="RAANDREE0";
 Name="RootMetaMOF";
};

Is this by design or does something go wrong?

Command injection via SecureDatum

This code is vulnerable to command injection from the data:
https://github.com/gaelcolas/Datum/blob/master/Datum/classes/SecureDatum.ps1#L44

e.g.

PS C:\> $datum = @{normal=1;evil='[ENC=s";Write-Host "HACKED";$e="]'} | ConvertTo-ProtectedDatum -UnprotectOptions @{ClearTextPassword='doesntmatter'}
PS C:\> $datum
HACKED
HACKED

normal evil
------ ----
     1

This is not great, there are cases where config data will be updated by less trusted persons - and that should not result in execution on the build machine. A quick fix may be to be more explicit with your regex to only accept base64 data but I'm not sure it should be relied on.

Merge-DatumArray doesn't always return an array

During some testing I found that the Merge-DatumArray function doesn't always return an array, but an OrderedDictionary. This results in an error when the next level is merged and Datum throwing the error:
Write-Warning -Message "Cannot merge different types in path '$StartingPath' REF:[hashtable] | DIFF:[hash_array] , returning most specific Datum."

I tracked down this issue to this line:

By adding a , (comma) in front, the function will return an array:

    , $mergedArray

Datum to reference another Datum datum path

Some Config Data should not or cannot be saved at the correct place of the hierarchy (i.e. a config file, or certificate used as a parameter to a Configuration):

MyResource:
  CN: MyCN
  Certificate: "<blob of file content>"

Managing the certificate in-line is not convenient, the file should probably be in it's own 'provider' or mounted somewhere else in the Datum Tree ($Datum.Files.certificates.MyResourceCert).

That means the data structure for MyResource should have the certificate file 'linked' to the Certificate key of the config Data, like so (in a similar pattern that of Secure Datum):

MyResource:
  CN: MyCN
  Certificate: "[REF=[Files\certificates\MyResourceCert]]"

Now, during a lookup $Node 'MyResource', Datum should resource the following hashtable, resolving under the hood the file content (or following what looks like a redirection):

@{
    CN = 'MyCN'
    Certificate = 'blob of file content'
}

Add merge support for PSCustomObject (and array of..)

The current version only supports merging Hashtable, array of hashtables, and basic types.
PSCustomObjects or arrays of PSCustomObjects would throw an exception or silently ignore the required merges.

This is making impossible to merge data that spans across files (as a File/Folder objects returns PSCustomObject by the Datum File Provider), so you can't split a Role as a folder with keys as Files.

The Merged data should be a hashtable, ready to be used for splatting.

Code Quality and Formatting

Creating and reviewing PRs for Datum is a challenge as there is not coherent style guideline used. #91 is just one example where most of the changes are just the result of the auto-format function in VSCode.

Proposal: Add a 'settings.json' and 'analyzersettings.psd1' to the project and then re-format each file.

Using an environment variable in the Datum.yml throws an error

For certain scenarios it is required to access an environment variable to create the ResolutionPrecedence:

ResolutionPrecedence:
  - AllNodes\$($Node.Environment)\$($Node.NodeName)
  - ...
  - LCM\LCM_$($env:BuildLcmMode)
  - ...

This results in the following error when creating the RSOP:

Generating RSOP output for 13 nodes.
Join-Path : Cannot find drive. A drive with the name 'LCM\LCM_$($env' does not exist.
At D:\Git\DatumTest\DSC\BuildOutput\Modules\Datum\0.40.0\datum.psm1:1418 char:26
+         $CurrentSearch = Join-Path $SearchPrefix $PropertyPath
+                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (LCM\LCM_$($env:String) [Join-Path], DriveNotFoundException
    + FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.JoinPathCommand

ERROR: Exception calling "Replace" with "4" argument(s): "Value cannot be null.
Parameter name: input"
At D:\Git\DatumTest\DSC\BuildOutput\Modules\Datum\0.40.0\ScriptsToProcess\Resolve-NodeProperty.ps1:58 char:18

Datum for modules/Resources/Composites

In Hiera, they allow a specific hierarchy for module independent of the global/env ones, yet overridable in the latter.

I really like this idea and it would allow to bundle some data specific to the resource or composite resource, that could be either defined at a layer above of the hierarchy or at runtime on the Node for a resource (not Composite as they're evaluated at MOF compilation time).

To support something similar, DSC Resources could:

  • use a locally defined Datum
  • feed non-mandatory parameters with Datum lookup (if defined above, they're overridden)

Needs to do a PoC of this...

Datum Merge use case: Addition to hashtable[] unique per property name with knockout prefix

Node:
  SoftwareBase:
    Sources: #to test disabling this source on that node
      - Name: chocolatey.licensed
        Disabled: true
        Source: https://chocolatey,licensed.org/api/v2
      - Name: --testToRemove

#+++++++++++++++++
Roles:
  SoftwareBase:
    Sources:
      - Name: chocolatey
        Disabled: false
        Source: https://chocolatey.org/api/v2
      - Name: testToRemove
        Disabled: false
        Source: https://proget/nuget/choco

#=================
Result:
  SoftwareBase:
    Sources: #to test disabling this source on that node
      - Name: chocolatey.licensed
        Disabled: true
        Source: https://chocolatey,licensed.org/api/v2
      - Name: chocolatey
        Disabled: false
        Source: https://chocolatey.org/api/v2

### Datum.yml 
lookup_options:
  SoftwareBase\Sources:
    merge: 
      strategy: ArrayOfUniqueHashByPropertyName
      hashMerge: deep #override/hash/deep?
      PropertyName: Name
      knockoutPrefix: -- #will be matched for PropertyName

$Node Resultant Set of Config Data

Similar to the GPO's RSOP, it'd be very useful to be able to 'Compile' the Configuration Data per Node.
This could be used for testing, or to actually drive the MOF compilation.

Now that the Merge behaviours can be specified per Path in Datum, it should be possible to start from $Node and merging all layers down to produce the Resultant Set of Config Data.

Resolve-NodeProperty does not support $false in DefaultValue parameter

When specifying $false as the DefaultValue for Resolve-NodeProperty it is treated as if no DefaultValue was passed in. If the specified PropertyPath does not exist instead of $false being returned instead we throw the following error:
"The lookup of path '$PropertyPath' for node '$($node.Name)' returned a Null value, but Null is not specified as Default. This is not allowed."

Get-FileProviderData returns [PSObject[]] on Linux

When trying to create a new Datum structure in AzureCloudshell, we're greeted by an error as below.
It turns out that Get-FileProviderData returns an array of PSObject instead of a single Item.
It's most likely an issue with the -NoEnumerate.

Needs further investigation

PS /home/gael/data> $datum = New-DatumStructure -DefinitionFile /home/gael/data/datum.yml
DEBUG: File /home/gael/data/datum.yml found. Loading...
VERBOSE: Getting File Provider Cache for Path: /home/gael/data/datum.yml
Cannot convert the "System.Management.Automation.PSObject[]" value of type "System.Management.Automation.PSObject[]" to type "System.Collections.Hashtable".
At /home/gael/data/datum/0.0.35/Datum.psm1:917 char:17
+ ...             $DatumHierarchyDefinition = Get-FileProviderData $Definit ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : ConvertToFinalInvalidCastException

Improve Merging definitions

The current merging parameters is a bit messy, and could be improved to look like this:

lookup_options:
  MergeTest1:
    merge_options:
      knockout_prefix: --
      PropertyNames:
        - name
        - version
    merge_hash: 
      strategy: MostSpecific|Hash|Deep
    merge_basetype_arrays: 
      strategy: Unique|Sum
    merge_hash_arrays:
      strategy: UniqueTuple|DeepTuple|Sum

That would also allow to simplify and clarify the code underneath.
As this will be a breaking change, it should be done before v1.0 release.

Datum merge use case: deep + ko prefix + array unique merge

Node:
  Role1:
    Subkey1:
      b:
        - val1
        - val2
      c: 3
      --d: NULL

#+++++++++++++++++
Roles:
  Role1:
    Subkey1:
      a: 1
      b:
      - val3
      - val4
      d: 666

#=================
Result:
  Role1:
    Subkey1:
      a: 1
      b:
      - val1
      - val2
      - val3
      - val4
      c: 3

### Datum.yml 
lookup_options:
  Role1\Subkey1:
    merge:
      strategy: deep
      knockoutPrefix: --
  Role1\Subkey1\b:
      strategy: UniqueItems

make the building of rsop nodes declarative

find a way to declare how to get the nodes and load their RSOP in the configdata variable.

So far, the best I found is this:

Variables:
  ConfigurationData:
    AllNodes:
	  Path: Datum:\AllNodes\#{Environment}\#{Name}
	Datum:
	  Path: Datum:\
	  
ConfigurationData:
  AllNodes:
    - 

The main goals are to:

  • define where to find the $Nodes in the hierarchy
  • what path metadata to be loaded (#{property} or * to not include it)

Datum Merge use case: deep + knockout prefix

Node:
  Role1:
    Subkey1:
      c: 3
      --d: NULL
#+++++++++++++++++
Roles:
  Role1:
    Subkey1:
      a: 1
      b: 2
      d: 666
#=================
Result:
  Role1:
    Subkey1:
      a: 1
      b: 2
      c: 3

### Datum.yml 
lookup_options:
  Role1\Subkey1:
    merge:
      strategy: deep
      knockoutPrefix: --

Knockouts don't work for: Hashtables

Knockouts don't work as expected / produce unexpected results assuming that 'knockout_prefix' is implemented similar like in Puppet. There is not much documentation and descriptions in Puppet and the best summary of that feature is on StackOverflow: Using knockout_prefix in puppet hiera hierarchy:

Lower Level:

Car:
  Brand: VW
  Model: Golf
  Engine:
    Volume: 2.0

Higher Level:

Car:
  --Model:
  Color: Red
  Engine:
    Type: Gasoline

Actual RSOP:

Car:
  Color: Red                                                                                    AllNodes\Dev\DSCFile01
  --Model: 
  Engine:
    Type: Gasoline                                                                              AllNodes\Dev\DSCFile01
    Volume: 2                                                                                         Roles\FileServer
  Brand: VW

Expected RSOP:

Car:
  Color: Red                                                                                    AllNodes\Dev\DSCFile01
  Engine:
    Type: Gasoline                                                                              AllNodes\Dev\DSCFile01
    Volume: 2                                                                                         Roles\FileServer
  Brand: VW

Datum.yml

  Car:
    merge_hash: deep
    merge_options:
      knockout_prefix: --

The key model was knocked out but the key for knocking out is added to the result. This is not expected and results in errors when splatting the hashtable to the DSC resource.

  • Is there a reason why the knocking out key is added to the result?
  • When should it been removed? After the merge, while merging?
  • How does knockout work with more than two layers / how is it supposed to work?

A hash table can only be added to another hash table

@gaelcolas, can you have a look at brach https://github.com/AutomatedLab/DscWorkshop/tree/HashTableMergeIssue of DscWorkshop. I have added the following configuration to the DSCFile01 node which leads to the conflict:

FilesAndFolders:
  Items:
    - DestinationPath: C:\TestFolderInDev
      Type: Directory

and that's the merge behavior:

lookup_options:
  Configurations:
    merge_basetype_array: Unique
  
  FilesAndFolders:
    merge_hash: deep

  FilesAndFolders\Items:
    merge_hash_array: DeepItemMergeByTuples

Am I doing something wrong or is this a bug?

Datum Load configuration data fails silently when Data Handler Not available

Seen when loading a Datum Tree without a defined Data handler.
The tree seemed to load fine, the DatumFileProvider looked fine, but the data was actually missing (i.e. $Datum.AllNodes.DEV.SRV01 was empty (but existing).

Validation should be done in New-DatumStructure to make sure everything will work as expected, or raise an exception and stop.

Can we have all ConfigData under $Node.PropertyName?

Hey Gael,

Just some feedback following your presentation since we didn't get to chat properly. I might have missed your intentions but for me as a DSC user I would have thought it simpler and more intuitive to take all the data as you do, apply the ResolutionPrecedence as you do, and then present the resultant values as part of $AllNodes.

A few points why i think this:

  • It will let people access variables using $Node.VariableName which is simple and familliar
  • It pushes the configuration author to respect the fact that a particular value is "preferred" based on the ResolutionPrecedence (but you may want to allow them to override this as you said)
  • It abstracts from the structure of the data which is quite complex, what i mean by this is a lot of data in different formats from different sources is collected. This is likely to result in a complex structure, you yourself had to index into a fair few variables to access the value you wanted. If you flattened this structure respecting the ResolutionPrecedence then it would ease the burden of the author having to figure out where his values are
  • It will be less attractive for authors to make exceptions at a whim
  • Finally its more fitting with the definition of Datum. I would say that each $Node is the "datum". I wouldn't term a complex structure as "datum", that is more "data".

Just some thoughts, otherwise i really like the idea. Don't think ill get much of a chance to use this inside my black box but maybe this will come in handy when i start working on some personal DSC projects.

Remove DSC Specifics From Datum module - Move functions to DscBuildHelpers

Datum is not meant to be DSC Specific, although it's been built mainly to be used within a DSC Pipeline.
It was convenient at that time to bundle function such as Get-DscSplattedResource, but was never intended to stay beyond the initial experimentation.

In an effort to move closer to a v1.0.0, some functions will be removed, including:

Datum variable Interpolation

Similar to Redirections, it would be useful to use variable interpolation in Datum similar to Hiera:

'#{user}@#{domain}'

That would do a lookup for User and a lookup for Domain, and then aggregates them.

Build new mof files only if the specific role or server configuration changed

It would be nice, if there is a stage or task to build only the modified server configurations or all affected servers from a role.

Server1 - role appserver
Server2 - role appserver
Server3- role webserver

Change server1.yaml -> new *.mof for Server1
Change of role appserver -> new *.mof for Server1 and Server2

Is there an easy way to do this?

Case issue for Datum/datum when installing on linux

I found that in Azure cloudshell the module would not load unless targeting the PSD1, and I think it's because of a case issue between the folder it installs to: datum and the PSD1/PSM1 Datum.

I haven't found where the case is dropped, but I suspect it's not PSDeploy but maybe Publish-Module.

Keeping lower case might avoid future issues anyway.

RSOP does not contain consolidated LcmConfig data

This is a clone of issue dsccommunity/DscWorkshop#94. I don't have the rights to transfer the issue.

LcmConfig data cannot be captured with the standard composition key 'Configurations'. But the data can be easily added in case it is there.


When running the Build script, the RSOP output only contains the LcmConfig data that is included in the YAML file of the node itself. The data from the DscBaseline.yml file is missing:
https://github.com/dsccommunity/DscWorkshop/blob/935d168cad1546c4bf37db23f0735380d2d9866d/DSC/DscConfigData/Roles/DscBaseline.yml#L38-L54

Example of DSCFile01.yml

  MaxLcmRuntime: 00:30:00
LcmConfig:
  ConfigurationRepositoryWeb:
    Server:
      ConfigurationNames: DSCFile01
PSDscAllowDomainUser: true

Would be great if these config items would also be included in the RSOP data.

Datum IEX

I personally think that any string beginning with a dollar sign ($) should be ran through Invoke-Expression. That would cover several use cases:

  • $Node.Name
  • $env:ComputerName
  • $((Get-CimInstance Win32_ComputerSystem).Name)

That last two would have to be compiled on the node, but serves as an example. If you really want a string beginning with a $, do: $('$pecial$tring').

Thoughts?

The given assembly name or codebase was invalid

Since upgrading to Datum 0.0.36 the CommonTasks project does no longer build on AppVeyor. When starting the build on the AppVeyor host via RDP manually, it work in PowerShell 5 and 6.

The error is this one: https://ci.appveyor.com/project/AutomatedLab/commontask/builds/21816268.

The 'Microsoft.PowerShell.Management' module was not imported because the 'Microsoft.PowerShell.Management' snap-in was already imported.
The 'Microsoft.PowerShell.Management' module was not imported because the 'Microsoft.PowerShell.Management' snap-in was already imported.
PSDesiredStateConfiguration\Configuration : The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)
At C:\projects\commontask\BuildOutput\Modules\CommonTasks\DSCResources\FilesAndFolders\FilesAndFolders.schema.psm1:1 char:1
+ Configuration FilesAndFolders {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Configuration], FileLoadException
    + FullyQualifiedErrorId : System.IO.FileLoadException,Configuration
 
    [-] FilesAndFolders Compiles 2.67s

Configurations with one item not merging as same object type as multiple configurations

I have example files that demonstrate this issue at https://github.com/thomwatt/datum

If you have a look at Datum.ps1 and once run, look at the $TableData object. The output should return a collection of Windows shares with properties:
•StartupType
•Name
•State

If you look at the output below, it returns objects for the services taken from \Tibco\FTL.yml but for the service taken from \Tibco\Base.yml it is returning key/value pairs (the last 3 objects in the YAML below).

This behaviour only happens if the base.yml file contains one service definition. If it has 2 or more, the output is perfect

  • StartupType: Automatic
    Name: tibJettySvc
    State: Running
  • StartupType: Automatic
    Name: tibrealmserver
    State: Running
  • StartupType: Automatic
    Name: tibrealmserver-as
    State: Running
  • StartupType: Automatic
    Name: tibstore
    State: Running
  • Key: StartupType
    Value: Automatic
  • Key: Name
    Value: Base Service
  • Key: State
    Value: Running

Following our discussion you thought that this may be a bug that you had spotted but not yet released the fix for.

Use a parameterset to load datum from a file path or from Structure/Precedence

Currently Datum is loaded via New-DatumStructure, but the structure path are either aboslute, or relative to the calling script.

By making a different parameterSet where the New-DatumStructure could load its configuration from a file (the Datum.yml), or from a parent folder of a file it'd be easier to load the structure with relative path from that file (and less params to deal with).

Examples for DatumHandlers available?

Hi,
Is there a complete example of how to use the DatumHandler feature? We need to be able to inject variable in the yaml at built time (we need to be able to use a function that get passwords out of SecretServer and inject the result in the yml.) and the Handler seems the way to do it, but I admit it's a bit advanced for my Powershell skills and so far I never got it to work.

New-DatumStructure JSON DefinitionFile broken

A JSON that worked without any issue on version 0.39.0 doesn't work anymore in version 0.40.1

{
    "ResolutionPrecedence": [
      "AllNodes\\$($Node.NodeName)"
    ],
    "DatumHandlersThrowOnError": true,
    "DatumHandlers": {
      "Datum.ProtectedData::ProtectedDatum": {
        "CommandOptions": {
          "PlainTextPassword": "SomeSecret"
        }
      },
      "Datum.InvokeCommand::InvokeCommand": {
        "SkipDuringLoad": true
      }
    }
  }

This json for example doesn't work with 0.40.1, but it works with 0.39.0. If I convert this json to yml it works with both versions.

New-DatumStructure -DefinitionFile .\Datum.json returns

MetadataError: Cannot convert value "@{ResolutionPrecedence=System.Object[]; DatumHandlersThrowOnError=True; DatumHandlers=; __File=C:\tmp\DSCTest\Datum.json}" to type
"System.Collections.Hashtable". Error: "Cannot convert the "@{ResolutionPrecedence=System.Object[]; DatumHandlersThrowOnError=True; DatumHandlers=;
__File=C:\tmp\DSCTest\Datum.json}" value of type "System.Management.Automation.PSCustomObject" to type "System.Collections.Hashtable"."
InvalidOperation: You cannot call a method on a null-valued expression.
InvalidOperation: The property 'DatumDefinitionFile' cannot be found on this object. Verify that the property exists and can be set.
InvalidOperation: You cannot call a method on a null-valued expression.
InvalidOperation: You cannot call a method on a null-valued expression.

File type precedence when multiple files in a folder share a name?

If I've got a structure like below is is possible to say I'd want yml -> psd1 -> json or something similar to let me choose which files should be added to the datum structure?

Nodes
|
|--Node1.yml
|--Node1.json
|--Node1.psd1

The main use case here is that I've got some folders with nodes in them that also contain ARM templates named after the node type, along with config data (which is what I want) and a DSC config. Since all the files match in name Datum is grabbing the json file first, which isn't much use since it's an ARM template.

If I could set a precedence for what file types it should look for first then that would be great, or being able to filter out (or in) file types would be another useful solution.

Getting a lot of warnings in Resolve-NodeProperty when Lookup is used in Composite resource

To implement some additional logic/filtering, I am using the Lookup function in a composite resource. When compiling the MOF files, I am getting a lot of warnings (several hundred):

WARNING: /CompileRootConfiguration C:\src\DscWorkshop\DSC\BuildOutput\Modules\Datum\0.39.0\ScriptsToProcess\Resolve-NodeProperty.ps1:98
	No Datum store found for DSC Resource

This is caused because the following check fails:

if(-not ($here = $MyInvocation.PSScriptRoot)) {
$here = $Pwd.Path
}
Write-Debug "`t`tAttempting to load datum from $($here)."
$ResourceConfigDataPath = Join-Path $here 'ConfigData' -Resolve -ErrorAction SilentlyContinue
if($ResourceConfigDataPath) {

The code I am using in the composite resource is:

    $currentEnvironment = $AllNodes.Environment
    $currentDepartment = $AllNodes.Department
    $currentTier = $AllNodes.Tier

    $servers = @{}
    foreach ($node in $datum.AllNodes.$currentDepartment.$currentEnvironment.$currentTier.ToHashTable().GetEnumerator())
    {
        $nodeObj = $datum.AllNodes.$currentDepartment.$currentEnvironment.$currentTier.$($node.Name)
        $servers.$($node.Name) = Lookup -Node $nodeObj -Datum $datum -PropertyPath 'SharePointFarmConfig/RunCentralAdmin' -WarningAction SilentlyContinue
    }

    $firstAppServer = $servers.Keys | Where-Object -FilterScript { $servers[$_] -eq $true } | Sort-Object | Select-Object -First 1

Datum Arrays with single items do not stay as arrays

Hey Gael,

with Resolve-DatumPath line 34:

$PathItem = ($CurrentNode.$($ExecutionContext.InvokeCommand.ExpandString($StackItem)))

This is breaking my merges as I want to add a single hashtable to an array of hashtables.

Maybe I'm doing something wrong but when I run Get-FileProviderData in PowerShell, it maintains the object as an array. However, when I run the Resolve-DatumPath or Resolve-NodeProperty then it loses its original type.

I've isolated the problem to when this command it run but with strictmode on/off json/yaml/psd1. It doesn't make a difference.

Do you know what this could be or how to solve it?

Thanks

Merge Options

Datum needs different Merge behaviours when searching for datum property in the hierarchy.
Hiera defines those here: https://docs.puppet.com/puppet/5.3/hiera_merging.html

In Datum, Resolve-Datum should be changed here: https://github.com/gaelcolas/Datum/blob/master/Datum/Public/Resolve-Datum.ps1#L58-L65

And it should support:

  • Most Specific (already implemented)
  • All Values (already implemented)
  • Array Merge (all unique items from all arrays)
  • Hash merge (all keys merged, not recursive)
  • Deep merge (all keys merged, if Key's value is hash also merge, if array, do Array Merge)

Other nice to have features:

  • Data source can override lookup options: a configuration uses an option, but the option can be overridden by a user in their config data in control repo.

Configure Many Lookup key with a regex:

  • Litteral wins over Regex
  • first Regex wins

Think about Priority when merging, Hiera has low pri first for hash merge.

Merge not working

Hi,
I've a problem with the merging when using non unique roles (multiples rules assigned to one server). I've created some demo code here
"ResolutionPrecedence": [ "AllNodes\\$($Node.Name)", "Role\\<%= $CurrentNode.PSObject.Properties.where{$_.Name -in $Node.Packages}.Value %>" ],

When using the "dynamic" Datum.json section "firewallrules" then contains two times "items",

{
  "FirewallRules": [
    {
      "Items": [
        {
          "Action": 2,
          "Description": "Test123",
          "Direction": 1,
          "DisplayName": "Test123",
          "RemotePort": "Any",
          "Name": "Test123",
          "LocalAddress": "Any",
          "LocalPort": "123",
          "Service": "Any",
          "Protocol": "TCP",
          "Profile": 0,
          "Program": "Any",
          "RemoteAddress": "Any",
          "Enabled": 1
        }
      ]
    },
    {
      "Items": [
        {
          "Action": 2,
          "Description": "Test456",
          "Direction": 1,
          "DisplayName": "Test456",
          "RemotePort": "Any",
          "Name": "Test456",
          "LocalAddress": "Any",
          "LocalPort": "456",
          "Service": "Any",
          "Protocol": "TCP",
          "Profile": 0,
          "Program": "Any",
          "RemoteAddress": "Any",
          "Enabled": 1
        }
      ]
    }
  ],
  "Configurations": [
    "FirewallRules",
    "FirewallRules"
  ],
  "NodeName": "SRV01",
  "Packages": [
    "Test123",
    "Test456"
  ],
  "Name": "SRV01"
}

when using Datum1.json (that has just hardcoded the two dynamic linked JSON's it just contains one "items" under "firewallrules".

{
  "Configurations": [
    "FirewallRules"
  ],
  "Packages": [
    "Test123",
    "Test456"
  ],
  "FirewallRules": {
    "Items": [
      {
        "Service": "Any",
        "Name": "Test123",
        "RemoteAddress": "Any",
        "LocalPort": "123",
        "RemotePort": "Any",
        "LocalAddress": "Any",
        "Enabled": 1,
        "Description": "Test123",
        "Action": 2,
        "Profile": 0,
        "Protocol": "TCP",
        "Program": "Any",
        "DisplayName": "Test123",
        "Direction": 1
      },
      {
        "Action": 2,
        "Description": "Test456",
        "Direction": 1,
        "DisplayName": "Test456",
        "RemotePort": "Any",
        "Name": "Test456",
        "LocalAddress": "Any",
        "LocalPort": "456",
        "Service": "Any",
        "Protocol": "TCP",
        "Profile": 0,
        "Program": "Any",
        "RemoteAddress": "Any",
        "Enabled": 1
      }
    ]
  },
  "Name": "SRV01",
  "NodeName": "SRV01"
}

Error using Datum Handler / Datum handlers cannot work with arrays

If you have strings in an array that are catched up by a Datum handler, you get this error, as the Datum handler expects a string.

WARNING: Error using Datum Handler Datum.InvokeCommand::InvokeCommand, returning Input Object

The result is pretty malformed data in the RSOP. The following array looks like this then:

TestArray1:
  - '[x={ Get-Random }]'
  - '[x={ Get-Random }]'
TestArray1:
- '[x={ Get-Random }]'
- value:
  - 196783108
  - 685218656
  Count: 2
- value:
  - 1621033775
  - 327351527
  Count: 2

In almost any projects we want to use the handler Datum.InvokeCommand also on arrays.

I have published a working demo of the issue: https://github.com/raandree/DscWorkshop/tree/test/DatumHandlerArrayBug
The same demo with the fix added to Datum: https://github.com/raandree/DscWorkshop/tree/test/DatumHandlerArrayBugFix

Merging error with objects containing only one element

When you merge an object containing only one element with other objects containing more elements you get the following error message:

WARNING: Cannot merge different types in path 'xPSDesiredStateConfiguration\xRegistry' REF:[baseType_array] | DIFF:[hash_array]System.Object[] , returning most specific Datum.

or another example:

WARNING: Cannot merge different types in path 'cChoco\cChocoPackageInstaller' REF:[baseType_array] | DIFF:[hash_array]System.Object[] , returning most specific Datum.

I have found a workaround by adding a fake element to the object with only one element and by ensuring it is absent.

For example:

xPSDesiredStateConfiguration:
xRegistry:
- Key: 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
ValueName: TargetGroup
ValueData: Group1
ValueType: String
Ensure: Present
Force: True
#Fake element as a workaround
- Key: 'HKLM:\SOFTWARE\DatumFakeKey'
ValueName: RandomNumber_654321654
ValueData: 0
ValueType: Dword
Ensure: Absent
Force: True

However, unfortunately all objects don't have an Ensure: Absent|Present property.
Thus this workaround doesn't work for everything...

Datum throws warning when you use hash_array and edit data on three levels

When you use a hash_array to configure data and then edit data on three or more different levels, Datum throws a warning and only uses the data from the highest two levels.

SSLCertificateImport:
    merge_hash: deep
  SSLCertificateImport\Certificates:
    merge_basetype_array: Deep
    merge_hash_array: DeepTuple
    merge_options:
      tuple_keys:
        - Name

If you then edit the data of this composite resource on level 1 and add data on level 2 and 3, the following error is displayed:

WARNING: Cannot merge different types in path 'SSLCertificateImport\Certificates' REF:[hashtable] | DIFF:[hash_array]System.Object[] , returning most specific Datum.

Troubleshooting info

Data Level 1

SSLCertificateImport:
  Certificates:
    - Name: SSLCertificate
      Location: LocalMachine
      Store: My
      Exportable: True
      Ensure: Present

Data Level 2

SSLCertificateImport:
  Certificates:
    - Name: SSLCertificate
      Ensure: Absent

Data Level 3

SSLCertificateImport:
  Certificates:
    - Name: SSLCertificate
      Thumbprint: A1B2C3D4E5F6

Actual result

SSLCertificateImport:
  Certificates:
    - Name: SSLCertificate
      Ensure: Absent
      Thumbprint: A1B2C3D4E5F6

Expected result

SSLCertificateImport:
  Certificates:
    - Name: SSLCertificate
      Location: LocalMachine
      Store: My
      Exportable: True
      Ensure: Absent
      Thumbprint: A1B2C3D4E5F6

Reproduction
If you download the following zip file and run the below commands, the first command will work (SPApplication.yml does not contain data) and the second with show the issue (SPSearchBackEnd.yml does contain data.

Level 1: SharePointServer.yml
Level 2: SPSearchBackEnd.yml
Level 3: StandardAcceptancePrimary.yml

$datum = New-DatumStructure -DefinitionFile .\Datum.yml

$srvApp = $datum.AllNodes.Standard.Acceptance.Primary.ServerApp
$dataApp = Lookup -Node $srvApp -DatumTree $datum -PropertyPath 'SSLCertificateImport'
$dataApp.Certificates

$srvSbe = $datum.AllNodes.Standard.Acceptance.Primary.ServerSbe
$dataSbe = Lookup -Node $srvSbe -DatumTree $datum -PropertyPath 'SSLCertificateImport'
$dataSbe.Certificates

Solution
I found that this issue is caused by the fact that PowerShell doesn't return an array when it only contains one item. This is causing the module later on to try to compare a hashtable with an array. By changing line 606 in Datum.psm1 by the following code the issue is fixed, forcing the item to be stored as an array:

                    if ($clonedReference.$currentKey -is [System.Array])
                    {
                        [System.Array]$clonedReference[$currentKey]  = Merge-Datum @MergeDatumParams
                    }
                    else
                    {
                        $clonedReference[$currentKey]  = Merge-Datum @MergeDatumParams
                    }

Is this a good solution or do you see a different (better) solution?

Datum error after Cumulative Update June

Hi Gael

I've noticed an error after the CU June for my 2016 Server, and i took me some time to find out, what possibly cause this error.

After the (update)reboot i was not longer able to build the mof files. The error message was this one:

===============================================================================
			COMPILE ROOT CONFIGURATION

-------------------------------------------------------------------------------
  /./Compile_Root_Configuration
  C:\Temp\DSC\.build\DSC\ConfigData.build.ps1:63

-----------------------
FilteredNode: 
-----------------------
PSDesiredStateConfiguration\Configuration : The term 'Set-PSTopConfigurationName' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a 
path was included, verify that the path is correct and try again.
At C:\Temp\DSC\RootConfiguration.ps1:7 char:1
+ configuration "RootConfiguration"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (Set-PSTopConfigurationName:String) [Configuration], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : CommandNotFoundException,Configuration
 
ERROR OCCURED DURING COMPILATION
Done /./Compile_Root_Configuration 00:00:15.2377078

As mentioned before, this happened only on a fresh patched buildserver.

PS C:\Temp\DSC> $PSVersionTable
Name                           Value                                                                                                                                                                 
----                           -----
PSVersion                      5.1.14393.2312                                                                                                                                                  
PSEdition                      Desktop                                                                                                                                                            
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                                                                                                                                               
BuildVersion                   10.0.14393.2312                                                                                                                                                
CLRVersion                     4.0.30319.42000                                                                                                                                                
WSManStackVersion              3.0                                                                                                                                                                     
PSRemotingProtocolVersion      2.3                                                                                                                                                                     
SerializationVersion           1.1.0.1 

I copied the Module PSDesiredStateConfiguration from an unpatched prod server to this server with the error to "C:\Windows\System32\WindowsPowerShell\v1.0\Modules\PSDesiredStateConfiguration" and replaced this module. The feshly started build of the mof files went through as usual.

Lookup per Nodename or node's name

To work with Datum (during dev) it'd be easier to do a lookup by using a nodename (i.e. SRV01) instead of $Node, and let the lookup function resolve the $Node via the Configuration Data.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.