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