bolt 3.14.1 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +137 -104
- data/guides/debugging.yaml +27 -0
- data/guides/inventory.yaml +23 -0
- data/guides/links.yaml +12 -0
- data/guides/logging.yaml +17 -0
- data/guides/module.yaml +18 -0
- data/guides/modulepath.yaml +24 -0
- data/guides/project.yaml +21 -0
- data/guides/targets.yaml +28 -0
- data/guides/transports.yaml +22 -0
- data/lib/bolt/analytics.rb +2 -19
- data/lib/bolt/application.rb +634 -0
- data/lib/bolt/bolt_option_parser.rb +28 -4
- data/lib/bolt/cli.rb +592 -788
- data/lib/bolt/fiber_executor.rb +7 -3
- data/lib/bolt/inventory/inventory.rb +68 -39
- data/lib/bolt/inventory.rb +2 -9
- data/lib/bolt/module_installer/puppetfile.rb +24 -10
- data/lib/bolt/outputter/human.rb +83 -32
- data/lib/bolt/outputter/json.rb +63 -38
- data/lib/bolt/pal/yaml_plan/transpiler.rb +1 -1
- data/lib/bolt/pal.rb +31 -11
- data/lib/bolt/plan_creator.rb +84 -25
- data/lib/bolt/plan_future.rb +11 -6
- data/lib/bolt/plan_result.rb +1 -1
- data/lib/bolt/plugin/task.rb +1 -1
- data/lib/bolt/plugin.rb +11 -17
- data/lib/bolt/project.rb +0 -7
- data/lib/bolt/result_set.rb +2 -1
- data/lib/bolt/transport/local/connection.rb +17 -1
- data/lib/bolt/transport/orch/connection.rb +13 -1
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/file_cache.rb +12 -0
- data/lib/bolt_server/schemas/action-apply.json +32 -0
- data/lib/bolt_server/schemas/action-apply_prep.json +19 -0
- data/lib/bolt_server/transport_app.rb +113 -24
- data/lib/bolt_spec/bolt_context.rb +1 -1
- data/lib/bolt_spec/run.rb +1 -1
- metadata +14 -3
- data/lib/bolt/secret.rb +0 -37
    
        data/lib/bolt/fiber_executor.rb
    CHANGED
    
    | @@ -52,7 +52,7 @@ module Bolt | |
| 52 52 | 
             
                  # tracking which Futures to wait on when `wait()` is called without
         | 
| 53 53 | 
             
                  # arguments.
         | 
| 54 54 | 
             
                  @id += 1
         | 
| 55 | 
            -
                  future = Bolt::PlanFuture.new(future, @id, name: name, plan_id: plan_id)
         | 
| 55 | 
            +
                  future = Bolt::PlanFuture.new(future, @id, name: name, plan_id: plan_id, scope: newscope)
         | 
| 56 56 | 
             
                  @logger.trace("Created future #{future.name}")
         | 
| 57 57 |  | 
| 58 58 | 
             
                  # Register the PlanFuture with the FiberExecutor to be executed
         | 
| @@ -68,11 +68,15 @@ module Bolt | |
| 68 68 | 
             
                #
         | 
| 69 69 | 
             
                def round_robin
         | 
| 70 70 | 
             
                  active_futures.each do |future|
         | 
| 71 | 
            -
                    # If the Fiber is still running and can be resumed, then resume it
         | 
| 71 | 
            +
                    # If the Fiber is still running and can be resumed, then resume it.
         | 
| 72 | 
            +
                    # Override Puppet's global_scope to prevent ephemerals in other scopes
         | 
| 73 | 
            +
                    # from being popped off in the wrong order due to race conditions.
         | 
| 74 | 
            +
                    # This primarily happens when running executor functions from custom
         | 
| 75 | 
            +
                    # Puppet language functions, but may happen elsewhere.
         | 
| 72 76 | 
             
                    @logger.trace("Checking future '#{future.name}'")
         | 
| 73 77 | 
             
                    if future.alive?
         | 
| 74 78 | 
             
                      @logger.trace("Resuming future '#{future.name}'")
         | 
| 75 | 
            -
                      future.resume
         | 
| 79 | 
            +
                      Puppet.override(global_scope: future.scope) { future.resume }
         | 
| 76 80 | 
             
                    end
         | 
| 77 81 |  | 
| 78 82 | 
             
                    # Once we've restarted the Fiber, check to see if it's finished again
         | 
| @@ -6,7 +6,7 @@ require 'bolt/inventory/target' | |
| 6 6 | 
             
            module Bolt
         | 
| 7 7 | 
             
              class Inventory
         | 
| 8 8 | 
             
                class Inventory
         | 
| 9 | 
            -
                  attr_reader : | 
| 9 | 
            +
                  attr_reader :plugins, :source, :targets, :transport
         | 
| 10 10 |  | 
| 11 11 | 
             
                  class WildcardError < Bolt::Error
         | 
| 12 12 | 
             
                    def initialize(target)
         | 
| @@ -14,25 +14,15 @@ module Bolt | |
| 14 14 | 
             
                    end
         | 
| 15 15 | 
             
                  end
         | 
| 16 16 |  | 
| 17 | 
            -
                  # TODO: Pass transport config instead of config object
         | 
| 18 17 | 
             
                  def initialize(data, transport, transports, plugins, source = nil)
         | 
| 19 | 
            -
                    @logger | 
| 20 | 
            -
                    @data | 
| 21 | 
            -
                    @transport | 
| 22 | 
            -
                    @config | 
| 23 | 
            -
                    @ | 
| 24 | 
            -
                    @ | 
| 25 | 
            -
                    @ | 
| 26 | 
            -
                    @ | 
| 27 | 
            -
                    @source       = source
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                    @groups.resolve_string_targets(@groups.target_aliases, @groups.all_targets)
         | 
| 30 | 
            -
             | 
| 31 | 
            -
                    collect_groups
         | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
             | 
| 34 | 
            -
                  def validate
         | 
| 35 | 
            -
                    @groups.validate
         | 
| 18 | 
            +
                    @logger          = Bolt::Logger.logger(self)
         | 
| 19 | 
            +
                    @data            = data || {}
         | 
| 20 | 
            +
                    @transport       = transport
         | 
| 21 | 
            +
                    @config          = transports
         | 
| 22 | 
            +
                    @config_resolved = transports.values.all?(&:resolved?)
         | 
| 23 | 
            +
                    @plugins         = plugins
         | 
| 24 | 
            +
                    @targets         = {}
         | 
| 25 | 
            +
                    @source          = source
         | 
| 36 26 | 
             
                  end
         | 
| 37 27 |  | 
| 38 28 | 
             
                  def version
         | 
| @@ -43,13 +33,52 @@ module Bolt | |
| 43 33 | 
             
                    Bolt::Target
         | 
| 44 34 | 
             
                  end
         | 
| 45 35 |  | 
| 46 | 
            -
                   | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 36 | 
            +
                  # Load and resolve the groups in the inventory. Loading groups resolves
         | 
| 37 | 
            +
                  # all plugin references except for those for target data and config.
         | 
| 38 | 
            +
                  #
         | 
| 39 | 
            +
                  # @return [Bolt::Inventory::Group]
         | 
| 40 | 
            +
                  #
         | 
| 41 | 
            +
                  def groups
         | 
| 42 | 
            +
                    @groups ||= Group.new(@data, @plugins, all_group: true).tap do |groups|
         | 
| 43 | 
            +
                      groups.resolve_string_targets(groups.target_aliases, groups.all_targets)
         | 
| 44 | 
            +
                      groups.validate
         | 
| 45 | 
            +
                    end
         | 
| 49 46 | 
             
                  end
         | 
| 50 47 |  | 
| 48 | 
            +
                  # Return a list of all group names in the inventory.
         | 
| 49 | 
            +
                  #
         | 
| 50 | 
            +
                  # @return [Array[String]]
         | 
| 51 | 
            +
                  #
         | 
| 51 52 | 
             
                  def group_names
         | 
| 52 | 
            -
                     | 
| 53 | 
            +
                    group_lookup.keys
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  # Return a map of all groups in the inventory.
         | 
| 57 | 
            +
                  #
         | 
| 58 | 
            +
                  # @return [Hash[String, Bolt::Inventory::Group]]
         | 
| 59 | 
            +
                  #
         | 
| 60 | 
            +
                  def group_lookup
         | 
| 61 | 
            +
                    @group_lookup ||= groups.collect_groups
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  # Return a map of transport configuration for the inventory. Any
         | 
| 65 | 
            +
                  # unresolved plugin references are resolved.
         | 
| 66 | 
            +
                  #
         | 
| 67 | 
            +
                  # @return [Hash[String, Bolt::Config::Transport]]
         | 
| 68 | 
            +
                  #
         | 
| 69 | 
            +
                  def config
         | 
| 70 | 
            +
                    if @config_resolved
         | 
| 71 | 
            +
                      @config
         | 
| 72 | 
            +
                    else
         | 
| 73 | 
            +
                      @config_resolved = true
         | 
| 74 | 
            +
                      @config.transform_values! { |t| t.resolved? ? t : t.resolve(@plugins) }
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  # Validates the inventory.
         | 
| 79 | 
            +
                  #
         | 
| 80 | 
            +
                  def validate
         | 
| 81 | 
            +
                    groups.validate
         | 
| 53 82 | 
             
                  end
         | 
| 54 83 |  | 
| 55 84 | 
             
                  def group_names_for(target_name)
         | 
| @@ -57,7 +86,7 @@ module Bolt | |
| 57 86 | 
             
                  end
         | 
| 58 87 |  | 
| 59 88 | 
             
                  def target_names
         | 
| 60 | 
            -
                     | 
| 89 | 
            +
                    groups.all_targets
         | 
| 61 90 | 
             
                  end
         | 
| 62 91 | 
             
                  # alias for analytics
         | 
| 63 92 | 
             
                  alias node_names target_names
         | 
| @@ -81,7 +110,7 @@ module Bolt | |
| 81 110 |  | 
| 82 111 | 
             
                  #### PRIVATE ####
         | 
| 83 112 | 
             
                  def group_data_for(target_name)
         | 
| 84 | 
            -
                     | 
| 113 | 
            +
                    groups.group_collect(target_name)
         | 
| 85 114 | 
             
                  end
         | 
| 86 115 |  | 
| 87 116 | 
             
                  # If target is a group name, expand it to the members of that group.
         | 
| @@ -89,15 +118,15 @@ module Bolt | |
| 89 118 | 
             
                  # If a wildcard string, error if no matches are found.
         | 
| 90 119 | 
             
                  # Else fall back to [target] if no matches are found.
         | 
| 91 120 | 
             
                  def resolve_name(target)
         | 
| 92 | 
            -
                    if (group =  | 
| 121 | 
            +
                    if (group = group_lookup[target])
         | 
| 93 122 | 
             
                      group.all_targets
         | 
| 94 123 | 
             
                    else
         | 
| 95 124 | 
             
                      # Try to wildcard match targets in inventory
         | 
| 96 125 | 
             
                      # Ignore case because hostnames are generally case-insensitive
         | 
| 97 126 | 
             
                      regexp = Regexp.new("^#{Regexp.escape(target).gsub('\*', '.*?')}$", Regexp::IGNORECASE)
         | 
| 98 127 |  | 
| 99 | 
            -
                      targets =  | 
| 100 | 
            -
                      targets +=  | 
| 128 | 
            +
                      targets = groups.all_targets.select { |targ| targ =~ regexp }
         | 
| 129 | 
            +
                      targets += groups.target_aliases.select { |target_alias, _target| target_alias =~ regexp }.values
         | 
| 101 130 |  | 
| 102 131 | 
             
                      if targets.empty?
         | 
| 103 132 | 
             
                        raise(WildcardError, target) if target.include?('*')
         | 
| @@ -165,7 +194,7 @@ module Bolt | |
| 165 194 | 
             
                  # associated references. This is used when a target is resolved by
         | 
| 166 195 | 
             
                  # get_targets.
         | 
| 167 196 | 
             
                  def create_target_from_inventory(target_name)
         | 
| 168 | 
            -
                    target_data =  | 
| 197 | 
            +
                    target_data = groups.target_collect(target_name) || { 'uri' => target_name }
         | 
| 169 198 |  | 
| 170 199 | 
             
                    target = Bolt::Inventory::Target.new(target_data, self)
         | 
| 171 200 | 
             
                    @targets[target.name] = target
         | 
| @@ -187,24 +216,24 @@ module Bolt | |
| 187 216 | 
             
                    @targets[new_target.name] = new_target
         | 
| 188 217 |  | 
| 189 218 | 
             
                    if existing_target
         | 
| 190 | 
            -
                      clear_alia_from_group( | 
| 219 | 
            +
                      clear_alia_from_group(groups, new_target.name)
         | 
| 191 220 | 
             
                    else
         | 
| 192 221 | 
             
                      add_to_group([new_target], 'all')
         | 
| 193 222 | 
             
                    end
         | 
| 194 223 |  | 
| 195 224 | 
             
                    if new_target.target_alias
         | 
| 196 | 
            -
                       | 
| 225 | 
            +
                      groups.insert_alia(new_target.name, Array(new_target.target_alias))
         | 
| 197 226 | 
             
                    end
         | 
| 198 227 |  | 
| 199 228 | 
             
                    new_target
         | 
| 200 229 | 
             
                  end
         | 
| 201 230 |  | 
| 202 231 | 
             
                  def validate_target_from_hash(target)
         | 
| 203 | 
            -
                     | 
| 204 | 
            -
                     | 
| 232 | 
            +
                    used_groups  = Set.new(group_names)
         | 
| 233 | 
            +
                    used_targets = target_names
         | 
| 205 234 |  | 
| 206 235 | 
             
                    # Make sure there are no group name conflicts
         | 
| 207 | 
            -
                    if  | 
| 236 | 
            +
                    if used_groups.include?(target.name)
         | 
| 208 237 | 
             
                      raise ValidationError.new("Target name #{target.name} conflicts with group of the same name", nil)
         | 
| 209 238 | 
             
                    end
         | 
| 210 239 |  | 
| @@ -217,11 +246,11 @@ module Bolt | |
| 217 246 | 
             
                    end
         | 
| 218 247 |  | 
| 219 248 | 
             
                    # Make sure there are no conflicts with the new target aliases
         | 
| 220 | 
            -
                    used_aliases =  | 
| 249 | 
            +
                    used_aliases = groups.target_aliases
         | 
| 221 250 | 
             
                    Array(target.target_alias).each do |alia|
         | 
| 222 | 
            -
                      if  | 
| 251 | 
            +
                      if used_groups.include?(alia)
         | 
| 223 252 | 
             
                        raise ValidationError.new("Alias #{alia} conflicts with group of the same name", nil)
         | 
| 224 | 
            -
                      elsif  | 
| 253 | 
            +
                      elsif used_targets.include?(alia)
         | 
| 225 254 | 
             
                        raise ValidationError.new("Alias #{alia} conflicts with target of the same name", nil)
         | 
| 226 255 | 
             
                      elsif used_aliases[alia] && used_aliases[alia] != target.name
         | 
| 227 256 | 
             
                        raise ValidationError.new(
         | 
| @@ -250,7 +279,7 @@ module Bolt | |
| 250 279 | 
             
                    end
         | 
| 251 280 |  | 
| 252 281 | 
             
                    if group_names.include?(desired_group)
         | 
| 253 | 
            -
                      remove_target( | 
| 282 | 
            +
                      remove_target(groups, @targets[target.first.name], desired_group)
         | 
| 254 283 | 
             
                    else
         | 
| 255 284 | 
             
                      raise ValidationError.new("Group #{desired_group} does not exist in inventory", nil)
         | 
| 256 285 | 
             
                    end
         | 
| @@ -260,7 +289,7 @@ module Bolt | |
| 260 289 | 
             
                    if group_names.include?(desired_group)
         | 
| 261 290 | 
             
                      targets.each do |target|
         | 
| 262 291 | 
             
                        # Add the inventory copy of the target
         | 
| 263 | 
            -
                        add_target( | 
| 292 | 
            +
                        add_target(groups, @targets[target.name], desired_group)
         | 
| 264 293 | 
             
                      end
         | 
| 265 294 | 
             
                    else
         | 
| 266 295 | 
             
                      raise ValidationError.new("Group #{desired_group} does not exist in inventory", nil)
         | 
    
        data/lib/bolt/inventory.rb
    CHANGED
    
    | @@ -91,19 +91,12 @@ module Bolt | |
| 91 91 | 
             
                    end
         | 
| 92 92 | 
             
                  end
         | 
| 93 93 |  | 
| 94 | 
            -
                  # Resolve plugin references from transport config
         | 
| 95 | 
            -
                  config.transports.each_value do |t|
         | 
| 96 | 
            -
                    t.resolve(plugins) unless t.resolved?
         | 
| 97 | 
            -
                  end
         | 
| 98 | 
            -
             | 
| 99 94 | 
             
                  Bolt::Validator.new.tap do |validator|
         | 
| 100 95 | 
             
                    validator.validate(data, schema, source)
         | 
| 101 96 | 
             
                    validator.warnings.each { |warning| Bolt::Logger.warn(warning[:id], warning[:msg]) }
         | 
| 102 97 | 
             
                  end
         | 
| 103 98 |  | 
| 104 | 
            -
                   | 
| 105 | 
            -
                  inventory.validate
         | 
| 106 | 
            -
                  inventory
         | 
| 99 | 
            +
                  create_version(data, config.transport, config.transports, plugins, source)
         | 
| 107 100 | 
             
                end
         | 
| 108 101 |  | 
| 109 102 | 
             
                def self.create_version(data, transport, transports, plugins, source = nil)
         | 
| @@ -119,7 +112,7 @@ module Bolt | |
| 119 112 |  | 
| 120 113 | 
             
                def self.empty
         | 
| 121 114 | 
             
                  config  = Bolt::Config.default
         | 
| 122 | 
            -
                  plugins = Bolt::Plugin. | 
| 115 | 
            +
                  plugins = Bolt::Plugin.new(config, nil)
         | 
| 123 116 |  | 
| 124 117 | 
             
                  create_version({}, config.transport, config.transports, plugins, nil)
         | 
| 125 118 | 
             
                end
         | 
| @@ -97,20 +97,34 @@ module Bolt | |
| 97 97 | 
             
                      end
         | 
| 98 98 | 
             
                    end
         | 
| 99 99 |  | 
| 100 | 
            -
                     | 
| 101 | 
            -
             | 
| 100 | 
            +
                    versionless_mods = @modules.select { |mod| mod.is_a?(ForgeModule) && mod.version.nil? }
         | 
| 102 101 | 
             
                    command = Bolt::Util.windows? ? 'Install-BoltModule -Force' : 'bolt module install --force'
         | 
| 103 102 |  | 
| 104 | 
            -
                     | 
| 105 | 
            -
                       | 
| 103 | 
            +
                    if unsatisfied_specs.any?
         | 
| 104 | 
            +
                      message = <<~MESSAGE.chomp
         | 
| 105 | 
            +
                        Puppetfile does not include modules that satisfy the following specifications:
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                        #{unsatisfied_specs.map(&:to_hash).to_yaml.lines.drop(1).join.chomp}
         | 
| 108 | 
            +
                        
         | 
| 109 | 
            +
                        This Puppetfile might not be managed by Bolt. To forcibly overwrite the
         | 
| 110 | 
            +
                        Puppetfile, run '#{command}'.
         | 
| 111 | 
            +
                      MESSAGE
         | 
| 106 112 |  | 
| 107 | 
            -
                       | 
| 108 | 
            -
             | 
| 109 | 
            -
                      This Puppetfile might not be managed by Bolt. To forcibly overwrite the
         | 
| 110 | 
            -
                      Puppetfile, run '#{command}'.
         | 
| 111 | 
            -
                    MESSAGE
         | 
| 113 | 
            +
                      raise Bolt::Error.new(message, 'bolt/missing-module-specs')
         | 
| 114 | 
            +
                    end
         | 
| 112 115 |  | 
| 113 | 
            -
                     | 
| 116 | 
            +
                    if versionless_mods.any?
         | 
| 117 | 
            +
                      message = <<~MESSAGE.chomp
         | 
| 118 | 
            +
                        Puppetfile includes Forge modules without a version requirement:
         | 
| 119 | 
            +
                        
         | 
| 120 | 
            +
                        #{versionless_mods.map(&:to_spec).join.chomp}
         | 
| 121 | 
            +
                        
         | 
| 122 | 
            +
                        This Puppetfile might not be managed by Bolt. To forcibly overwrite the
         | 
| 123 | 
            +
                        Puppetfile, run '#{command}'.
         | 
| 124 | 
            +
                      MESSAGE
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                      raise Bolt::Error.new(message, 'bolt/missing-module-version-specs')
         | 
| 127 | 
            +
                    end
         | 
| 114 128 | 
             
                  end
         | 
| 115 129 | 
             
                end
         | 
| 116 130 | 
             
              end
         | 
    
        data/lib/bolt/outputter/human.rb
    CHANGED
    
    | @@ -310,7 +310,12 @@ module Bolt | |
| 310 310 | 
             
                    )
         | 
| 311 311 | 
             
                  end
         | 
| 312 312 |  | 
| 313 | 
            -
                   | 
| 313 | 
            +
                  # List available tasks.
         | 
| 314 | 
            +
                  #
         | 
| 315 | 
            +
                  # @param tasks [Array] A list of task names and descriptions.
         | 
| 316 | 
            +
                  # @param modulepath [Array] The modulepath.
         | 
| 317 | 
            +
                  #
         | 
| 318 | 
            +
                  def print_tasks(tasks:, modulepath:)
         | 
| 314 319 | 
             
                    command = Bolt::Util.powershell? ? 'Get-BoltTask -Name <TASK NAME>' : 'bolt task show <TASK NAME>'
         | 
| 315 320 |  | 
| 316 321 | 
             
                    tasks = tasks.map do |name, description|
         | 
| @@ -330,8 +335,11 @@ module Bolt | |
| 330 335 | 
             
                    @stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific task.")
         | 
| 331 336 | 
             
                  end
         | 
| 332 337 |  | 
| 333 | 
            -
                  #  | 
| 334 | 
            -
                   | 
| 338 | 
            +
                  # Print information about a task.
         | 
| 339 | 
            +
                  #
         | 
| 340 | 
            +
                  # @param task [Bolt::Task] The task information.
         | 
| 341 | 
            +
                  #
         | 
| 342 | 
            +
                  def print_task_info(task:)
         | 
| 335 343 | 
             
                    params = (task.parameters || []).sort
         | 
| 336 344 |  | 
| 337 345 | 
             
                    info = +''
         | 
| @@ -443,7 +451,7 @@ module Bolt | |
| 443 451 | 
             
                    @stream.puts info
         | 
| 444 452 | 
             
                  end
         | 
| 445 453 |  | 
| 446 | 
            -
                  def print_plans(plans | 
| 454 | 
            +
                  def print_plans(plans:, modulepath:)
         | 
| 447 455 | 
             
                    command = Bolt::Util.powershell? ? 'Get-BoltPlan -Name <PLAN NAME>' : 'bolt plan show <PLAN NAME>'
         | 
| 448 456 |  | 
| 449 457 | 
             
                    plans = plans.map do |name, description|
         | 
| @@ -463,7 +471,11 @@ module Bolt | |
| 463 471 | 
             
                    @stream.puts indent(2, "Use '#{command}' to view details and parameters for a specific plan.")
         | 
| 464 472 | 
             
                  end
         | 
| 465 473 |  | 
| 466 | 
            -
                   | 
| 474 | 
            +
                  # Print available guide topics.
         | 
| 475 | 
            +
                  #
         | 
| 476 | 
            +
                  # @param topics [Array] The available topics.
         | 
| 477 | 
            +
                  #
         | 
| 478 | 
            +
                  def print_topics(topics:, **_kwargs)
         | 
| 467 479 | 
             
                    info = +"#{colorize(:cyan, 'Topics')}\n"
         | 
| 468 480 | 
             
                    info << indent(2, topics.join("\n"))
         | 
| 469 481 | 
             
                    info << "\n\n#{colorize(:cyan, 'Additional information')}\n"
         | 
| @@ -471,7 +483,11 @@ module Bolt | |
| 471 483 | 
             
                    @stream.puts info
         | 
| 472 484 | 
             
                  end
         | 
| 473 485 |  | 
| 474 | 
            -
                   | 
| 486 | 
            +
                  # Print the guide for the specified topic.
         | 
| 487 | 
            +
                  #
         | 
| 488 | 
            +
                  # @param guide [String] The guide.
         | 
| 489 | 
            +
                  #
         | 
| 490 | 
            +
                  def print_guide(topic:, guide:, documentation: nil, **_kwargs)
         | 
| 475 491 | 
             
                    info = +"#{colorize(:cyan, topic)}\n"
         | 
| 476 492 | 
             
                    info << indent(2, guide)
         | 
| 477 493 |  | 
| @@ -595,17 +611,17 @@ module Bolt | |
| 595 611 | 
             
                    @stream.puts info
         | 
| 596 612 | 
             
                  end
         | 
| 597 613 |  | 
| 598 | 
            -
                  def print_plugin_list( | 
| 614 | 
            +
                  def print_plugin_list(plugins:, modulepath:)
         | 
| 599 615 | 
             
                    info   = +''
         | 
| 600 | 
            -
                    length =  | 
| 616 | 
            +
                    length = plugins.values.map(&:keys).flatten.map(&:length).max + 4
         | 
| 601 617 |  | 
| 602 | 
            -
                     | 
| 603 | 
            -
                      next if  | 
| 618 | 
            +
                    plugins.each do |hook, plugin|
         | 
| 619 | 
            +
                      next if plugin.empty?
         | 
| 604 620 | 
             
                      next if hook == :validate_resolve_reference
         | 
| 605 621 |  | 
| 606 622 | 
             
                      info << colorize(:cyan, "#{hook}\n")
         | 
| 607 623 |  | 
| 608 | 
            -
                       | 
| 624 | 
            +
                      plugin.each do |name, description|
         | 
| 609 625 | 
             
                        info << indent(2, name.ljust(length))
         | 
| 610 626 | 
             
                        info << truncate(description, 80 - length) if description
         | 
| 611 627 | 
             
                        info << "\n"
         | 
| @@ -623,12 +639,37 @@ module Bolt | |
| 623 639 | 
             
                    @stream.puts info.chomp
         | 
| 624 640 | 
             
                  end
         | 
| 625 641 |  | 
| 626 | 
            -
                  def  | 
| 627 | 
            -
                     | 
| 642 | 
            +
                  def print_new_plan(name:, path:)
         | 
| 643 | 
            +
                    if Bolt::Util.powershell?
         | 
| 644 | 
            +
                      show_command = 'Get-BoltPlan -Name '
         | 
| 645 | 
            +
                      run_command  = 'Invoke-BoltPlan -Name '
         | 
| 646 | 
            +
                    else
         | 
| 647 | 
            +
                      show_command = 'bolt plan show'
         | 
| 648 | 
            +
                      run_command  = 'bolt plan run'
         | 
| 649 | 
            +
                    end
         | 
| 650 | 
            +
             | 
| 651 | 
            +
                    print_message(<<~OUTPUT)
         | 
| 652 | 
            +
                      Created plan '#{name}' at '#{path}'
         | 
| 653 | 
            +
              
         | 
| 654 | 
            +
                      Show this plan with:
         | 
| 655 | 
            +
                          #{show_command} #{name}
         | 
| 656 | 
            +
                      Run this plan with:
         | 
| 657 | 
            +
                          #{run_command} #{name}
         | 
| 658 | 
            +
                    OUTPUT
         | 
| 659 | 
            +
                  end
         | 
| 660 | 
            +
             | 
| 661 | 
            +
                  # Print target names and where they came from.
         | 
| 662 | 
            +
                  #
         | 
| 663 | 
            +
                  # @param adhoc [Hash] Adhoc targets provided on the command line.
         | 
| 664 | 
            +
                  # @param inventory [Hash] Targets provided from the inventory.
         | 
| 665 | 
            +
                  # @param flag [Boolean] Whether a targeting command-line option was used.
         | 
| 666 | 
            +
                  #
         | 
| 667 | 
            +
                  def print_targets(adhoc:, inventory:, flag:, **_kwargs)
         | 
| 668 | 
            +
                    adhoc_text = colorize(:yellow, "(Not found in inventory file)")
         | 
| 628 669 |  | 
| 629 670 | 
             
                    targets  = []
         | 
| 630 | 
            -
                    targets +=  | 
| 631 | 
            -
                    targets +=  | 
| 671 | 
            +
                    targets += inventory[:targets].map { |target| [target['name'], nil] }
         | 
| 672 | 
            +
                    targets += adhoc[:targets].map { |target| [target['name'], adhoc_text] }
         | 
| 632 673 |  | 
| 633 674 | 
             
                    info = +''
         | 
| 634 675 |  | 
| @@ -641,27 +682,31 @@ module Bolt | |
| 641 682 | 
             
                            end
         | 
| 642 683 | 
             
                    info << "\n\n"
         | 
| 643 684 |  | 
| 644 | 
            -
                    info << format_inventory_source( | 
| 645 | 
            -
                    info << format_target_summary( | 
| 685 | 
            +
                    info << format_inventory_source(inventory[:file], inventory[:default])
         | 
| 686 | 
            +
                    info << format_target_summary(inventory[:count], adhoc[:count], flag, false)
         | 
| 646 687 |  | 
| 647 688 | 
             
                    @stream.puts info
         | 
| 648 689 | 
             
                  end
         | 
| 649 690 |  | 
| 650 | 
            -
                   | 
| 651 | 
            -
             | 
| 652 | 
            -
             | 
| 653 | 
            -
             | 
| 691 | 
            +
                  # Print detailed target information.
         | 
| 692 | 
            +
                  #
         | 
| 693 | 
            +
                  # @param adhoc [Hash] Adhoc targets provided on the command line.
         | 
| 694 | 
            +
                  # @param inventory [Hash] Targets provided from the inventory.
         | 
| 695 | 
            +
                  # @param flag [Boolean] Whether a targeting command-line option was used.
         | 
| 696 | 
            +
                  #
         | 
| 697 | 
            +
                  def print_target_info(adhoc:, inventory:, flag:, **_kwargs)
         | 
| 698 | 
            +
                    targets = (adhoc[:targets] + inventory[:targets]).sort_by { |t| t['name'] }
         | 
| 654 699 |  | 
| 655 700 | 
             
                    info = +''
         | 
| 656 701 |  | 
| 657 702 | 
             
                    if targets.any?
         | 
| 658 | 
            -
                       | 
| 703 | 
            +
                      adhoc_text = colorize(:yellow, " (Not found in inventory file)")
         | 
| 659 704 |  | 
| 660 705 | 
             
                      targets.each do |target|
         | 
| 661 | 
            -
                        info << colorize(:cyan, target | 
| 662 | 
            -
                        info <<  | 
| 706 | 
            +
                        info << colorize(:cyan, target['name'])
         | 
| 707 | 
            +
                        info << adhoc_text if adhoc[:targets].include?(target)
         | 
| 663 708 | 
             
                        info << "\n"
         | 
| 664 | 
            -
                        info << indent(2, target. | 
| 709 | 
            +
                        info << indent(2, target.to_yaml.lines.drop(1).join)
         | 
| 665 710 | 
             
                        info << "\n"
         | 
| 666 711 | 
             
                      end
         | 
| 667 712 | 
             
                    else
         | 
| @@ -669,8 +714,8 @@ module Bolt | |
| 669 714 | 
             
                      info << indent(2, "No targets\n\n")
         | 
| 670 715 | 
             
                    end
         | 
| 671 716 |  | 
| 672 | 
            -
                    info << format_inventory_source( | 
| 673 | 
            -
                    info << format_target_summary( | 
| 717 | 
            +
                    info << format_inventory_source(inventory[:file], inventory[:default])
         | 
| 718 | 
            +
                    info << format_target_summary(inventory[:count], adhoc[:count], flag, true)
         | 
| 674 719 |  | 
| 675 720 | 
             
                    @stream.puts info
         | 
| 676 721 | 
             
                  end
         | 
| @@ -716,7 +761,13 @@ module Bolt | |
| 716 761 | 
             
                    info
         | 
| 717 762 | 
             
                  end
         | 
| 718 763 |  | 
| 719 | 
            -
                   | 
| 764 | 
            +
                  # Print inventory group information.
         | 
| 765 | 
            +
                  #
         | 
| 766 | 
            +
                  # @param count [Integer] Number of groups in the inventory.
         | 
| 767 | 
            +
                  # @param groups [Array] Names of groups in the inventory.
         | 
| 768 | 
            +
                  # @param inventory [Hash] Where the inventory was loaded from.
         | 
| 769 | 
            +
                  #
         | 
| 770 | 
            +
                  def print_groups(count:, groups:, inventory:)
         | 
| 720 771 | 
             
                    info = +''
         | 
| 721 772 |  | 
| 722 773 | 
             
                    # Add group list
         | 
| @@ -725,18 +776,18 @@ module Bolt | |
| 725 776 | 
             
                    info << "\n\n"
         | 
| 726 777 |  | 
| 727 778 | 
             
                    # Add inventory file source
         | 
| 728 | 
            -
                    info << format_inventory_source( | 
| 779 | 
            +
                    info << format_inventory_source(inventory[:source], inventory[:default])
         | 
| 729 780 |  | 
| 730 781 | 
             
                    # Add group count summary
         | 
| 731 782 | 
             
                    info << colorize(:cyan, "Group count\n")
         | 
| 732 | 
            -
                    info << indent(2, "#{ | 
| 783 | 
            +
                    info << indent(2, "#{count} total")
         | 
| 733 784 |  | 
| 734 785 | 
             
                    @stream.puts info
         | 
| 735 786 | 
             
                  end
         | 
| 736 787 |  | 
| 737 788 | 
             
                  # @param [Bolt::ResultSet] apply_result A ResultSet object representing the result of a `bolt apply`
         | 
| 738 | 
            -
                  def print_apply_result(apply_result | 
| 739 | 
            -
                    print_summary(apply_result, elapsed_time)
         | 
| 789 | 
            +
                  def print_apply_result(apply_result)
         | 
| 790 | 
            +
                    print_summary(apply_result, apply_result.elapsed_time)
         | 
| 740 791 | 
             
                  end
         | 
| 741 792 |  | 
| 742 793 | 
             
                  # @param [Bolt::PlanResult] plan_result A PlanResult object
         |