loading...

Requirements, a PowerShell idempotent and declarative system configuration framework

omiossec profile image Olivier Miossec Updated on ・3 min read

In a DevOps world, concerning configuration management and Infrastructure as code, there are two import notions.

Idempotence, the property of an operation that can be applied several times but only change the state to the desired state only on the initial run.

Declarative is a programming paradigm where you say what is the desired state and not (only) how to do it.

One of my friends, FX @lazywinadmin, pointed me to a new framework, Requirements. It’s a new configuration management framework made with PowerShell Core.

Requirements define a state with a collection of requirements. The collection is used to implement the desired state. 

A requirement can be a script or a DSC resource (for PowerShell 5.x) and is composed by:

  • Name, the name of the requirement. It must be unique in a collection
  • Describe, a description of the requirement such as create a text file or change the mine/type on IIS Website
  • DependsOn, an array containing names of other requirements needed before setting this one

For a script requirement, you also need

  • Test, the test to determine if the requirement is in the desired state or not. The test script needs to return a Boolean, $true or $false
  • Set, the script to put the requirement in the desired state

For a DSC requirement, you will need

  • ResourceName, the name of the DSC resource used in the requirement
  • ModuleName, the name of the module used by the resource. Requirements do not manage module, the module needs to be present on the machine, but you can use a script requirement to perform this task
  • Property, un hashtable representing the configuration

A requirement looks like that

@{  

  Name = "File-Add"  

  Describe = "Add test.txt file"  

  Test = {   
    test-path -Path "c:\test.txt"  
  }  
  Set = {   
     new-item -Path "c:\test.txt"  
  }  
}  

And as you need a collection of requirements you need to use an array

$requirements = @(  
    @{…} ,
    @{…}  
)  

You can also create a requirement object by using New-Requirement cmdlet with the same parameters

To start the configuration simply pass the requirement array object by pipeline to the invoke-Requirements

$MyRequirementsObjectList | invoke-requirements   

You can also use

Invoke-requirements Requirements $MyRequirementsObjectList   

You can also use the Test-Requirements cmdlet to only see if everything is in the desired state.

Let’s look a deeper example. The first one will create a file and set the content in the current directory. This script work with Windows PowerShell and PowerShell Core on Linux or Windows

$ErrorActionPreference = "Stop"
Import-Module Requirements

$PathSeparator = [IO.Path]::DirectorySeparatorChar
$TextContent = "This is a script from Requirements"

$ScriptRequirements = @(
    @{
        Name     = "File script"
        Describe = "File script.txt is present in $PSScriptRoot"
        Test     = { 
            test-path -Path "$($PSScriptRoot)$($PathSeparator)script.txt"
         }
        Set      = { 
            new-item -Path "$($PSScriptRoot)$($PathSeparator)script.txt" | Out-Null 
        }
    },
    @{
        Name     = "Set content in script"
        Describe = "Ensure script.txt contain This is a script from Requirements"
        Test     = { 
            $fileContent = Get-Content -Path "$($PSScriptRoot)$($PathSeparator)script.txt"
            $fileContent -eq $TextContent
         }
        Set      = { 
            set-content -Path "$($PSScriptRoot)$($PathSeparator)script.txt" -Value $TextContent
        }
        DependsOn = @("File script")
    }
)

$ScriptRequirements | Invoke-Requirement | Format-table

Note the

 

$PathSeparator = [IO.Path]::DirectorySeparatorChar  

I use it to deal with the Path separator difference on Linux and Windows so I don’t have to care where I run my script.

The second script does exactly the same things, create a file and set up its content but with DSC so it works on Windows PowerShell.

$ErrorActionPreference = "Stop"
Import-Module Requirements

$file = "$($PSScriptRoot)\dsc.txt"
$TextContent = "This is DSC from Requirements"

$requirement = @{
    Name = "Dsc"
    Describe     = "Dsc Requirement"
    ResourceName = "File"
    ModuleName   = "PSDesiredStateConfiguration"
    Property     = @{
        Contents        = $TextContent
        DestinationPath = $file
        Force           = $true
    }
}
New-Requirement @requirement | Invoke-Requirement

Note that I use the second construction for requirements with New-Requirement to create the object.

The Requirements module work with PowerShell core (on Linux and on Windows) and with Windows PowerShell 5.1. The module is present in the PowerShell Gallery.

To install it

Install-module name Requirement Scope CurentUser   

The project is located on GitHub and it's still under development

Discussion

pic
Editor guide