Pair a Search with a standard command


Looking for the right syntax I can use to merge a search and loop the results with a command that performs a function.


I want to search for all server tagged "Dev" and add a recipe to their run-list .

knife search "NOT tags:*" -i | knife node run_list add $HOSTNAME 'recipe[MY_RECIPE]'

What would the format and syntax look like for something like this?

Thank you,

Usually it's easier to use knife exec for that, something like this should work:

knife exec -E '"NOT tags:*").each { |n| puts "#{} run list before is: #{n.run_list}"; n.run_list = n.run_list + ["recipe[MY_RECIPE]"]; puts "Run list after: #{n.run_list}; }

I have always done this in the format @hunt is asking about. What you need to add is the tag you want to find, and a for-each after the pipe:

knife search "tags:DEV" -i | ForEach-Object { knife node run_list add $_ 'recipe[MY_RECIPE]' }

But i've been looking for a more performant pattern, since this one has to run knife for every item coming out of the search. I've tried running it in parallel in Powershell 7, and while it works most of the time, for more complex operations it's intermittent in VSCode ps console and in Jenkins.

I've seen knife exec before but it always looked like something for more obscure needs than this. It's more likely that i just don't understand it. The docs don't provide a complete explanation of how to use this API wrapper. @Tensibai, where does the come from? Does that write the result of the script back up to the Chef Server?

More specifically, does this allow us to perform actions like run_list add or policy set in parallel, like we are able to do with knife winrm?

edit: just found this link with similar information: (but still not a deep explanation)

Sorry, the answer was a bit terse, knife exec just run ruby, with all chef classes available.

What it does here is searching for nodes, and on each node object it got, modify it's run list attribute, roughly what can happen in a chef run on a node.

When a node run is successful, the node object is saved back to the server, that's the call to here.

That's a bunch of inner API of chef, but the doc is here (more or less complete depending on the class).

For the parallelism part, I don't think it would be short enough to declare as a one liner, you'd need to code it in pure Ruby, handling threads, etc.

In brief: knife exec -E 'code' is roughly running a ruby script with chef and knife classes already loaded.

could we use knife winrm to exec something locally on each node to do something, like update a run list or set policy? That would provide the parallelism.

knife winrm 'tags:<tag>' 'chef-client <some way to set run_list, policy, tags etc locally on that node>' -U <user> -P <pass>

You can write the desired runlist in a cookbook recipe and then chef-client -o the_cookbook, as -o override the runlist you'd have to redefine it totally, adding a reiipe would add it to what has been given to -o an non to the original runlist and overwrite the original

That may give a bit of overload to the chef-server if too much noded run at the same time that said.

Selfishly, i'm mostly interested in policy set - can that be done on the node, meaning with a knife winrm call that doesn't have to be piped into individual knife node policy set calls?

No idea how policy are saved on nodes as I'm not using them, I'd think they're defined in the runlist, but best idea is to try knife exec -E '"NOT tags:*").each { |n| puts "#{} run list before is: #{n.run_list}" }' and see if the policy are in the runlist or not :wink:

In fact it seems there's a method for policy_name: