===================================================== Using |ruby| in InSpec ===================================================== The |inspec| DSL is a |ruby| based DSL for writing audit controls, which includes audit resources that you can invoke. Core and custom resources are written as regular |ruby| classes which inherit from ``Inspec.resource``. Assuming we have a |json| file like this on the node to be tested: .. code-block:: json { "keys":[ {"username":"john", "key":"/opt/keys/johnd.key"}, {"username":"jane", "key":"/opt/keys/janed.key"}, {"username":"sunny ", "key":"/opt/keys/sunnym.key"} ] } The following example shows how you can use pure |ruby| code(variables, loops, conditionals, regular expressions, etc) to run a few tests against the above |json| file: .. code-block:: ruby control 'check-interns' do # use the json inspec resource to get the file json_obj = json('/opt/keys/interns.json') describe json_obj do its('keys') { should_not eq nil } end if json_obj['keys'] # loop over the keys array json_obj['keys'].each do |intern| username = intern['username'].strip # check for white spaces chars in usernames describe username do it { should_not match(/\s/) } end # check key file owners and permissions describe file(intern['key']) do it { should be_owned_by username } its('mode') { should eq 0600 } end end end end Execution ===================================================== It's important to understand that |ruby| code used in custom resources and controls DSL is executed on the system that runs |inspec|. This allows |inspec| to work without |ruby| and rubygems being required on remote targets(servers or containers). For example, using ```ls``` or ``system('ls')`` will result in the ``ls`` command being run locally and not on the target(remote) system. In order to process the output of ``ls`` executed on the target system, use ``inspec.command('ls')`` or ``inspec.powershell('ls')`` Similarly, use ``inspec.file(PATH)`` to access files or directories from remote systems in your tests or custom resources. Using rubygems ===================================================== |ruby| gems are self-contained programs and libraries ... Interactive Debugging with Pry ===================================================== Here's a sample |inspec| control that users |ruby| variables to instantiate an |inspec| resource once and use the content in multipLe tests. .. code-block:: ruby control 'check-perl' do impact 0.3 title 'Check perl compiled options and permissions' perl_out = command('perl -V') #require 'pry'; binding.pry; describe perl_out do its('exit_status') { should eq 0 } its('stdout') { should match (/USE_64_BIT_ALL/) } its('stdout') { should match (/useposix=true/) } its('stdout') { should match (/-fstack-protector/) } end # extract an array of include directories perl_inc = perl_out.stdout.partition('@INC:').last.strip.split("\n") # ensure include directories are only writable by 'owner' perl_inc.each do |path| describe directory(path.strip) do it { should_not be_writable.by('group') } it { should_not be_writable.by('other') } end end end An **advanced** but very useful |ruby| tip. In the previous example, I commented out the ``require 'pry'; binding.pry;`` line. If you remove the ``#`` prefix and run the control, the execution will stop at that line and give you a ``pry`` shell. Use that to troubleshoot, print variables, see methods available, etc. For the above example: .. code-block:: ruby [1] pry> perl_out.exit_status => 0 [2] pry> perl_out.stderr => "" [3] pry> ls perl_out Inspec::Plugins::Resource#methods: inspect Inspec::Resources::Cmd#methods: command exist? exit_status result stderr stdout to_s Inspec::Plugins::ResourceCommon#methods: resource_skipped skip_resource Inspec::Resource::Registry::Command#methods: inspec instance variables: @__backend_runner__ @__resource_name__ @command @result [4] pry> perl_out.stdout.partition('@INC:').last.strip.split("\n") => ["/Library/Perl/5.18/darwin-thread-multi-2level", " /Library/Perl/5.18", ...REDACTED... [5] pry> exit # or abort You can use ``pry`` inside both the controls DSL and resources. Similarly, for dev and test, you can use ``inspec shell`` which is based on ``pry``, for example: .. code-block:: ruby $ inspec shell Welcome to the interactive InSpec Shell To find out how to use it, type: help inspec> command('ls /home/gordon/git/inspec/docs').stdout => "ctl_inspec.rst\ndsl_inspec.rst\ndsl_resource.rst\n" inspec> command('ls').stdout.split("\n") => ["ctl_inspec.rst", "dsl_inspec.rst", "dsl_resource.rst"] inspec> help command Name: command Description: Use the command InSpec audit resource to test an arbitrary command that is run on the system. Example: describe command('ls -al /') do it { should exist } its('stdout') { should match /bin/ } its('stderr') { should eq '' } its('exit_status') { should eq 0 } end .. |inspec| replace:: InSpec .. |chef compliance| replace:: Chef Compliance .. |ruby| replace:: Ruby .. |csv| replace:: CSV .. |json| replace:: JSON