Using `chef-shell` to interactively debug attributes and recipes

If you've ever wanted to determine what Chef attributes are generated by Ohai i.e. node['ec2']['region'] or which attributes are available when running your cookbook, you have probably run a recipe with a number of puts outputs, or have manually run ohai on the node.

You'll know that this is a slow and not-quite-ideal situation to be in; what you may not know is that there's a command, chef-shell, which provides you an interactive Ruby REPL, much like how Pry works (which, as an aside - I'm in the process of writing an article about using Pry to debug Chef).

Within this REPL, you also gain access to the node attributes as well as other Chef variables for instance:

chef (13.6.4)> node['uptime']
 => "6 days 23 hours 12 minutes 27 seconds"
chef (13.6.4)> node.run_list
 => #<Chef::RunList:0x00000004c5e0a0 @run_list_items=[#<Chef::RunList::RunListItem:0x00000004b8dfb8 @version=nil, @type=:recipe, @name="cookbook-spectat::default">]>
chef (13.6.4)>

Getting it Running

Due to the deployment models of Chef, chef-shell needs to be started with certain configuration depending on how your node is hooked up.

On nodes that connect to Chef Server

When running against a Chef server, you need to run it in client mode, with -z / --client:

$ chef-shell -z -c client.rb -j dna.json
loading configuration: client.rb
Session type: client
Loading...resolving cookbooks for run list: ["cookbook-spectat::default"]
.Synchronizing cookbooks:
  - yum (0.1.x)
  - yum-epel (1.2.3)
  - php-fpm (2.3.4)
done.

This is the chef-shell.
 Chef Version: 13.6.4
 https://www.chef.io/
 https://docs.chef.io/

run `help' for help, `exit' or ^D to quit.

Ohai2u jamie@TheColonel.localdomain!
Chef (13.6.4)>

Notice that in this case, the shell connects to the Chef server, requesting what versions of dependencies and what the run list was for the node. This also pulls down all attributes that are available for the node, as well as syncing the local cookbook cache.

On nodes that do not connect to Chef Server

When you're not running against a Chef server, for instance when you're using Chef Solo, you will either want to run it in standalone mode:

# or chef-shell --standalone
# or chef-shell -a
$ chef-shell
loading configuration: none (standalone session)
Session type: standalone
Loading....done.

This is the chef-shell.
 Chef Version: 13.6.4
 https://www.chef.io/
 https://docs.chef.io/

run `help' for help, `exit' or ^D to quit.

Ohai2u jamie@TheColonel.localdomain!
chef (13.6.4)>

Or in solo mode:

$ chef-shell --solo # or chef-shell -s
loading configuration: none (standalone session)
Session type: solo
Loading..........................................resolving cookbooks for run list: []
Synchronizing Cookbooks:
done.

This is the chef-shell.
 Chef Version: 13.6.4
 https://www.chef.io/
 https://docs.chef.io/

run `help' for help, `exit' or ^D to quit.

Ohai2u jamie@TheColonel.localdomain!
chef (13.6.4)>

Confusingly, this is different to running chef-client, which asks for -z for a Chef Solo session.

On a Test-Kitchen Node

When running on a node that you're using Test-Kitchen with, you will need to run it at a minimum with -c client.rb to prefer the local cookbooks and config. Additionally, to ensure that attribute config is correct, it's worth also adding -j dna.json:

$ cd /tmp/kitchen
$ chef-shell -c client.rb -j dna.json
loading configuration: client.rb
Session type: standalone
Loading...done.

This is the chef-shell.
 Chef Version: 13.6.4
 https://www.chef.io/
 https://docs.chef.io/

run `help' for help, `exit' or ^D to quit.

Ohai2u kitchen@2be8ff185c16!
Chef (13.6.4)>

Interacting with the Chef Shell

Once you're in your chef-shell, you can now look to list what attributes you have available, and can interactively request information about the node. If we want to know what top-level attributes we have access to, we can take advantage of the node object responding to :keys as if it were a Hash:

chef (13.6.4)> node.keys
 => ["tags", "group", "memory", "cpu", "filesystem", "network", "counters", "ipaddress", "macaddress", "kernel", "lsb", "os", "os_version", "platform", "platform_version", "platform_family", "filesystem2", "virtualization", "dmi", "uptime_seconds", "uptime", "idletime_seconds", "idletime", "fips", "sessions", "block_device", "hostnamectl", "machine_id", "languages", "hostname", "machinename", "fqdn", "domain", "time", "etc", "current_user", "cloud_v2", "shells", "chef_packages", "init_package", "keys", "ohai_time", "root_group", "command", "packages", "sysconf", "recipes", "expanded_run_list", "roles"]

We can then pick out interesting tidbits, such as the fqdn:

chef (13.6.4)> node['fqdn']
 => "822df90e9211"

And even determine what type of virtualisation the system is using - spoiler, we're running this example in a docker container:

chef (13.6.4)> node['virtualization']
 => {"systems"=>{"vbox"=>"host", "kvm"=>"host", "docker"=>"guest"}, "system"=>"docker", "role"=>"guest"}

As mentioned in the chef-shell docs, we can also run in recipe_mode to create our own instances of resources, such as changing owner of the /etc/motd file:

First we need to find out what users we have available:

chef (13.6.4)> node['etc']['passwd'].keys
 => ["root", "daemon", "bin", "sys", "sync", "games", "man", "lp", "mail", "news", "uucp", "proxy", "www-data", "backup", "list", "irc", "gnats", "nobody", "systemd-timesync", "systemd-network", "systemd-resolve", "systemd-bus-proxy", "sshd", "kitchen", "jamie"]

Then we move into recipe_mode and construct our file resource:

chef (13.6.4)> recipe_mode
chef:recipe (13.6.4)> file '/etc/motd' do
chef:recipe > owner 'kitchen'
chef:recipe ?> end
Please see https://docs.chef.io/deprecations_resource_cloning.html for further details and information on how to correct this problem.
 => <file[/etc/motd] @name: "/etc/motd" @noop: nil @before: nil @params: {} @provider: nil @allowed_actions: [:nothing, :create, :delete, :touch, :create_if_missing] @action: [:create] @updated: false @updated_by_last_action: false @supports: {} @ignore_failure: false @retries: 0 @retry_delay: 2 @source_line: "(irb#1):1:in `irb_binding'" @guard_interpreter: nil @default_guard_interpreter: :default @elapsed_time: 0 @sensitive: false @declared_type: :file @cookbook_name: nil @recipe_name: nil @owner: "kitchen">

Once happy, we trigger a Chef run with run_chef, and leave our Chef shell:

chef:recipe (13.6.4)> run_chef
 => true
chef:recipe (13.6.4)> exit
chef (13.6.4)> exit

We can then verify that the file has had its permissions changed:

$ ls -al /etc/motd
-rw-r--r-- 1 kitchen root 286 Nov 19  2017 /etc/motd
*****

Written by Jamie Tanna on 29 August 2018.

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under Apache License 2.0.

Tags

Categories