Skip to content

Commit

Permalink
(hack) Add bolt module show <module> command
Browse files Browse the repository at this point in the history
This adds a new `bolt module show <module>` command that shows detailed
information for a module. It lists the module's name, version, summary,
available plans and tasks, operating system support, and dependencies.

!feature

* **Show detailed module information**

  Bolt now supports showing detailed information about a module using
  the `bolt module show <module>` command and `Get-BoltModule -Name
  <module>` PowerShell cmdlet.
  • Loading branch information
beechtom committed Jul 20, 2021
1 parent 0dc7f2a commit fb71d07
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 19 deletions.
5 changes: 4 additions & 1 deletion lib/bolt/bolt_option_parser.rb
Expand Up @@ -507,11 +507,14 @@ module Manage Bolt project modules
show
#{colorize(:cyan, 'Usage')}
bolt module show [options]
bolt module show [module name] [options]
#{colorize(:cyan, 'Description')}
List modules available to the Bolt project.
Providing the name of a module will display detailed documentation for
the module.
#{colorize(:cyan, 'Documentation')}
To learn more about Bolt modules, run 'bolt guide module'.
HELP
Expand Down
10 changes: 9 additions & 1 deletion lib/bolt/cli.rb
Expand Up @@ -494,7 +494,11 @@ def execute(options)
when 'group'
list_groups
when 'module'
list_modules
if options[:object]
show_module(options[:object])
else
list_modules
end
when 'plugin'
list_plugins
end
Expand Down Expand Up @@ -852,6 +856,10 @@ def list_modules
outputter.print_module_list(pal.list_modules)
end

def show_module(name)
outputter.print_module_info(**pal.show_module(name))
end

def list_plugins
outputter.print_plugin_list(plugins.list_plugins, pal.user_modulepath)
end
Expand Down
100 changes: 90 additions & 10 deletions lib/bolt/outputter/human.rb
Expand Up @@ -341,7 +341,7 @@ def print_task_info(task)
info << if task.description
indent(2, task.description.chomp)
else
indent(2, 'No description')
indent(2, 'No description available.')
end
info << "\n\n"

Expand Down Expand Up @@ -400,7 +400,7 @@ def print_plan_info(plan)
info << if plan['description']
indent(2, plan['description'].chomp)
else
indent(2, 'No description')
indent(2, 'No description available.')
end
info << "\n\n"

Expand Down Expand Up @@ -480,15 +480,19 @@ def print_plan_lookup(value)
end

def print_module_list(module_list)
info = +''

module_list.each do |path, modules|
if (mod = modules.find { |m| m[:internal_module_group] })
@stream.puts(colorize(:cyan, mod[:internal_module_group]))
else
@stream.puts(colorize(:cyan, path))
end
info << if (mod = modules.find { |m| m[:internal_module_group] })
colorize(:cyan, mod[:internal_module_group])
else
colorize(:cyan, path)
end

info << "\n"

if modules.empty?
@stream.puts('(no modules installed)')
info << '(no modules installed)'
else
module_info = modules.map do |m|
version = if m[:version].nil?
Expand All @@ -500,11 +504,87 @@ def print_module_list(module_list)
[m[:name], version]
end

@stream.puts format_table(module_info, 2, 1)
info << format_table(module_info, 2, 1).to_s
end

@stream.write("\n")
info << "\n\n"
end

command = Bolt::Util.powershell? ? 'Get-BoltModule -Name <MODULE>' : 'bolt module show <MODULE>'
info << colorize(:cyan, "Additional information\n")
info << indent(2, "Use '#{command}' to view details for a specific module.")

@stream.puts info
end

# Prints detailed module information.
#
# @param name [String] The module's short name.
# @param metadata [Hash] The module's metadata.
# @param path [String] The path to the module.
# @param plans [Array] The module's plans.
# @param tasks [Array] The module's tasks.
#
def print_module_info(name:, metadata:, path:, plans:, tasks:, **_kwargs)
info = +''

info << colorize(:cyan, name)

info << colorize(:dim, " [#{metadata['version']}]") if metadata['version']
info << "\n"

info << if metadata['summary']
indent(2, wrap(metadata['summary'].strip, 76))
else
indent(2, "No description available.\n")
end
info << "\n"

if tasks.any?
length = tasks.map(&:first).map(&:length).max
data = tasks.map { |task, desc| [task, truncate(desc, 76 - length)] }
info << colorize(:cyan, "Tasks\n")
info << format_table(data, 2).to_s
info << "\n\n"
end

if plans.any?
length = plans.map(&:first).map(&:length).max
data = plans.map { |plan, desc| [plan, truncate(desc, 76 - length)] }
info << colorize(:cyan, "Plans\n")
info << format_table(data, 2).to_s
info << "\n\n"
end

if metadata['operatingsystem_support']&.any?
supported = metadata['operatingsystem_support'].map do |os|
[os['operatingsystem'], os['operatingsystemrelease']&.join(', ')]
end

info << colorize(:cyan, "Operating system support\n")
info << format_table(supported, 2).to_s
info << "\n\n"
end

if metadata['dependencies']&.any?
dependencies = metadata['dependencies'].map do |dep|
[dep['name'], dep['version_requirement']]
end

info << colorize(:cyan, "Dependencies\n")
info << format_table(dependencies, 2).to_s
info << "\n\n"
end

info << colorize(:cyan, "Path\n")
info << if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH) ||
path.start_with?(Bolt::Config::Modulepath::BOLTLIB_PATH)
indent(2, 'built-in module')
else
indent(2, path)
end

@stream.puts info
end

def print_plugin_list(plugin_list, modulepath)
Expand Down
1 change: 1 addition & 0 deletions lib/bolt/outputter/json.rb
Expand Up @@ -47,6 +47,7 @@ def print_table(results)
@stream.puts results.to_json
end
alias print_module_list print_table
alias print_module_info print_table

def print_task_info(task)
path = task.files.first['path'].chomp("/tasks/#{task.files.first['name']}")
Expand Down
30 changes: 30 additions & 0 deletions lib/bolt/pal.rb
Expand Up @@ -635,6 +635,36 @@ def list_modules
end
end

# Return information about a module.
#
# @param name [String] The name of the module.
# @return [Hash]
#
def show_module(name)
name = name.tr('-', '/')

data = in_bolt_compiler do |_compiler|
mod = Puppet.lookup(:current_environment).module(name.split(%r{[/-]}, 2).last)

unless mod && (mod.forge_name == name || mod.name == name)
raise Bolt::Error.new("Could not find module #{name} on the modulepath.", 'bolt/unknown-module')
end

{
name: mod.forge_name || mod.name,
metadata: mod.metadata,
path: mod.path,
plans: mod.plans.map(&:name).sort,
tasks: mod.tasks.map(&:name).sort
}
end

data[:plans] = list_plans_with_cache.to_h.slice(*data[:plans]).to_a
data[:tasks] = list_tasks_with_cache.to_h.slice(*data[:tasks]).to_a

data
end

def generate_types(cache: false)
require 'puppet/face/generate'
in_bolt_compiler do
Expand Down
4 changes: 4 additions & 0 deletions pwsh_module/command.tests.ps1
Expand Up @@ -256,6 +256,10 @@ Describe "test all bolt command examples" {
$result = Get-BoltModule
$result | Should -Be 'bolt module show'
}
It "bolt module show puppet_agent" {
$result = Get-BoltModule -Name 'puppet_agent'
$result | Should -Be 'bolt module show puppet_agent'
}
}


Expand Down
16 changes: 15 additions & 1 deletion rakelib/pwsh.rake
Expand Up @@ -263,7 +263,8 @@ namespace :pwsh do
when 'module'
# bolt module install
# bolt module add [module]
if @pwsh_command[:verb] == 'Add'
case @pwsh_command[:verb]
when 'Add'
@pwsh_command[:options] << {
name: 'Module',
ruby_short: 'md',
Expand All @@ -275,6 +276,19 @@ namespace :pwsh do
ruby_arg: 'bare',
validate_not_null_or_empty: true
}
# bolt module show
when 'Get'
@pwsh_command[:options] << {
name: 'Name',
ruby_short: 'n',
help_msg: "The module to show",
type: 'string',
switch: false,
mandatory: false,
position: 0,
ruby_arg: 'bare',
validate_not_null_or_empty: true
}
end
when 'lookup'
# bolt lookup <key> [options]
Expand Down
7 changes: 2 additions & 5 deletions spec/bolt/cli_spec.rb
Expand Up @@ -2457,11 +2457,8 @@ def stub_config(file_content = {})
allow(cli).to receive(:outputter)
.and_return(Bolt::Outputter::Human.new(false, false, false, false, output))
cli.parse
modules = cli.list_modules
expect(modules.keys.first).to match(/bolt-modules/)
expect(modules.values.first.map { |h| h[:name] }).to match_array(Dir.children("#{__dir__}/../../bolt-modules"))
expect(modules.values[1].map { |h| h[:name] })
.to include("aggregate", "canary", "puppetdb_fact", "puppetlabs/yaml")
cli.list_modules
expect(output.string).to match(/boltlib.*aggregate.*canary/m)
end
end

Expand Down
5 changes: 4 additions & 1 deletion spec/bolt/outputter/human_spec.rb
Expand Up @@ -97,13 +97,16 @@
[{ name: "boltlib", version: nil, internal_module_group: "Plan Language Modules" },
{ name: "ctrl", version: nil, internal_module_group: "Plan Language Modules" },
{ name: "dir", version: nil, internal_module_group: "Plan Language Modules" }] }
command = Bolt::Util.powershell? ? 'Get-BoltModule -Name <MODULE>' : 'bolt module show <MODULE>'
outputter.print_module_list(modules)
expect(output.string).to eq(<<~TABLE)
expect(output.string).to match(<<~TABLE)
Plan Language Modules
boltlib (built-in)
ctrl (built-in)
dir (built-in)
Additional information
Use '#{command}' to view details for a specific module.
TABLE
end

Expand Down

0 comments on commit fb71d07

Please sign in to comment.