Skip to content

Commit

Permalink
(feat) - Add console command
Browse files Browse the repository at this point in the history
   * Add a new command called console that is a pass through
     to the puppet debugger console.

   * Only allows for use within a module at this time.
  • Loading branch information
logicminds committed Sep 27, 2019
1 parent 2f372c8 commit 68d9167
Show file tree
Hide file tree
Showing 8 changed files with 675 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ group :development do
gem 'activesupport', '4.2.9'
gem 'github_changelog_generator', '~> 1.14'
gem 'pry-byebug', '~> 3.4'
# if you want to test out the pdk console you will need puppet-debugger gem
# the unit tests don't actually call the debugger, so this is not needed
# gem 'puppet-debugger' # only needed in bundler sandbox
if RUBY_VERSION < '2.2.2'
# byebug >= 9.1.0 requires ruby 2.2.0 or newer
gem 'byebug', '~> 9.0.6'
Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ puppet-syntax | Checks for correct syntax in Puppet manifests, templates, and Hi
puppetlabs_spec_helper | Provides classes, methods, and Rake tasks to help with spec testing Puppet code.
rspec-puppet | Tests the behavior of Puppet when it compiles your manifests into a catalog of Puppet resources.
rspec-puppet-facts | Adds support for running rspec-puppet tests against the facts for your supported operating systems.
puppet-debugger | Provides a REPL based debugger console.


## Installation
Expand Down Expand Up @@ -105,6 +106,46 @@ This command runs all available unit tests.

## Experimental features

### `pdk console` command
The pdk console command executes a session of the puppet debugger when inside a module and allows for exploration of puppet code. See the official [puppet debugger site](https://www.puppet-debugger.com) for more info and the official docs [site here.](https://docs.puppet-debugger.com)

To use, execute `pdk console` from inside your module directory. You can also supply the `--puppet-version` or `--pe-version` or `--puppet-dev` to swap out the puppet version when using the console.

Example (from within a module):

* `pdk console --puppet-version=5`
* `pdk console --pe-version=2018.1`

The `pdk console` command will also pass through any puppet debugger arguments you wish to use.

Example:

* `pdk console --no-facterdb`
* `pdk console --play https://gist.github.com/logicminds/4f6bcfd723c92aad1f01f6a800319fa4`
* `pdk console -e "md5('sdfasdfasdf')" --run-once --quiet`

Use `pdk console -h` for a further explanation of pass through arguments.

If you receive the following error you do not have the puppet-debugger gem installed.

```
pdk console -h
Error: Unknown Puppet subcommand 'debugger'
See 'puppet help' for help on available puppet subcommands
```

To fix this you will need to add the following entry to your .sync.yml file and run pdk update:

```
Gemfile:
required:
":development":
- gem: puppet-debugger
version: "~> 0.14"
```

**NOTE**: The puppet-debugger gem has been added to the [puppet-module-* gems](https://github.com/puppetlabs/puppet-module-gems/pull/117), so once you get the gem update you no longer need the .sync.yml entry.

### `pdk bundle` command

This command executes arbitrary commands in a bundler context within the module you're currently working on. Arguments to this command are passed straight through to bundler. This command is experimental and can lead to errors that can't be resolved by PDK itself.
Expand Down
1 change: 1 addition & 0 deletions lib/pdk/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ def self.puppet_dev_option(dsl)
require 'pdk/cli/update'
require 'pdk/cli/validate'
require 'pdk/cli/module'
require 'pdk/cli/console'

@base_cmd.add_command Cri::Command.new_basic_help
end
124 changes: 124 additions & 0 deletions lib/pdk/cli/console.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
module PDK::CLI
@console_cmd = @base_cmd.define_command do
name 'console'
usage _('console [console_options]')
summary _('(Experimental) Start a session of the puppet debugger console.')
default_subcommand 'help'
description _(<<-EOF
The pdk console runs a interactive session of the puppet debugger tool to test out snippets of code, run
language evaluations, datatype prototyping and much more. A virtual playground for your puppet code!
For usage details see the puppet debugger docs at https://docs.puppet-debugger.com.
EOF
)
PDK::CLI.puppet_version_options(self)
PDK::CLI.puppet_dev_option(self)
# we have to skip option parsing because it is expected the user
# will be passing additional args that are passed to the debugger
skip_option_parsing

# TODO: using -h or --help skips the pdk help and passes to puppet debugger help
run do |_opts, args, _cmd|
PDK::CLI::Util.ensure_in_module!(
message: _('Console can only be run from inside a valid module directory'),
log_level: :info,
)

processed_options, processed_args = process_opts(args)

PDK::CLI::Util.validate_puppet_version_opts(processed_options)

PDK::CLI::Util.analytics_screen_view('console', args)

# TODO: figure out if we need to remove default configs set by puppet
# so it is scoped for the module only
# "--environmentpath"...
flags = if PDK::Util.in_module_root?
["--basemodulepath=#{base_module_path}",
"--environmentpath=#{base_module_path}",
"--modulepath=#{base_module_path}"]
else
[]
end
debugger_args = ['debugger'] + processed_args + flags
result = run_in_module(processed_options, debugger_args)

exit result[:exit_code]
end

# Logs a fatal message about the gem missing and how to add it
def inform_user_for_missing_gem(gem_name = 'puppet-debugger', version = '~> 0.14')
PDK.logger.fatal(<<-EOF
Your Gemfile is missing the #{gem_name} gem. You can add the missing gem
by updating your #{File.join(PDK::Util.module_root, '.sync.yml')} file with the following
and running pdk update.
Gemfile:
required:
":development":
- gem: #{gem_name}
version: \"#{version}\"
EOF
)
end

# @return [Boolean] - true if the gem was found in the lockfile
# @param [String] - name of ruby gem to check in bundle lockfile
def gem_in_bundle_lockfile?(gem_name)
PDK.logger.debug("Checking lockfile #{Bundler.default_lockfile} for #{gem_name}")
lock_file = Bundler::LockfileParser.new(Bundler.read_file(Bundler.default_lockfile))
!lock_file.specs.find { |spec| spec.name.eql?(gem_name) }.nil?
end

# @return [Array] - array of split options [{:"puppet-version"=>"6.9.0"}, ['--loglevel=debug']]
# options are for the pdk and debugger pass through
def process_opts(opts)
args = opts.map do |e|
if e =~ %r{\A-{2}puppet|pe\-version|dev}
value = e.split('=')
(value.count < 2) ? value + [''] : value
end
end
args = args.compact.to_h
# symbolize keys
args = args.inject({}) do |memo, (k, v)| # rubocop:disable Style/EachWithObject
memo[k.sub('--', '').to_sym] = v
memo
end
# pass through all other args that are bound for puppet debugger
processed_args = opts.map { |e| e unless e =~ %r{\A-{2}puppet|pe\-version|dev} }.compact
[args, processed_args]
end

# @param opts [Hash] - the options passed into the CRI command
# @param bundle_args [Array] array of bundle exec args and puppet debugger args
# @return [Hash] - a command result hash
def run_in_module(opts, bundle_args)
output = opts[:debug].nil?
puppet_env = PDK::CLI::Util.puppet_from_opts_or_env(opts, output)
gemfile_env = PDK::Util::Bundler::BundleHelper.gemfile_env(puppet_env[:gemset])
PDK::Util::RubyVersion.use(puppet_env[:ruby_version])
PDK::Util::RubyVersion.instance(puppet_env[:ruby_version])
PDK::Util::Bundler.ensure_bundle!(puppet_env[:gemset])
unless gem_in_bundle_lockfile?('puppet-debugger')
inform_user_for_missing_gem
return { exit_code: 1 }
end

debugger_args = %w[exec puppet] + bundle_args
command = PDK::CLI::Exec::InteractiveCommand.new(PDK::CLI::Exec.bundle_bin, *debugger_args).tap do |c|
c.context = :pwd
c.update_environment(gemfile_env)
end
command.execute!
end

# @return [String] - the basemodulepath of the fixtures and modules from the current module
# also includes ./modules in case librarian puppet is used
def base_module_path
base_module_path = File.join(PDK::Util.module_fixtures_dir, 'modules')
"#{base_module_path}:#{File.join(PDK::Util.module_root, 'modules')}"
end
end
end
74 changes: 74 additions & 0 deletions spec/fixtures/module_gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
source ENV['GEM_SOURCE'] || 'https://rubygems.org'

def location_for(place_or_version, fake_version = nil)
git_url_regex = %r{\A(?<url>(https?|git)[:@][^#]*)(#(?<branch>.*))?}
file_url_regex = %r{\Afile:\/\/(?<path>.*)}

if place_or_version && (git_url = place_or_version.match(git_url_regex))
[fake_version, { git: git_url[:url], branch: git_url[:branch], require: false }].compact
elsif place_or_version && (file_url = place_or_version.match(file_url_regex))
['>= 0', { path: File.expand_path(file_url[:path]), require: false }]
else
[place_or_version, { require: false }]
end
end

ruby_version_segments = Gem::Version.new(RUBY_VERSION.dup).segments
minor_version = ruby_version_segments[0..1].join('.')

group :development do
gem "fast_gettext", '1.1.0', require: false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.1.0')
gem "fast_gettext", require: false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1.0')
gem "json_pure", '<= 2.0.1', require: false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0')
gem "json", '= 1.8.1', require: false if Gem::Version.new(RUBY_VERSION.dup) == Gem::Version.new('2.1.9')
gem "json", '= 2.0.4', require: false if Gem::Requirement.create('~> 2.4.2').satisfied_by?(Gem::Version.new(RUBY_VERSION.dup))
gem "json", '= 2.1.0', require: false if Gem::Requirement.create(['>= 2.5.0', '< 2.7.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup))
gem "rb-readline", '= 0.5.5', require: false, platforms: [:mswin, :mingw, :x64_mingw]
gem "puppet-module-posix-default-r#{minor_version}", '~> 0.3', require: false, platforms: [:ruby]
gem "puppet-module-posix-dev-r#{minor_version}", '~> 0.3', require: false, platforms: [:ruby]
gem "puppet-module-win-default-r#{minor_version}", '~> 0.3', require: false, platforms: [:mswin, :mingw, :x64_mingw]
gem "puppet-module-win-dev-r#{minor_version}", '~> 0.3', require: false, platforms: [:mswin, :mingw, :x64_mingw]
gem "puppet-debugger", '~> 0.14.0', require: false
gem "puppet-debugger-playbooks", require: false
end

puppet_version = ENV['PUPPET_GEM_VERSION']
facter_version = ENV['FACTER_GEM_VERSION']
hiera_version = ENV['HIERA_GEM_VERSION']

gems = {}

gems['puppet'] = location_for(puppet_version)

# If facter or hiera versions have been specified via the environment
# variables

gems['facter'] = location_for(facter_version) if facter_version
gems['hiera'] = location_for(hiera_version) if hiera_version

if Gem.win_platform? && puppet_version =~ %r{^(file:///|git://)}
# If we're using a Puppet gem on Windows which handles its own win32-xxx gem
# dependencies (>= 3.5.0), set the maximum versions (see PUP-6445).
gems['win32-dir'] = ['<= 0.4.9', require: false]
gems['win32-eventlog'] = ['<= 0.6.5', require: false]
gems['win32-process'] = ['<= 0.7.5', require: false]
gems['win32-security'] = ['<= 0.2.5', require: false]
gems['win32-service'] = ['0.8.8', require: false]
end

gems.each do |gem_name, gem_params|
gem gem_name, *gem_params
end

# Evaluate Gemfile.local and ~/.gemfile if they exist
extra_gemfiles = [
"#{__FILE__}.local",
File.join(Dir.home, '.gemfile'),
]

extra_gemfiles.each do |gemfile|
if File.file?(gemfile) && File.readable?(gemfile)
eval(File.read(gemfile), binding)
end
end
# vim: syntax=ruby
Loading

0 comments on commit 68d9167

Please sign in to comment.