Creating Node Selection on the fly

This topic contains 11 replies, has 2 voices, and was last updated by Profile photo of Simon Broad Simon Broad 7 months, 3 weeks ago.

Viewing 12 posts - 1 through 12 (of 12 total)
  • Author
    Posts
  • #34509
    Profile photo of Simon Broad
    Simon Broad
    Participant

    So I am looking at setting up DSC to manage environments for multiple projects, and within my DSC Configuration I can select the nodes to apply configuration to in one of three ways...

    If I want to build the entire environment for one project I can pass in a parameter $Project and use that to select the nodes for the Node{} block to create mof's for.

    Node $AllNodes.where { $_.Project -eq "$Project" }.NodeName
    {
    ......
    }

    If I want to build all the web servers I can pass in a parameter $Role and select the nodes based on that

    Node $AllNodes.where { $_.Role -eq "$Role" }.NodeName
    {
    ......
    }

    which builds all the web-servers for all the projects, so I can also set up my Node selection like this


    Node $AllNodes.where { ($_.Project -eq "$Project") -and ($_.Role -eq "$Role" ) }.NodeName
    {
    ......
    }

    And that will build all the servers of the select role for the selected project.

    It's a bit messy.

    Problem is, to do this I have a bunch of if/else statements to interrogate the parameters and then select one of the three version of the Node{} block to execute.

    As I say, messy.

    What I would like to try is creating that Node selection string on the fly, I could interrogate the parameters, construct the Node selection criteria, and just have a single Node {} block. That would make sense to me.

    Something like this...

    If ( $Project -and $Role){
    $NodeSelection = "($_.Project -eq "$Project") -and ($_.Role -eq "$Role" )"
    }ElseIf ( $Project){
    $NodeSelection = "($_.Project -eq "$Project")"
    }If ( $Role){
    $NodeSelection = "($_.Role -eq "$Role" )"
    }

    Node $AllNodes.where{$NodeSlection}.NodeName
    {
    ........
    }

    However, no matter what I do, the "$_" in the $NodeSelection either gets interpreted when the $NodeSelection string is created, evaluating to $null, or it never gets evaluated at all and stays as '$_' in the final evaluation.

    This may or may not be the right way to go about what I am doing, but I'd like to see if it could be done, now that I have started.

    Any suggestions?
    Thanks, Simon.

    #34511
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Personally, I only ever have one node block in my configurations. Then I do conditionals inside of it:

    node $AllNodes.NodeName
    {
        if ($node.Project -eq $Project -and $node.Role -eq $Role)
        {
            # Do some resources here
        }
    }
    

    You're writing essentially the same code, but I find it easier to read.

    #34516
    Profile photo of Simon Broad
    Simon Broad
    Participant

    Dave

    Yes, that is easier to read, easier to write too, and possibly the way to go, though essentially it just moves the messy bit and the duplication inside the node block instead of outside it.

    My thinking was that if, say, I wanted to build just 20 of the machines in a configuration data file with 300 machines, by filtering the nodes I want before entering the node block, I only have to execute the node block 20 times instead of 300. And it keeps the actual code in the block smaller and tidier.

    This way also would be more reusable as I could modify the node filtering criteria to my hearts content without ever having to touch the actual code inside the node block.

    And, importantly, it annoys me. I should be able to work out how to do it and I can't, and I don't like that.

    Maybe the saving is irrelevant, I started my programming career in assembly on Motorola 6800's (pre 68000's) and optimisation was the watchword for Everything.

    #34519
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Ah, I see what you mean. You could take a script block filter as a parameter to your configuration function, if you wanted that dynamic filtering:

    configuration Whatever
    {
        param (
            [ValidateNotNull()]
            [scriptblock] $Filter = { $true }
        )
        
        node $AllNodes.Where($Filter).NodeName
        {
            # Do Stuff
        }
    }
    
    # Then to call it:
    
    Whatever -ConfigurationData $someHashTable -Filter { ($_.Project -eq "$Project") -and ($_.Role -eq "$Role" ) }
    
    #34522
    Profile photo of Simon Broad
    Simon Broad
    Participant

    That looks like an interesting option, I'll give it a whirl.
    Thanks.

    #34523
    Profile photo of Simon Broad
    Simon Broad
    Participant

    Couldn't get that to work.
    It keeps complaining that it can't convert the string to a script block, either that or the $_ ends up as a string '$_' and never gets evaluated to an object.

    #34524
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    What does your code look like? I ran the stuff I posted, and it worked fine. Remember, you're not passing in a string, it's a ScriptBlock object. (Curly braces instead of quotation marks.)

    #34525
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Here's what I ran to test:

    configuration Whatever
    {
        param (
            [ValidateNotNull()]
            [scriptblock] $Filter = { $true }
        )
        
        node $AllNodes.Where($Filter).NodeName
        {
            # Do Stuff
            Write-Verbose -Verbose $Node.NodeName
        }
    }
    
    $configData = @{
        AllNodes = @(
            for ($i = 1; $i -lt 10; $i++)
            {
                @{
                    NodeName = "Node$i"
                    Number = $i
                }
            }
        )
    }
    
    Whatever -ConfigurationData $configData -Filter { $_.Number % 2 -eq 0 }
    

    Got verbose output of the even numbered node names, as you'd expect.

    #34697
    Profile photo of Simon Broad
    Simon Broad
    Participant

    Dave
    Sorry, been distracted by work.
    My code is like this:

    Configuration config_three
    {
        param(
            [string] $NodeSelection
        )
    
        node $AllNodes.Where($NodeSelection).NodeName
        {
            Write-Host "  " $Node.NodeName " " $Node.Product " " $Node.Role
    
            File HaveThisDirectory
            {
                Ensure = "Present"
                DestinationPath = "C:\Temp\dud"
                Recurse = $false
                Type = "Directory"
            }
        }
    } 
    
    config_three  -NodeSelection  {$_.Role -eq "build" -and $_.Product -eq "ONE"} -ConfigurationData .\configdata.psd1  -OutputPath C:\Temp\mof\store
    

    And I get the error:

    PSDesiredStateConfiguration\Configuration : Cannot convert the "$_.Role -eq "build" -and $_.Product -eq "ONE"" value of type "System.String" to type "System.Management.Automation.ScriptBlock".
    

    I have tried many variations on how I run this command, but no success.

    #34698
    Profile photo of Simon Broad
    Simon Broad
    Participant

    I have actually taken an entirely different tack on a solution to the original problem now, one that is more likely to suit our usage.

    I keep the Configuration simple, I just process every node in the configuration data.

    What I do now is put the smarts in a higher level script that generates the configuration data file on the fly, so that it only contains the nodes I am interested in and I process them all.

    It keeps the actual Configuration simple, I can create other Configurations for anything I want, and all the Configuration Data files are generated on the fly from a single source where I can maintain all the config data in one place.

    #34709
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    Your parameter was still being declared as type [string], instead of [scriptblock]. (It also had no default value, but that's fine if you want to make it mandatory.) Take a closer look at the example I posted:

    param (
            [ValidateNotNull()]
            [scriptblock] $Filter = { $true }
    )
    
    #34771
    Profile photo of Simon Broad
    Simon Broad
    Participant

    Damn! I knew it would be something simple, and obvious, just missed it entirely.

    I cursed the error message for giving me no clue as to what the problem was, and it was telling me the whole time!

    Thanks.

Viewing 12 posts - 1 through 12 (of 12 total)

You must be logged in to reply to this topic.