bolt 3.4.0 → 3.7.1
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 +2 -2
- data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +26 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +51 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +43 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +29 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +34 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +55 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +66 -0
- data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
- data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
- data/bolt-modules/boltlib/types/planresult.pp +1 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
- data/guides/guide.txt +17 -0
- data/guides/links.txt +13 -0
- data/guides/targets.txt +29 -0
- data/guides/transports.txt +23 -0
- data/lib/bolt/analytics.rb +4 -8
- data/lib/bolt/bolt_option_parser.rb +329 -225
- data/lib/bolt/cli.rb +58 -29
- data/lib/bolt/config.rb +11 -7
- data/lib/bolt/config/options.rb +41 -9
- data/lib/bolt/config/transport/podman.rb +33 -0
- data/lib/bolt/container_result.rb +105 -0
- data/lib/bolt/error.rb +15 -0
- data/lib/bolt/executor.rb +17 -13
- data/lib/bolt/inventory.rb +5 -4
- data/lib/bolt/inventory/inventory.rb +3 -2
- data/lib/bolt/inventory/options.rb +9 -0
- data/lib/bolt/inventory/target.rb +16 -0
- data/lib/bolt/module_installer/specs/git_spec.rb +10 -6
- data/lib/bolt/outputter/human.rb +242 -76
- data/lib/bolt/outputter/json.rb +6 -4
- data/lib/bolt/outputter/logger.rb +17 -0
- data/lib/bolt/pal/yaml_plan/step.rb +4 -2
- data/lib/bolt/plan_creator.rb +2 -2
- data/lib/bolt/plugin.rb +13 -11
- data/lib/bolt/puppetdb/client.rb +54 -0
- data/lib/bolt/result.rb +1 -4
- data/lib/bolt/shell/bash.rb +23 -10
- data/lib/bolt/transport/docker.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +23 -34
- data/lib/bolt/transport/podman.rb +19 -0
- data/lib/bolt/transport/podman/connection.rb +98 -0
- data/lib/bolt/transport/ssh/connection.rb +3 -6
- data/lib/bolt/util.rb +34 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +3 -0
- data/lib/bolt_spec/plans/mock_executor.rb +91 -11
- data/modules/puppet_connect/plans/test_input_data.pp +22 -0
- metadata +13 -2
    
        data/lib/bolt/error.rb
    CHANGED
    
    | @@ -61,6 +61,21 @@ module Bolt | |
| 61 61 | 
             
                end
         | 
| 62 62 | 
             
              end
         | 
| 63 63 |  | 
| 64 | 
            +
              class ContainerFailure < Bolt::Error
         | 
| 65 | 
            +
                attr_reader :result
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def initialize(result)
         | 
| 68 | 
            +
                  details = {
         | 
| 69 | 
            +
                    'value' => result.value,
         | 
| 70 | 
            +
                    'object' => result.object
         | 
| 71 | 
            +
                  }
         | 
| 72 | 
            +
                  message = "Plan aborted: Running container '#{result.object}' failed."
         | 
| 73 | 
            +
                  super(message, 'bolt/container-failure', details)
         | 
| 74 | 
            +
                  @result = result
         | 
| 75 | 
            +
                  @error_code = 2
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
             | 
| 64 79 | 
             
              class RunFailure < Bolt::Error
         | 
| 65 80 | 
             
                attr_reader :result_set
         | 
| 66 81 |  | 
    
        data/lib/bolt/executor.rb
    CHANGED
    
    | @@ -12,34 +12,37 @@ require 'bolt/config' | |
| 12 12 | 
             
            require 'bolt/result_set'
         | 
| 13 13 | 
             
            require 'bolt/puppetdb'
         | 
| 14 14 | 
             
            # Load transports
         | 
| 15 | 
            -
            require 'bolt/transport/ | 
| 16 | 
            -
            require 'bolt/transport/winrm'
         | 
| 17 | 
            -
            require 'bolt/transport/orch'
         | 
| 15 | 
            +
            require 'bolt/transport/docker'
         | 
| 18 16 | 
             
            require 'bolt/transport/local'
         | 
| 19 17 | 
             
            require 'bolt/transport/lxd'
         | 
| 20 | 
            -
            require 'bolt/transport/ | 
| 18 | 
            +
            require 'bolt/transport/orch'
         | 
| 19 | 
            +
            require 'bolt/transport/podman'
         | 
| 21 20 | 
             
            require 'bolt/transport/remote'
         | 
| 21 | 
            +
            require 'bolt/transport/ssh'
         | 
| 22 | 
            +
            require 'bolt/transport/winrm'
         | 
| 22 23 | 
             
            require 'bolt/yarn'
         | 
| 23 24 |  | 
| 24 25 | 
             
            module Bolt
         | 
| 25 26 | 
             
              TRANSPORTS = {
         | 
| 26 | 
            -
                 | 
| 27 | 
            -
                winrm: Bolt::Transport::WinRM,
         | 
| 28 | 
            -
                pcp: Bolt::Transport::Orch,
         | 
| 27 | 
            +
                docker: Bolt::Transport::Docker,
         | 
| 29 28 | 
             
                local: Bolt::Transport::Local,
         | 
| 30 29 | 
             
                lxd: Bolt::Transport::LXD,
         | 
| 31 | 
            -
                 | 
| 32 | 
            -
                 | 
| 30 | 
            +
                pcp: Bolt::Transport::Orch,
         | 
| 31 | 
            +
                podman: Bolt::Transport::Podman,
         | 
| 32 | 
            +
                remote: Bolt::Transport::Remote,
         | 
| 33 | 
            +
                ssh: Bolt::Transport::SSH,
         | 
| 34 | 
            +
                winrm: Bolt::Transport::WinRM
         | 
| 33 35 | 
             
              }.freeze
         | 
| 34 36 |  | 
| 35 37 | 
             
              class Executor
         | 
| 36 | 
            -
                attr_reader :noop, :transports, :in_parallel
         | 
| 38 | 
            +
                attr_reader :noop, :transports, :in_parallel, :future
         | 
| 37 39 | 
             
                attr_accessor :run_as
         | 
| 38 40 |  | 
| 39 41 | 
             
                def initialize(concurrency = 1,
         | 
| 40 42 | 
             
                               analytics = Bolt::Analytics::NoopClient.new,
         | 
| 41 43 | 
             
                               noop = false,
         | 
| 42 | 
            -
                               modified_concurrency = false | 
| 44 | 
            +
                               modified_concurrency = false,
         | 
| 45 | 
            +
                               future = {})
         | 
| 43 46 | 
             
                  # lazy-load expensive gem code
         | 
| 44 47 | 
             
                  require 'concurrent'
         | 
| 45 48 | 
             
                  @analytics = analytics
         | 
| @@ -64,6 +67,7 @@ module Bolt | |
| 64 67 | 
             
                  @noop = noop
         | 
| 65 68 | 
             
                  @run_as = nil
         | 
| 66 69 | 
             
                  @in_parallel = false
         | 
| 70 | 
            +
                  @future = future
         | 
| 67 71 | 
             
                  @pool = if concurrency > 0
         | 
| 68 72 | 
             
                            Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
         | 
| 69 73 | 
             
                          else
         | 
| @@ -217,7 +221,7 @@ module Bolt | |
| 217 221 | 
             
                  results
         | 
| 218 222 | 
             
                end
         | 
| 219 223 |  | 
| 220 | 
            -
                def report_transport(transport, count)
         | 
| 224 | 
            +
                private def report_transport(transport, count)
         | 
| 221 225 | 
             
                  name = transport.class.name.split('::').last.downcase
         | 
| 222 226 | 
             
                  unless @reported_transports.include?(name)
         | 
| 223 227 | 
             
                    @analytics&.event('Transport', 'initialize', label: name, value: count)
         | 
| @@ -475,7 +479,7 @@ module Bolt | |
| 475 479 | 
             
                  Time.now
         | 
| 476 480 | 
             
                end
         | 
| 477 481 |  | 
| 478 | 
            -
                def wait_until(timeout, retry_interval)
         | 
| 482 | 
            +
                private def wait_until(timeout, retry_interval)
         | 
| 479 483 | 
             
                  start = wait_now
         | 
| 480 484 | 
             
                  until yield
         | 
| 481 485 | 
             
                    raise(TimeoutError, 'Timed out waiting for target') if (wait_now - start).to_i >= timeout
         | 
    
        data/lib/bolt/inventory.rb
    CHANGED
    
    | @@ -86,6 +86,7 @@ module Bolt | |
| 86 86 | 
             
                    if config.default_inventoryfile.exist?
         | 
| 87 87 | 
             
                      logger.debug("Loaded inventory from #{config.default_inventoryfile}")
         | 
| 88 88 | 
             
                    else
         | 
| 89 | 
            +
                      source = nil
         | 
| 89 90 | 
             
                      logger.debug("Tried to load inventory from #{config.default_inventoryfile}, but the file does not exist")
         | 
| 90 91 | 
             
                    end
         | 
| 91 92 | 
             
                  end
         | 
| @@ -100,17 +101,17 @@ module Bolt | |
| 100 101 | 
             
                    validator.warnings.each { |warning| Bolt::Logger.warn(warning[:id], warning[:msg]) }
         | 
| 101 102 | 
             
                  end
         | 
| 102 103 |  | 
| 103 | 
            -
                  inventory = create_version(data, config.transport, config.transports, plugins)
         | 
| 104 | 
            +
                  inventory = create_version(data, config.transport, config.transports, plugins, source)
         | 
| 104 105 | 
             
                  inventory.validate
         | 
| 105 106 | 
             
                  inventory
         | 
| 106 107 | 
             
                end
         | 
| 107 108 |  | 
| 108 | 
            -
                def self.create_version(data, transport, transports, plugins)
         | 
| 109 | 
            +
                def self.create_version(data, transport, transports, plugins, source = nil)
         | 
| 109 110 | 
             
                  version = (data || {}).delete('version') { 2 }
         | 
| 110 111 |  | 
| 111 112 | 
             
                  case version
         | 
| 112 113 | 
             
                  when 2
         | 
| 113 | 
            -
                    Bolt::Inventory::Inventory.new(data, transport, transports, plugins)
         | 
| 114 | 
            +
                    Bolt::Inventory::Inventory.new(data, transport, transports, plugins, source)
         | 
| 114 115 | 
             
                  else
         | 
| 115 116 | 
             
                    raise ValidationError.new("Unsupported version #{version} specified in inventory", nil)
         | 
| 116 117 | 
             
                  end
         | 
| @@ -120,7 +121,7 @@ module Bolt | |
| 120 121 | 
             
                  config  = Bolt::Config.default
         | 
| 121 122 | 
             
                  plugins = Bolt::Plugin.setup(config, nil)
         | 
| 122 123 |  | 
| 123 | 
            -
                  create_version({}, config.transport, config.transports, plugins)
         | 
| 124 | 
            +
                  create_version({}, config.transport, config.transports, plugins, nil)
         | 
| 124 125 | 
             
                end
         | 
| 125 126 | 
             
              end
         | 
| 126 127 | 
             
            end
         | 
| @@ -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 :config, :plugins, :source, :targets, :transport
         | 
| 10 10 |  | 
| 11 11 | 
             
                  class WildcardError < Bolt::Error
         | 
| 12 12 | 
             
                    def initialize(target)
         | 
| @@ -15,7 +15,7 @@ module Bolt | |
| 15 15 | 
             
                  end
         | 
| 16 16 |  | 
| 17 17 | 
             
                  # TODO: Pass transport config instead of config object
         | 
| 18 | 
            -
                  def initialize(data, transport, transports, plugins)
         | 
| 18 | 
            +
                  def initialize(data, transport, transports, plugins, source = nil)
         | 
| 19 19 | 
             
                    @logger       = Bolt::Logger.logger(self)
         | 
| 20 20 | 
             
                    @data         = data || {}
         | 
| 21 21 | 
             
                    @transport    = transport
         | 
| @@ -24,6 +24,7 @@ module Bolt | |
| 24 24 | 
             
                    @groups       = Group.new(@data, plugins, all_group: true)
         | 
| 25 25 | 
             
                    @group_lookup = {}
         | 
| 26 26 | 
             
                    @targets      = {}
         | 
| 27 | 
            +
                    @source       = source
         | 
| 27 28 |  | 
| 28 29 | 
             
                    @groups.resolve_string_targets(@groups.target_aliases, @groups.all_targets)
         | 
| 29 30 |  | 
| @@ -11,8 +11,10 @@ module Bolt | |
| 11 11 | 
             
                    facts
         | 
| 12 12 | 
             
                    features
         | 
| 13 13 | 
             
                    groups
         | 
| 14 | 
            +
                    plugin_hooks
         | 
| 14 15 | 
             
                    targets
         | 
| 15 16 | 
             
                    vars
         | 
| 17 | 
            +
                    version
         | 
| 16 18 | 
             
                  ].freeze
         | 
| 17 19 |  | 
| 18 20 | 
             
                  # Definitions used to validate the data.
         | 
| @@ -123,6 +125,13 @@ module Bolt | |
| 123 125 | 
             
                      description: "A map of variables for the group or target.",
         | 
| 124 126 | 
             
                      type: Hash,
         | 
| 125 127 | 
             
                      _plugin: true
         | 
| 128 | 
            +
                    },
         | 
| 129 | 
            +
                    "version" => {
         | 
| 130 | 
            +
                      description: "The version of the inventory file.",
         | 
| 131 | 
            +
                      type: Integer,
         | 
| 132 | 
            +
                      _plugin: false,
         | 
| 133 | 
            +
                      _example: 2,
         | 
| 134 | 
            +
                      _default: 2
         | 
| 126 135 | 
             
                    }
         | 
| 127 136 | 
             
                  }.freeze
         | 
| 128 137 | 
             
                end
         | 
| @@ -92,6 +92,7 @@ module Bolt | |
| 92 92 | 
             
                  end
         | 
| 93 93 |  | 
| 94 94 | 
             
                  def add_facts(new_facts = {})
         | 
| 95 | 
            +
                    validate_fact_names(new_facts)
         | 
| 95 96 | 
             
                    @facts = Bolt::Util.deep_merge(@facts, new_facts)
         | 
| 96 97 | 
             
                  end
         | 
| 97 98 |  | 
| @@ -153,9 +154,24 @@ module Bolt | |
| 153 154 | 
             
                      raise Bolt::UnknownTransportError.new(transport, uri)
         | 
| 154 155 | 
             
                    end
         | 
| 155 156 |  | 
| 157 | 
            +
                    validate_fact_names(facts)
         | 
| 158 | 
            +
             | 
| 156 159 | 
             
                    transport_config
         | 
| 157 160 | 
             
                  end
         | 
| 158 161 |  | 
| 162 | 
            +
                  # Validate fact names and issue a deprecation warning if any fact names have a dot.
         | 
| 163 | 
            +
                  #
         | 
| 164 | 
            +
                  private def validate_fact_names(facts)
         | 
| 165 | 
            +
                    if (dotted = facts.keys.select { |name| name.include?('.') }).any?
         | 
| 166 | 
            +
                      Bolt::Logger.deprecate(
         | 
| 167 | 
            +
                        'dotted_fact_name',
         | 
| 168 | 
            +
                        "Target '#{safe_name}' includes dotted fact names: '#{dotted.join("', '")}'. Dotted fact "\
         | 
| 169 | 
            +
                        "names are deprecated and Bolt does not automatically convert facts with dotted names to "\
         | 
| 170 | 
            +
                        "structured facts. For more information, see https://pup.pt/bolt-dotted-facts"
         | 
| 171 | 
            +
                      )
         | 
| 172 | 
            +
                    end
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
             | 
| 159 175 | 
             
                  def host
         | 
| 160 176 | 
             
                    @uri_obj.hostname || transport_config['host']
         | 
| 161 177 | 
             
                  end
         | 
| @@ -67,8 +67,7 @@ module Bolt | |
| 67 67 | 
             
                             elsif git.start_with?('https://github.com')
         | 
| 68 68 | 
             
                               git.split('https://github.com/').last.split('.git').first
         | 
| 69 69 | 
             
                             else
         | 
| 70 | 
            -
                               raise Bolt::ValidationError,
         | 
| 71 | 
            -
                                     "Invalid git source: #{git}. Only GitHub modules are supported."
         | 
| 70 | 
            +
                               raise Bolt::ValidationError, invalid_git_msg(git)
         | 
| 72 71 | 
             
                             end
         | 
| 73 72 |  | 
| 74 73 | 
             
                      [git, repo]
         | 
| @@ -89,6 +88,14 @@ module Bolt | |
| 89 88 | 
             
                      }
         | 
| 90 89 | 
             
                    end
         | 
| 91 90 |  | 
| 91 | 
            +
                    # Returns an error message that the provided repo is not a git repo or
         | 
| 92 | 
            +
                    # is private.
         | 
| 93 | 
            +
                    #
         | 
| 94 | 
            +
                    private def invalid_git_msg(repo_name)
         | 
| 95 | 
            +
                      "#{repo_name} is not a public GitHub repository. See https://pup.pt/no-resolve "\
         | 
| 96 | 
            +
                        "for information on how to install this module."
         | 
| 97 | 
            +
                    end
         | 
| 98 | 
            +
             | 
| 92 99 | 
             
                    # Returns a PuppetfileResolver::Model::GitModule object for resolving.
         | 
| 93 100 | 
             
                    #
         | 
| 94 101 | 
             
                    def to_resolver_module
         | 
| @@ -157,10 +164,7 @@ module Bolt | |
| 157 164 |  | 
| 158 165 | 
             
                          raise Bolt::Error.new(message, 'bolt/github-api-rate-limit-error')
         | 
| 159 166 | 
             
                        when Net::HTTPNotFound
         | 
| 160 | 
            -
                          raise Bolt::Error.new(
         | 
| 161 | 
            -
                            "#{git} is not a git repository.",
         | 
| 162 | 
            -
                            "bolt/missing-git-repository-error"
         | 
| 163 | 
            -
                          )
         | 
| 167 | 
            +
                          raise Bolt::Error.new(invalid_git_msg(git), "bolt/missing-git-repository-error")
         | 
| 164 168 | 
             
                        else
         | 
| 165 169 | 
             
                          raise Bolt::Error.new(
         | 
| 166 170 | 
             
                            "Ref #{ref} at #{git} is not a commit, tag, or branch.",
         | 
    
        data/lib/bolt/outputter/human.rb
    CHANGED
    
    | @@ -6,6 +6,7 @@ module Bolt | |
| 6 6 | 
             
              class Outputter
         | 
| 7 7 | 
             
                class Human < Bolt::Outputter
         | 
| 8 8 | 
             
                  COLORS = {
         | 
| 9 | 
            +
                    dim:    "2", # Dim, the other color of the rainbow
         | 
| 9 10 | 
             
                    red:    "31",
         | 
| 10 11 | 
             
                    green:  "32",
         | 
| 11 12 | 
             
                    yellow: "33",
         | 
| @@ -92,6 +93,10 @@ module Bolt | |
| 92 93 | 
             
                        print_plan_start(event)
         | 
| 93 94 | 
             
                      when :plan_finish
         | 
| 94 95 | 
             
                        print_plan_finish(event)
         | 
| 96 | 
            +
                      when :container_start
         | 
| 97 | 
            +
                        print_container_start(event) if plan_logging?
         | 
| 98 | 
            +
                      when :container_finish
         | 
| 99 | 
            +
                        print_container_finish(event) if plan_logging?
         | 
| 95 100 | 
             
                      when :start_spin
         | 
| 96 101 | 
             
                        start_spin
         | 
| 97 102 | 
             
                      when :stop_spin
         | 
| @@ -112,6 +117,34 @@ module Bolt | |
| 112 117 | 
             
                    @stream.puts(colorize(:green, "Started on #{target.safe_name}..."))
         | 
| 113 118 | 
             
                  end
         | 
| 114 119 |  | 
| 120 | 
            +
                  def print_container_result(result)
         | 
| 121 | 
            +
                    if result.success?
         | 
| 122 | 
            +
                      @stream.puts(colorize(:green, "Finished running container #{result.object}:"))
         | 
| 123 | 
            +
                    else
         | 
| 124 | 
            +
                      @stream.puts(colorize(:red, "Failed running container #{result.object}:"))
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                    if result.error_hash
         | 
| 128 | 
            +
                      @stream.puts(colorize(:red, remove_trail(indent(2, result.error_hash['msg']))))
         | 
| 129 | 
            +
                      return 0
         | 
| 130 | 
            +
                    end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    # Only print results if there's something other than empty string and hash
         | 
| 133 | 
            +
                    safe_value = result.safe_value
         | 
| 134 | 
            +
                    if safe_value['stdout'].strip.empty? && safe_value['stderr'].strip.empty?
         | 
| 135 | 
            +
                      @stream.puts(indent(2, "Running container #{result.object} completed successfully with no result"))
         | 
| 136 | 
            +
                    else
         | 
| 137 | 
            +
                      unless safe_value['stdout'].strip && safe_value['stdout'].strip.empty?
         | 
| 138 | 
            +
                        @stream.puts(indent(2, "STDOUT:"))
         | 
| 139 | 
            +
                        @stream.puts(indent(4, safe_value['stdout']))
         | 
| 140 | 
            +
                      end
         | 
| 141 | 
            +
                      unless safe_value['stderr'].strip.empty?
         | 
| 142 | 
            +
                        @stream.puts(indent(2, "STDERR:"))
         | 
| 143 | 
            +
                        @stream.puts(indent(4, safe_value['stderr']))
         | 
| 144 | 
            +
                      end
         | 
| 145 | 
            +
                    end
         | 
| 146 | 
            +
                  end
         | 
| 147 | 
            +
             | 
| 115 148 | 
             
                  def print_result(result)
         | 
| 116 149 | 
             
                    if result.success?
         | 
| 117 150 | 
             
                      @stream.puts(colorize(:green, "Finished on #{result.target.safe_name}:"))
         | 
| @@ -180,6 +213,25 @@ module Bolt | |
| 180 213 | 
             
                    @stream.puts(colorize(:green, message))
         | 
| 181 214 | 
             
                  end
         | 
| 182 215 |  | 
| 216 | 
            +
                  def print_container_start(image:, **_kwargs)
         | 
| 217 | 
            +
                    @stream.puts(colorize(:green, "Starting: run container '#{image}'"))
         | 
| 218 | 
            +
                  end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                  def print_container_finish(event)
         | 
| 221 | 
            +
                    result = if event[:result].is_a?(Bolt::ContainerFailure)
         | 
| 222 | 
            +
                               event[:result].result
         | 
| 223 | 
            +
                             else
         | 
| 224 | 
            +
                               event[:result]
         | 
| 225 | 
            +
                             end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                    if result.success?
         | 
| 228 | 
            +
                      @stream.puts(colorize(:green, "Finished: run container '#{result.object}' succeeded."))
         | 
| 229 | 
            +
                    else
         | 
| 230 | 
            +
                      @stream.puts(colorize(:red, "Finished: run container '#{result.object}' failed."))
         | 
| 231 | 
            +
                    end
         | 
| 232 | 
            +
                    print_container_result(result) if @verbose
         | 
| 233 | 
            +
                  end
         | 
| 234 | 
            +
             | 
| 183 235 | 
             
                  def print_plan_start(event)
         | 
| 184 236 | 
             
                    @plan_depth += 1
         | 
| 185 237 | 
             
                    # We use this event to both mark the start of a plan _and_ to enable
         | 
| @@ -241,7 +293,7 @@ module Bolt | |
| 241 293 | 
             
                  end
         | 
| 242 294 |  | 
| 243 295 | 
             
                  def print_tasks(tasks, modulepath)
         | 
| 244 | 
            -
                    command = Bolt::Util.powershell? ? 'Get-BoltTask - | 
| 296 | 
            +
                    command = Bolt::Util.powershell? ? 'Get-BoltTask -Name <TASK NAME>' : 'bolt task show <TASK NAME>'
         | 
| 245 297 |  | 
| 246 298 | 
             
                    tasks = tasks.map do |name, description|
         | 
| 247 299 | 
             
                      description = truncate(description, 72)
         | 
| @@ -262,78 +314,115 @@ module Bolt | |
| 262 314 |  | 
| 263 315 | 
             
                  # @param [Hash] task A hash representing the task
         | 
| 264 316 | 
             
                  def print_task_info(task)
         | 
| 265 | 
            -
                     | 
| 266 | 
            -
             | 
| 267 | 
            -
                     | 
| 268 | 
            -
             | 
| 269 | 
            -
             | 
| 317 | 
            +
                    params = (task.parameters || []).sort
         | 
| 318 | 
            +
             | 
| 319 | 
            +
                    info = +''
         | 
| 320 | 
            +
             | 
| 321 | 
            +
                    # Add task name and description
         | 
| 322 | 
            +
                    info << colorize(:cyan, "#{task.name}\n")
         | 
| 323 | 
            +
                    info << if task.description
         | 
| 324 | 
            +
                              indent(2, task.description.chomp)
         | 
| 270 325 | 
             
                            else
         | 
| 271 | 
            -
                               | 
| 326 | 
            +
                              indent(2, 'No description')
         | 
| 272 327 | 
             
                            end
         | 
| 273 | 
            -
             | 
| 274 | 
            -
             | 
| 275 | 
            -
             | 
| 276 | 
            -
             | 
| 277 | 
            -
             | 
| 278 | 
            -
             | 
| 279 | 
            -
             | 
| 328 | 
            +
                    info << "\n\n"
         | 
| 329 | 
            +
             | 
| 330 | 
            +
                    # Build usage string
         | 
| 331 | 
            +
                    usage = +''
         | 
| 332 | 
            +
                    usage << if Bolt::Util.powershell?
         | 
| 333 | 
            +
                               "Invoke-BoltTask -Name #{task.name} -Targets <targets>"
         | 
| 334 | 
            +
                             else
         | 
| 335 | 
            +
                               "bolt task run #{task.name} --targets <targets>"
         | 
| 336 | 
            +
                             end
         | 
| 337 | 
            +
                    usage << (Bolt::Util.powershell? ? ' [-Noop]' : ' [--noop]') if task.supports_noop
         | 
| 338 | 
            +
                    params.each do |name, data|
         | 
| 339 | 
            +
                      usage << if data['type']&.start_with?('Optional')
         | 
| 340 | 
            +
                                 " [#{name}=<value>]"
         | 
| 280 341 | 
             
                               else
         | 
| 281 | 
            -
                                 " #{ | 
| 342 | 
            +
                                 " #{name}=<value>"
         | 
| 282 343 | 
             
                               end
         | 
| 283 344 | 
             
                    end
         | 
| 284 345 |  | 
| 285 | 
            -
                     | 
| 286 | 
            -
             | 
| 346 | 
            +
                    # Add usage
         | 
| 347 | 
            +
                    info << colorize(:cyan, "Usage\n")
         | 
| 348 | 
            +
                    info << indent(2, wrap(usage))
         | 
| 349 | 
            +
                    info << "\n"
         | 
| 350 | 
            +
             | 
| 351 | 
            +
                    # Add parameters, if any
         | 
| 352 | 
            +
                    if params.any?
         | 
| 353 | 
            +
                      info << colorize(:cyan, "Parameters\n")
         | 
| 354 | 
            +
                      params.each do |name, data|
         | 
| 355 | 
            +
                        info << indent(2, "#{colorize(:yellow, name)}  #{colorize(:dim, data['type'] || 'Any')}\n")
         | 
| 356 | 
            +
                        info << indent(4, "#{wrap(data['description']).chomp}\n") if data['description']
         | 
| 357 | 
            +
                        info << indent(4, "Default: #{data['default'].inspect}\n") if data.key?('default')
         | 
| 358 | 
            +
                        info << "\n"
         | 
| 359 | 
            +
                      end
         | 
| 287 360 | 
             
                    end
         | 
| 288 361 |  | 
| 289 | 
            -
                     | 
| 290 | 
            -
                    task_info << " - #{task.description}" if task.description
         | 
| 291 | 
            -
                    task_info << "\n\n"
         | 
| 292 | 
            -
                    task_info << "USAGE:\n#{usage}\n\n"
         | 
| 293 | 
            -
                    task_info << "PARAMETERS:\n#{pretty_params}\n" unless pretty_params.empty?
         | 
| 294 | 
            -
                    task_info << "MODULE:\n"
         | 
| 295 | 
            -
             | 
| 362 | 
            +
                    # Add module location
         | 
| 296 363 | 
             
                    path = task.files.first['path'].chomp("/tasks/#{task.files.first['name']}")
         | 
| 297 | 
            -
                     | 
| 298 | 
            -
             | 
| 299 | 
            -
             | 
| 300 | 
            -
             | 
| 301 | 
            -
             | 
| 302 | 
            -
             | 
| 364 | 
            +
                    info << colorize(:cyan, "Module\n")
         | 
| 365 | 
            +
                    info << if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH)
         | 
| 366 | 
            +
                              indent(2, 'built-in module')
         | 
| 367 | 
            +
                            else
         | 
| 368 | 
            +
                              indent(2, path)
         | 
| 369 | 
            +
                            end
         | 
| 370 | 
            +
             | 
| 371 | 
            +
                    @stream.puts info
         | 
| 303 372 | 
             
                  end
         | 
| 304 373 |  | 
| 305 374 | 
             
                  # @param [Hash] plan A hash representing the plan
         | 
| 306 375 | 
             
                  def print_plan_info(plan)
         | 
| 307 | 
            -
                     | 
| 308 | 
            -
             | 
| 309 | 
            -
                     | 
| 310 | 
            -
             | 
| 311 | 
            -
             | 
| 376 | 
            +
                    params = plan['parameters'].sort
         | 
| 377 | 
            +
             | 
| 378 | 
            +
                    info = +''
         | 
| 379 | 
            +
             | 
| 380 | 
            +
                    # Add plan name and description
         | 
| 381 | 
            +
                    info << colorize(:cyan, "#{plan['name']}\n")
         | 
| 382 | 
            +
                    info << if plan['description']
         | 
| 383 | 
            +
                              indent(2, plan['description'].chomp)
         | 
| 312 384 | 
             
                            else
         | 
| 313 | 
            -
                               | 
| 385 | 
            +
                              indent(2, 'No description')
         | 
| 314 386 | 
             
                            end
         | 
| 387 | 
            +
                    info << "\n\n"
         | 
| 388 | 
            +
             | 
| 389 | 
            +
                    # Build the usage string
         | 
| 390 | 
            +
                    usage = +''
         | 
| 391 | 
            +
                    usage << if Bolt::Util.powershell?
         | 
| 392 | 
            +
                               "Invoke-BoltPlan -Name #{plan['name']}"
         | 
| 393 | 
            +
                             else
         | 
| 394 | 
            +
                               "bolt plan run #{plan['name']}"
         | 
| 395 | 
            +
                             end
         | 
| 396 | 
            +
                    params.each do |name, data|
         | 
| 397 | 
            +
                      usage << (data.include?('default_value') ? " [#{name}=<value>]" : " #{name}=<value>")
         | 
| 398 | 
            +
                    end
         | 
| 399 | 
            +
             | 
| 400 | 
            +
                    # Add usage
         | 
| 401 | 
            +
                    info << colorize(:cyan, "Usage\n")
         | 
| 402 | 
            +
                    info << indent(2, wrap(usage))
         | 
| 403 | 
            +
                    info << "\n"
         | 
| 315 404 |  | 
| 316 | 
            -
                     | 
| 317 | 
            -
             | 
| 318 | 
            -
                       | 
| 319 | 
            -
             | 
| 320 | 
            -
                       | 
| 405 | 
            +
                    # Add parameters, if any
         | 
| 406 | 
            +
                    if params.any?
         | 
| 407 | 
            +
                      info << colorize(:cyan, "Parameters\n")
         | 
| 408 | 
            +
             | 
| 409 | 
            +
                      params.each do |name, data|
         | 
| 410 | 
            +
                        info << indent(2, "#{colorize(:yellow, name)}  #{colorize(:dim, data['type'])}\n")
         | 
| 411 | 
            +
                        info << indent(4, "#{wrap(data['description']).chomp}\n") if data['description']
         | 
| 412 | 
            +
                        info << indent(4, "Default: #{data['default_value']}\n") unless data['default_value'].nil?
         | 
| 413 | 
            +
                        info << "\n"
         | 
| 414 | 
            +
                      end
         | 
| 321 415 | 
             
                    end
         | 
| 322 416 |  | 
| 323 | 
            -
                     | 
| 324 | 
            -
                     | 
| 325 | 
            -
                     | 
| 326 | 
            -
             | 
| 327 | 
            -
             | 
| 328 | 
            -
             | 
| 417 | 
            +
                    # Add module location
         | 
| 418 | 
            +
                    info << colorize(:cyan, "Module\n")
         | 
| 419 | 
            +
                    info << if plan['module'].start_with?(Bolt::Config::Modulepath::MODULES_PATH)
         | 
| 420 | 
            +
                              indent(2, 'built-in module')
         | 
| 421 | 
            +
                            else
         | 
| 422 | 
            +
                              indent(2, plan['module'])
         | 
| 423 | 
            +
                            end
         | 
| 329 424 |  | 
| 330 | 
            -
                     | 
| 331 | 
            -
                    plan_info << if path.start_with?(Bolt::Config::Modulepath::MODULES_PATH)
         | 
| 332 | 
            -
                                   "built-in module"
         | 
| 333 | 
            -
                                 else
         | 
| 334 | 
            -
                                   path
         | 
| 335 | 
            -
                                 end
         | 
| 336 | 
            -
                    @stream.puts(plan_info)
         | 
| 425 | 
            +
                    @stream.puts info
         | 
| 337 426 | 
             
                  end
         | 
| 338 427 |  | 
| 339 428 | 
             
                  def print_plans(plans, modulepath)
         | 
| @@ -394,42 +483,115 @@ module Bolt | |
| 394 483 | 
             
                    end
         | 
| 395 484 | 
             
                  end
         | 
| 396 485 |  | 
| 397 | 
            -
                  def print_targets(target_list,  | 
| 486 | 
            +
                  def print_targets(target_list, inventory_source, default_inventory, target_flag)
         | 
| 398 487 | 
             
                    adhoc = colorize(:yellow, "(Not found in inventory file)")
         | 
| 399 488 |  | 
| 400 489 | 
             
                    targets  = []
         | 
| 401 490 | 
             
                    targets += target_list[:inventory].map { |target| [target.name, nil] }
         | 
| 402 491 | 
             
                    targets += target_list[:adhoc].map { |target| [target.name, adhoc] }
         | 
| 403 492 |  | 
| 404 | 
            -
                     | 
| 405 | 
            -
                      @stream.puts format_table(targets, 0, 2)
         | 
| 406 | 
            -
                      @stream.puts
         | 
| 407 | 
            -
                    end
         | 
| 493 | 
            +
                    info = +''
         | 
| 408 494 |  | 
| 409 | 
            -
                     | 
| 410 | 
            -
                     | 
| 411 | 
            -
             | 
| 495 | 
            +
                    # Add target list
         | 
| 496 | 
            +
                    info << colorize(:cyan, "Targets\n")
         | 
| 497 | 
            +
                    info << if targets.any?
         | 
| 498 | 
            +
                              format_table(targets, 2, 2).to_s
         | 
| 499 | 
            +
                            else
         | 
| 500 | 
            +
                              indent(2, 'No targets')
         | 
| 501 | 
            +
                            end
         | 
| 502 | 
            +
                    info << "\n\n"
         | 
| 503 | 
            +
             | 
| 504 | 
            +
                    info << format_inventory_source(inventory_source, default_inventory)
         | 
| 505 | 
            +
                    info << format_target_summary(target_list[:inventory].count, target_list[:adhoc].count, target_flag, false)
         | 
| 506 | 
            +
             | 
| 507 | 
            +
                    @stream.puts info
         | 
| 508 | 
            +
                  end
         | 
| 509 | 
            +
             | 
| 510 | 
            +
                  def print_target_info(target_list, inventory_source, default_inventory, target_flag)
         | 
| 511 | 
            +
                    adhoc_targets     = target_list[:adhoc].map(&:name).to_set
         | 
| 512 | 
            +
                    inventory_targets = target_list[:inventory].map(&:name).to_set
         | 
| 513 | 
            +
                    targets           = target_list.values.flatten.sort_by(&:name)
         | 
| 514 | 
            +
             | 
| 515 | 
            +
                    info = +''
         | 
| 516 | 
            +
             | 
| 517 | 
            +
                    if targets.any?
         | 
| 518 | 
            +
                      adhoc = colorize(:yellow, " (Not found in inventory file)")
         | 
| 519 | 
            +
             | 
| 520 | 
            +
                      targets.each do |target|
         | 
| 521 | 
            +
                        info << colorize(:cyan, target.name)
         | 
| 522 | 
            +
                        info << adhoc if adhoc_targets.include?(target.name)
         | 
| 523 | 
            +
                        info << "\n"
         | 
| 524 | 
            +
                        info << indent(2, target.detail.to_yaml.lines.drop(1).join)
         | 
| 525 | 
            +
                        info << "\n"
         | 
| 526 | 
            +
                      end
         | 
| 412 527 | 
             
                    else
         | 
| 413 | 
            -
                       | 
| 528 | 
            +
                      info << colorize(:cyan, "Targets\n")
         | 
| 529 | 
            +
                      info << indent(2, "No targets\n\n")
         | 
| 414 530 | 
             
                    end
         | 
| 415 531 |  | 
| 416 | 
            -
                     | 
| 417 | 
            -
                     | 
| 418 | 
            -
             | 
| 532 | 
            +
                    info << format_inventory_source(inventory_source, default_inventory)
         | 
| 533 | 
            +
                    info << format_target_summary(inventory_targets.count, adhoc_targets.count, target_flag, true)
         | 
| 534 | 
            +
             | 
| 535 | 
            +
                    @stream.puts info
         | 
| 419 536 | 
             
                  end
         | 
| 420 537 |  | 
| 421 | 
            -
                  def  | 
| 422 | 
            -
                     | 
| 423 | 
            -
             | 
| 424 | 
            -
                     | 
| 425 | 
            -
                     | 
| 426 | 
            -
                     | 
| 538 | 
            +
                  private def format_inventory_source(inventory_source, default_inventory)
         | 
| 539 | 
            +
                    info = +''
         | 
| 540 | 
            +
             | 
| 541 | 
            +
                    # Add inventory file source
         | 
| 542 | 
            +
                    info << colorize(:cyan, "Inventory source\n")
         | 
| 543 | 
            +
                    info << if inventory_source
         | 
| 544 | 
            +
                              indent(2, "#{inventory_source}\n")
         | 
| 545 | 
            +
                            else
         | 
| 546 | 
            +
                              indent(2, wrap("Tried to load inventory from #{default_inventory}, but the file does not exist\n"))
         | 
| 547 | 
            +
                            end
         | 
| 548 | 
            +
                    info << "\n"
         | 
| 549 | 
            +
                  end
         | 
| 550 | 
            +
             | 
| 551 | 
            +
                  private def format_target_summary(inventory_count, adhoc_count, target_flag, detail_flag)
         | 
| 552 | 
            +
                    info = +''
         | 
| 553 | 
            +
             | 
| 554 | 
            +
                    # Add target count summary
         | 
| 555 | 
            +
                    count = "#{inventory_count + adhoc_count} total, "\
         | 
| 556 | 
            +
                            "#{inventory_count} from inventory, "\
         | 
| 557 | 
            +
                            "#{adhoc_count} adhoc"
         | 
| 558 | 
            +
                    info << colorize(:cyan, "Target count\n")
         | 
| 559 | 
            +
                    info << indent(2, count)
         | 
| 560 | 
            +
             | 
| 561 | 
            +
                    # Add filtering information
         | 
| 562 | 
            +
                    unless target_flag && detail_flag
         | 
| 563 | 
            +
                      info << colorize(:cyan, "\n\nAdditional information\n")
         | 
| 564 | 
            +
             | 
| 565 | 
            +
                      unless target_flag
         | 
| 566 | 
            +
                        opt = Bolt::Util.windows? ? "'-Targets', '-Query', or '-Rerun'" : "'--targets', '--query', or '--rerun'"
         | 
| 567 | 
            +
                        info << indent(2, "Use the #{opt} option to view specific targets\n")
         | 
| 568 | 
            +
                      end
         | 
| 569 | 
            +
             | 
| 570 | 
            +
                      unless detail_flag
         | 
| 571 | 
            +
                        opt = Bolt::Util.windows? ? '-Detail' : '--detail'
         | 
| 572 | 
            +
                        info << indent(2, "Use the '#{opt}' option to view target configuration and data")
         | 
| 573 | 
            +
                      end
         | 
| 574 | 
            +
                    end
         | 
| 575 | 
            +
             | 
| 576 | 
            +
                    info
         | 
| 427 577 | 
             
                  end
         | 
| 428 578 |  | 
| 429 | 
            -
                  def print_groups(groups)
         | 
| 430 | 
            -
                     | 
| 431 | 
            -
             | 
| 432 | 
            -
                     | 
| 579 | 
            +
                  def print_groups(groups, inventory_source, default_inventory)
         | 
| 580 | 
            +
                    info = +''
         | 
| 581 | 
            +
             | 
| 582 | 
            +
                    # Add group list
         | 
| 583 | 
            +
                    info << colorize(:cyan, "Groups\n")
         | 
| 584 | 
            +
                    info << indent(2, groups.join("\n"))
         | 
| 585 | 
            +
                    info << "\n\n"
         | 
| 586 | 
            +
             | 
| 587 | 
            +
                    # Add inventory file source
         | 
| 588 | 
            +
                    info << format_inventory_source(inventory_source, default_inventory)
         | 
| 589 | 
            +
             | 
| 590 | 
            +
                    # Add group count summary
         | 
| 591 | 
            +
                    info << colorize(:cyan, "Group count\n")
         | 
| 592 | 
            +
                    info << indent(2, "#{groups.count} total")
         | 
| 593 | 
            +
             | 
| 594 | 
            +
                    @stream.puts info
         | 
| 433 595 | 
             
                  end
         | 
| 434 596 |  | 
| 435 597 | 
             
                  # @param [Bolt::ResultSet] apply_result A ResultSet object representing the result of a `bolt apply`
         | 
| @@ -445,6 +607,10 @@ module Bolt | |
| 445 607 | 
             
                      @stream.puts("Plan completed successfully with no result")
         | 
| 446 608 | 
             
                    when Bolt::ApplyFailure, Bolt::RunFailure
         | 
| 447 609 | 
             
                      print_result_set(value.result_set)
         | 
| 610 | 
            +
                    when Bolt::ContainerResult
         | 
| 611 | 
            +
                      print_container_result(value)
         | 
| 612 | 
            +
                    when Bolt::ContainerFailure
         | 
| 613 | 
            +
                      print_container_result(value.result)
         | 
| 448 614 | 
             
                    when Bolt::ResultSet
         | 
| 449 615 | 
             
                      print_result_set(value)
         | 
| 450 616 | 
             
                    else
         |