(Working Code Available) Knife environment add or edit cookbook constraints with powershell or knife plugin

I’ve been trying to figure out how to do environment level version pinning using powershell. I feel there has to be a way to easily do this, but I’m not finding a way. Previously, I was using a linux host that was running knife. This was fine and dandy, and allowed the use of sed to do a stream replacement of the data that needed pinning. An example is shown below. It is based on variable replacement using Jenkins builds against a bash script.

# Update the environments version for this cookbook
echo "Updating cookbook version to ${PIPELINE_VERSION} for environment ${ENVIRONMENT}"
EDITOR="sed -i 's/.*${COOKBOOK}.*/    \"${COOKBOOK}\": \"= ${PIPELINE_VERSION}\",/g'" knife environment edit ${ENVIRONMENT}

Doing this is powershell, a language I’m not super familiar with is proving to be more difficult. I feel that something with "knife exec -E “api.post(‘organizations//environments//cookbook_versions’, :run_list => [‘cookbook@1.0.10’])” should work, and is probably optimal. Does anyone have a suggestion as to how this environment cookbook version pinning would best be accomplished?

I also found this knife-pin plugin, but I’m not a big Ruby expert. It appears to version pin a version to nodes, which isn’t what I want to do. I thought about adapting it, but I don’t know how. :frowning:

Any help would be kindly appreciated!

I am getting closer to what I need with this code. Note that it is still a work in progress, as I notice that we never did an initial version pin if it isn’t there.:

param (
    [Parameter(Mandatory=$true)][string]$Environment,
    [Parameter(Mandatory=$true)][string]$Recipe,
    [Parameter(Mandatory=$true)][string]$Version
)

filter sed($before,$after) { %{$_ -replace $before,$after} }

function pinCookbookVersionToEnvironment
{
    $knife_env = @"
    knife environment show $Environment
"@

    $results = Invoke-Expression -Command:$knife_env     
    Write-Host ($results | sed "${Recipe}:\s*= *(\d+).(\d+).(\d+)" "${Recipe}: = ${Version}")      
}

pinCookbookVersionToEnvironment

Here’s my latest version of the pinCookbookVersionToEnvironment function… Note that now I have to rid of an error, but that shouldn’t be much of a problem. Once I figure out that, I will post a final solution, since someone may want to do what I needed to do.

function pinCookbookVersionToEnvironment
{
    $env_lower = $Environment.ToLower()
    $knife_env_show = @"
    knife environment show $env_lower -F json
"@

    $results = Invoke-Expression -Command:$knife_env_show
    
    $temp_file = "$env:temp\chef_env_temp.json"
    $before = """${Recipe}"":\s*""= *(\d+).(\d+).(\d+)"""
    $after = """${Recipe}"": ""= ${Version}"""
    ($results | sed $before $after) | Out-File -FilePath $temp_file -Encoding ascii -Force

    $knife_env_from_file = @"
    knife environment from file "$temp_file"
"@

    Invoke-Expression -Command:$knife_env_from_file
}

Here is the final solution! I hope someone benefits from this work.

param (
    [Parameter(Mandatory=$true)][string]$Environment,
    [Parameter(Mandatory=$true)][string]$Cookbook,
    [Parameter(Mandatory=$true)][string]$Version
)

function pinCookbookVersionToEnvironment
{
    $env_lower = $Environment.ToLower()
    $knife_env_show = @"
    knife environment show $env_lower -F json
"@

    $json = [string](Invoke-Expression -Command:$knife_env_show)
    $jobj = ConvertFrom-Json -InputObject $json

    # Add or update the pinned cookbook version to the environment
    if ($jobj.cookbook_versions.$Cookbook -eq $Null)
    {
        $jobj.cookbook_versions | Add-Member -Name $Cookbook -Value "= $Version" -MemberType NoteProperty
    }
    else
    {
        $jobj.cookbook_versions.$Cookbook = "= $Version"
    }
    
    # Save the modified json object to disk.  Note that ConvertTo-Json is escaping unicode characters, so we have to undo that!
    $temp_file = "$env:temp\chef_env_temp.json"
    $jobj | ConvertTo-Json -Compress | % { [System.Text.RegularExpressions.Regex]::Unescape($_) } | Out-File -FilePath $temp_file -Encoding ascii -Force

    $knife_env_from_file = @"
    knife environment from file "$temp_file" 2>&1 
"@
    
    # Apparently "knife environment from file" likes to return different status codes, this prevents error and actually reports a status
    $output = Invoke-Expression -Command:$knife_env_from_file | %{ "$_" }
    Write-Host $output
}

pinCookbookVersionToEnvironment

Hey all that may have taken a look at this. I found through further testing that ConvertTo-Json likes to "nuke" your json object by not recursing through enough depth. I temporarily lost some data, so I found that this was the solution to that.

ConvertTo-Json -Compress -Depth 50

I also found that Unescaping Unicode was less than optimal. Since my group doesn't use non-exact matches, we likely won't run into problems with that. Thusly, I removed this:

% { [System.Text.RegularExpressions.Regex]::Unescape($_)

Your mileage may vary, and I usually like to not have any imperfect bounds on my code, but I don't have time to make it "perfect". Nonetheless, this knowledge should save you some time if you are using it.

That’s kind of you and congrats for the hard work here, but FWIW my position is to stick to ruby code to play with chef objects, specially under windows where the codepage and carriage return difference with linux could become a real headache.

Knife exec is usually the perfect tool for this kind of operations.

For your particular use case (promoting a version into some environment), knife spork is the first coming to my mind, I think berkshelf have this kind of option too but I never used it so my memory can be faulty here.

Knife spork link: https://github.com/jonlives/knife-spork

P.S: sorry to not have noticed this post before.

Hey thanks! I've noticed that the whole ecosystem of Chef is kind of a pain to search on the internet. I mean, I get it, they went all in with the cooking terms, but using common English words is less than ideal.

From your article, this seems to be what I'd most like:

knife spork promote [ENVIRONMENT OR ENVIRONMENT GROUP NAME] COOKBOOK [--version, --remote]

I did take a look at knife exec, but it was less than ideal on Windows because other bash commands aren't there.

Thanks again!

Well knife exec allow any ruby code, so usually you can do eveything in ruby (for sed in a replace way, I think of .gsub, etc.).

Now if you wish to get a ‘nice’ env under windows, chef-dk comes with most unix utilities, chef shell-init bash should give you a unix like shell to work in (even in a windows environment at start, but still take care of line endings, that’s the major pitfall)