Monthly Archives: November 2013

Chef Windows Perf — Disabling Ohai Plugins

From Doug Ireton’s chefconf slides — here’s a code snippet for disabling underperforming or irrelevant Ohai plugins on your windows chef node.

Ohai::Config::disabled_plugins= [
 #
 # add to client.rb file -- c:\chef\client.rb
 #
 # ref: http://www.slideshare.net/opscode/chef-conf-windowsdougireton # slide 30
 # ohai plugins that have poor perf or are irrelevant to windows
 #
 "c", "cloud", "ec2", "rackspace", "eucalyptus", "command", "dmi", "dmi_common",
 "erlang", "groovy", "ip_scopes", "java", "keys", "lua", "mono", "network_listeners",
 "passwd", "perl", "php", "python", "ssh_host_key", "uptime", "virtualization",
 "windows::virtualization", "windows::kernel_devices"
 ]

The above snippet is also available here: https://gist.github.com/tcotav/7566353

Here’s a link to the relevant opscode docs on this: https://wiki.opscode.com/display/chef/Disabling+Ohai+Plugins

Idempotency, Chef, Powershell, Windows

Here are some notes on writing idempotent recipes in Chef using Powershell on Windows

make powershell fully functional as standalone script on windows first (and second, and third)
– are you sure you got the first thing done properly? y/n?

Here’s a crude skeleton for a base powershell script:

######################
try {
 $Some-Crazy-Command;
 $message = "We made it";
 $exitVal=2;
}
catch
{
 $message = "Join Error - ";
 # tack on the thrown error string here, $_
 $message += $_;
 $exitVal=1;
}
write-host $message;
exit $exitVal;
}
########################

Basically, you just want to pass along an exit status and message that chef will be able to key off.

You could easily add additional states with corresponding exit codes to the try block. Why do we do this? Well, we need to know if the script did what it was supposed to or not. In the example below, we want to know whether to restart the host after adding it to an AD domain.

So then, within the powershell script itself is where you want to manage your idempotency (or at least, it is more likely where you’re going to HAVE to manage it.

In the join AD example (assuming a new host — not a change… see below for notes on that), in the powershell script we would:

1) is the host already a member of an AD Domain?
2) if not, then join to a domain

* The case where it is a member of a different domain and you want to change it is more complicated as you would probably need to Remove-Computer and then reboot. On coming up, it would be a member of no domain, and on next chef run, would join new domain.

The powershell would look something like this:

function addComputer { param([string]$username, [string]$password, [string]$domain)
try {
 if ((gwmi win32_computersystem).partofdomain -eq $true) {
  # arguably here, I would check if it is the RIGHT domain... next rev...
  # $domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
  # $domainName = $domain.name
  # < compare with passed in value >
  $message = \"The system is joined to the domain\";
  $exitVal=2;
 }
 else {
  add-computer -domain $domain -credential (New-Object System.Management.Automation.PSCredential   ($username, (ConvertTo-SecureString $password -AsPlainText -Force))) -passthru -verbose
  $message = \"computer joined to domain\";
  $exitVal=3;
 }
}
catch {
  $message = \"Join Error - \";
  $message += $_;
  $exitVal=1;
}
  write-host $message;
  exit $exitVal;
}

# this next line uses ruby
addComputer #{node['ad']['user']} #{node['ad']['pwd']} #{node['ad']['domain']}

here’s a gist of a more final (and better formatted) version of this: https://gist.github.com/tcotav/7489860

Now ANOTHER (potentially more *nix-y) way to do this is instead of a single monolithic script, you would just shell out for all the bits and then process the output in chef/ruby. The possible issue with going that way is that it would be more expensive resource (and probably time-wise) to continually spin up a powershell process to handle each command. This would be more relevant if you had 30 little cmdlets that you wanted to invoke.

Okay, you’ve looked at the gist and saw a line that made you wonder WTF?  It looked like this:

::Chef::Recipe.send(:include, Chef::Mixin::PowershellOut)

Well, we need that more than anything because we use it to capture the exit status and stdout/stderr of the powershell shellout.  There’s some debug log code in there to dump out these values (you know — for posterity).  That bit is:

result = powershell_out(script)
Chef::Log.debug("powershell exit #{result.exitstatus}")
Chef::Log.debug("powershell error #{result.stderr}")
Chef::Log.debug("powershell stdout #{result.stdout}")

This is just what it looks like.  We run the script (a variable brilliantly named “script” here).  The results go into… result.  Then from that object we access the 3 variables mentioned earlier.  Those then are what we use to pass the messages back OUT of the powershell process to Chef.

  # same as shell_out
  if result.exitstatus == 2
    Chef::Log.debug("Already part of domain: #{result.stdout}")
  elsif result.exitstatus == 3
    Chef::Log.debug("Joined domain: #{result.stdout}")
    # reboot if joining domain
    notifies :request, 'windows_reboot[5]', :delayed
  else
    Chef::Log.error("Domain join fail: #{result.stdout}")
    # any other actions here?  maybe flag the node?
  end

We don’t do much with the return, but we do something — we notify the windows reboot we CLEVERLY inserted earlier into our Chef recipe. What this line tells the recipe to do is to queue up a windows reboot AFTER the rest of the runlist for this host are done. In the context of our little example though, it shows how we would be able to interact and take action based on powershell runs.

And that’s about it for now.