bolt 2.37.0 → 2.44.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +17 -17
- data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +6 -8
- data/lib/bolt/analytics.rb +3 -2
- data/lib/bolt/applicator.rb +11 -1
- data/lib/bolt/bolt_option_parser.rb +20 -13
- data/lib/bolt/catalog.rb +10 -29
- data/lib/bolt/cli.rb +58 -40
- data/lib/bolt/config.rb +134 -119
- data/lib/bolt/config/options.rb +142 -77
- data/lib/bolt/config/transport/base.rb +2 -2
- data/lib/bolt/config/transport/local.rb +1 -0
- data/lib/bolt/config/transport/options.rb +18 -68
- data/lib/bolt/config/transport/orch.rb +1 -0
- data/lib/bolt/config/transport/ssh.rb +0 -5
- data/lib/bolt/executor.rb +15 -5
- data/lib/bolt/inventory.rb +26 -0
- data/lib/bolt/inventory/group.rb +35 -12
- data/lib/bolt/inventory/inventory.rb +1 -1
- data/lib/bolt/inventory/options.rb +130 -0
- data/lib/bolt/inventory/target.rb +10 -11
- data/lib/bolt/logger.rb +114 -10
- data/lib/bolt/module.rb +10 -2
- data/lib/bolt/module_installer.rb +25 -15
- data/lib/bolt/module_installer/resolver.rb +65 -12
- data/lib/bolt/module_installer/specs/forge_spec.rb +8 -2
- data/lib/bolt/module_installer/specs/git_spec.rb +17 -2
- data/lib/bolt/outputter.rb +19 -5
- data/lib/bolt/outputter/human.rb +24 -1
- data/lib/bolt/outputter/json.rb +1 -1
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/outputter/rainbow.rb +12 -1
- data/lib/bolt/pal.rb +93 -14
- data/lib/bolt/pal/yaml_plan.rb +8 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +2 -2
- data/lib/bolt/pal/yaml_plan/transpiler.rb +6 -1
- data/lib/bolt/plugin.rb +3 -3
- data/lib/bolt/plugin/cache.rb +8 -8
- data/lib/bolt/plugin/module.rb +1 -1
- data/lib/bolt/plugin/puppet_connect_data.rb +35 -0
- data/lib/bolt/plugin/puppetdb.rb +2 -2
- data/lib/bolt/project.rb +76 -50
- data/lib/bolt/project_manager.rb +2 -0
- data/lib/bolt/project_manager/config_migrator.rb +9 -1
- data/lib/bolt/project_manager/module_migrator.rb +2 -0
- data/lib/bolt/puppetdb/client.rb +8 -0
- data/lib/bolt/rerun.rb +1 -1
- data/lib/bolt/shell/bash.rb +1 -1
- data/lib/bolt/shell/bash/tmpdir.rb +4 -1
- data/lib/bolt/shell/powershell.rb +7 -5
- data/lib/bolt/target.rb +4 -0
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +2 -2
- data/lib/bolt/transport/local.rb +13 -0
- data/lib/bolt/transport/orch/connection.rb +1 -1
- data/lib/bolt/transport/ssh.rb +1 -2
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/validator.rb +227 -0
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/config.rb +1 -1
- data/lib/bolt_server/schemas/partials/task.json +1 -1
- data/lib/bolt_server/transport_app.rb +28 -27
- data/libexec/bolt_catalog +1 -1
- metadata +27 -11
- data/lib/bolt/config/validator.rb +0 -231
data/lib/bolt/module.rb
CHANGED
@@ -6,8 +6,14 @@ module Bolt
|
|
6
6
|
CONTENT_NAME_REGEX = /\A[a-z][a-z0-9_]*(::[a-z][a-z0-9_]*)*\z/.freeze
|
7
7
|
MODULE_NAME_REGEX = /\A[a-z][a-z0-9_]*\z/.freeze
|
8
8
|
|
9
|
-
def self.discover(modulepath)
|
10
|
-
|
9
|
+
def self.discover(modulepath, project)
|
10
|
+
mods = {}
|
11
|
+
|
12
|
+
if project.load_as_module?
|
13
|
+
mods[project.name] = Bolt::Module.new(project.name, project.path.to_s)
|
14
|
+
end
|
15
|
+
|
16
|
+
modulepath.each do |path|
|
11
17
|
next unless File.exist?(path) && File.directory?(path)
|
12
18
|
Dir.children(path)
|
13
19
|
.map { |dir| File.join(path, dir) }
|
@@ -20,6 +26,8 @@ module Bolt
|
|
20
26
|
end
|
21
27
|
end
|
22
28
|
end
|
29
|
+
|
30
|
+
mods
|
23
31
|
end
|
24
32
|
|
25
33
|
attr_reader :name, :path
|
@@ -17,14 +17,14 @@ module Bolt
|
|
17
17
|
|
18
18
|
# Adds a single module to the project.
|
19
19
|
#
|
20
|
-
def add(name, specs, puppetfile_path, moduledir,
|
20
|
+
def add(name, specs, puppetfile_path, moduledir, project_file, config)
|
21
21
|
project_specs = Specs.new(specs)
|
22
22
|
|
23
23
|
# Exit early if project config already includes a spec with this name.
|
24
24
|
if project_specs.include?(name)
|
25
25
|
@outputter.print_message(
|
26
|
-
"Project configuration file #{
|
27
|
-
"#{name}. Nothing to do."
|
26
|
+
"Project configuration file #{project_file} already includes specification "\
|
27
|
+
"with name #{name}. Nothing to do."
|
28
28
|
)
|
29
29
|
return true
|
30
30
|
end
|
@@ -47,30 +47,32 @@ module Bolt
|
|
47
47
|
# a version conflict.
|
48
48
|
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
49
49
|
|
50
|
+
@outputter.start_spin
|
50
51
|
begin
|
51
52
|
resolve_specs.add_specs('name' => name)
|
52
|
-
puppetfile = Resolver.new.resolve(resolve_specs)
|
53
|
+
puppetfile = Resolver.new.resolve(resolve_specs, config)
|
53
54
|
rescue Bolt::Error
|
54
55
|
project_specs.add_specs('name' => name)
|
55
|
-
puppetfile = Resolver.new.resolve(project_specs)
|
56
|
+
puppetfile = Resolver.new.resolve(project_specs, config)
|
56
57
|
end
|
58
|
+
@outputter.stop_spin
|
57
59
|
|
58
60
|
# Display the diff between the existing Puppetfile and the new Puppetfile.
|
59
61
|
print_puppetfile_diff(existing_puppetfile, puppetfile)
|
60
62
|
|
61
63
|
# Add the module to the project configuration.
|
62
|
-
@outputter.print_action_step("Updating project configuration file at #{
|
64
|
+
@outputter.print_action_step("Updating project configuration file at #{project_file}")
|
63
65
|
|
64
|
-
data = Bolt::Util.read_yaml_hash(
|
66
|
+
data = Bolt::Util.read_yaml_hash(project_file, 'project')
|
65
67
|
data['modules'] ||= []
|
66
68
|
data['modules'] << name.tr('-', '/')
|
67
69
|
|
68
70
|
begin
|
69
|
-
File.write(
|
71
|
+
File.write(project_file, data.to_yaml)
|
70
72
|
rescue SystemCallError => e
|
71
73
|
raise Bolt::FileError.new(
|
72
74
|
"Unable to update project configuration file: #{e.message}",
|
73
|
-
|
75
|
+
project_file
|
74
76
|
)
|
75
77
|
end
|
76
78
|
|
@@ -79,7 +81,7 @@ module Bolt
|
|
79
81
|
puppetfile.write(puppetfile_path, moduledir)
|
80
82
|
|
81
83
|
# Install the modules.
|
82
|
-
install_puppetfile(puppetfile_path, moduledir)
|
84
|
+
install_puppetfile(puppetfile_path, moduledir, config)
|
83
85
|
end
|
84
86
|
|
85
87
|
# Outputs a diff of an old Puppetfile and a new Puppetfile.
|
@@ -145,7 +147,7 @@ module Bolt
|
|
145
147
|
|
146
148
|
# Installs a project's module dependencies.
|
147
149
|
#
|
148
|
-
def install(specs, path, moduledir, force: false, resolve: true)
|
150
|
+
def install(specs, path, moduledir, config = {}, force: false, resolve: true)
|
149
151
|
@outputter.print_message("Installing project modules\n\n")
|
150
152
|
|
151
153
|
if resolve != false
|
@@ -155,7 +157,11 @@ module Bolt
|
|
155
157
|
# and write a Puppetfile.
|
156
158
|
if force || !path.exist?
|
157
159
|
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
158
|
-
|
160
|
+
|
161
|
+
# This doesn't use the block as it's more testable to just mock *_spin
|
162
|
+
@outputter.start_spin
|
163
|
+
puppetfile = Resolver.new.resolve(specs, config)
|
164
|
+
@outputter.stop_spin
|
159
165
|
|
160
166
|
# We get here either through 'bolt module install' which uses the
|
161
167
|
# managed modulepath (which isn't configurable) or through bolt
|
@@ -177,18 +183,22 @@ module Bolt
|
|
177
183
|
end
|
178
184
|
|
179
185
|
# Install the modules.
|
180
|
-
install_puppetfile(path, moduledir)
|
186
|
+
install_puppetfile(path, moduledir, config)
|
181
187
|
end
|
182
188
|
|
183
189
|
# Installs the Puppetfile and generates types.
|
184
190
|
#
|
185
191
|
def install_puppetfile(path, moduledir, config = {})
|
186
192
|
@outputter.print_action_step("Syncing modules from #{path} to #{moduledir}")
|
193
|
+
@outputter.start_spin
|
187
194
|
ok = Installer.new(config).install(path, moduledir)
|
195
|
+
@outputter.stop_spin
|
188
196
|
|
189
197
|
# Automatically generate types after installing modules
|
190
|
-
|
191
|
-
|
198
|
+
if ok
|
199
|
+
@outputter.print_action_step("Generating type references")
|
200
|
+
@pal.generate_types(cache: true)
|
201
|
+
end
|
192
202
|
|
193
203
|
@outputter.print_puppetfile_result(ok, path, moduledir)
|
194
204
|
|
@@ -9,14 +9,19 @@ module Bolt
|
|
9
9
|
class Resolver
|
10
10
|
# Resolves module specs and returns a Puppetfile object.
|
11
11
|
#
|
12
|
-
def resolve(specs)
|
12
|
+
def resolve(specs, config = {})
|
13
13
|
require 'puppetfile-resolver'
|
14
14
|
|
15
15
|
# Build the document model from the specs.
|
16
|
-
document
|
16
|
+
document = PuppetfileResolver::Puppetfile::Document.new('')
|
17
|
+
unresolved = []
|
17
18
|
|
18
19
|
specs.specs.each do |spec|
|
19
|
-
|
20
|
+
if spec.resolve
|
21
|
+
document.add_module(spec.to_resolver_module)
|
22
|
+
else
|
23
|
+
unresolved << spec
|
24
|
+
end
|
20
25
|
end
|
21
26
|
|
22
27
|
# Make sure the document model is valid.
|
@@ -38,29 +43,49 @@ module Bolt
|
|
38
43
|
# raised by puppetfile-resolver and re-raising them as Bolt errors.
|
39
44
|
begin
|
40
45
|
result = resolver.resolve(
|
41
|
-
cache:
|
42
|
-
ui:
|
43
|
-
|
44
|
-
|
46
|
+
cache: nil,
|
47
|
+
ui: nil,
|
48
|
+
allow_missing_modules: false,
|
49
|
+
spec_searcher_configuration: spec_searcher_config(config)
|
45
50
|
)
|
46
51
|
rescue StandardError => e
|
47
52
|
raise Bolt::Error.new(e.message, 'bolt/module-resolver-error')
|
48
53
|
end
|
49
54
|
|
50
|
-
#
|
51
|
-
|
55
|
+
# Create the Puppetfile object.
|
56
|
+
generate_puppetfile(specs, result.specifications.values, unresolved)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Creates a puppetfile-resolver config object.
|
60
|
+
#
|
61
|
+
private def spec_searcher_config(config)
|
62
|
+
PuppetfileResolver::SpecSearchers::Configuration.new.tap do |obj|
|
63
|
+
obj.forge.proxy = config.dig('forge', 'proxy') || config.dig('proxy')
|
64
|
+
obj.git.proxy = config.dig('proxy')
|
65
|
+
obj.forge.forge_api = config.dig('forge', 'baseurl')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Creates a Puppetfile object with Module objects created from resolved and
|
70
|
+
# unresolved specs.
|
71
|
+
#
|
72
|
+
private def generate_puppetfile(specs, resolved, unresolved)
|
73
|
+
modules = []
|
74
|
+
|
75
|
+
# Convert the resolved specs into Bolt module objects.
|
76
|
+
resolved.each do |mod|
|
52
77
|
# Skip over anything that isn't a module spec, such as a Puppet spec.
|
53
78
|
next unless mod.is_a? PuppetfileResolver::Models::ModuleSpecification
|
54
79
|
|
55
80
|
case mod.origin
|
56
81
|
when :forge
|
57
|
-
|
82
|
+
modules << Bolt::ModuleInstaller::Puppetfile::ForgeModule.new(
|
58
83
|
"#{mod.owner}/#{mod.name}",
|
59
84
|
mod.version.to_s
|
60
85
|
)
|
61
86
|
when :git
|
62
87
|
spec = specs.specs.find { |s| s.name == mod.name }
|
63
|
-
|
88
|
+
modules << Bolt::ModuleInstaller::Puppetfile::GitModule.new(
|
64
89
|
spec.name,
|
65
90
|
spec.git,
|
66
91
|
spec.sha
|
@@ -68,7 +93,35 @@ module Bolt
|
|
68
93
|
end
|
69
94
|
end
|
70
95
|
|
71
|
-
#
|
96
|
+
# Error if there are any name conflicts between unresolved specs and
|
97
|
+
# resolved modules. r10k will error if a Puppetfile includes duplicate
|
98
|
+
# names, but we error early here to provide a more helpful message.
|
99
|
+
if (name_conflicts = modules.map(&:name) & unresolved.map(&:name)).any?
|
100
|
+
raise Bolt::Error.new(
|
101
|
+
"Detected unresolved module specifications with the same name as a resolved module "\
|
102
|
+
"dependency: #{name_conflicts.join(', ')}. Either remove the unresolved module specification "\
|
103
|
+
"or set the module with the conflicting dependency to not resolve.",
|
104
|
+
"bolt/module-name-conflict-error"
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Convert the unresolved specs into Bolt module objects.
|
109
|
+
unresolved.each do |spec|
|
110
|
+
case spec.type
|
111
|
+
when :forge
|
112
|
+
modules << Bolt::ModuleInstaller::Puppetfile::ForgeModule.new(
|
113
|
+
spec.full_name,
|
114
|
+
spec.version_requirement
|
115
|
+
)
|
116
|
+
when :git
|
117
|
+
modules << Bolt::ModuleInstaller::Puppetfile::GitModule.new(
|
118
|
+
spec.name,
|
119
|
+
spec.git,
|
120
|
+
spec.ref
|
121
|
+
)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
72
125
|
Bolt::ModuleInstaller::Puppetfile.new(modules)
|
73
126
|
end
|
74
127
|
end
|
@@ -13,14 +13,20 @@ module Bolt
|
|
13
13
|
class ForgeSpec
|
14
14
|
NAME_REGEX = %r{\A[a-zA-Z0-9]+[-/](?<name>[a-z][a-z0-9_]*)\z}.freeze
|
15
15
|
REQUIRED_KEYS = Set.new(%w[name]).freeze
|
16
|
-
KNOWN_KEYS = Set.new(%w[name version_requirement]).freeze
|
16
|
+
KNOWN_KEYS = Set.new(%w[name resolve version_requirement]).freeze
|
17
17
|
|
18
|
-
attr_reader :full_name, :name, :semantic_version, :type
|
18
|
+
attr_reader :full_name, :name, :resolve, :semantic_version, :type, :version_requirement
|
19
19
|
|
20
20
|
def initialize(init_hash)
|
21
|
+
@resolve = init_hash.key?('resolve') ? init_hash['resolve'] : true
|
21
22
|
@full_name, @name = parse_name(init_hash['name'])
|
22
23
|
@version_requirement, @semantic_version = parse_version_requirement(init_hash['version_requirement'])
|
23
24
|
@type = :forge
|
25
|
+
|
26
|
+
unless @resolve == true || @resolve == false
|
27
|
+
raise Bolt::ValidationError,
|
28
|
+
"Option 'resolve' for module spec #{@full_name} must be a Boolean"
|
29
|
+
end
|
24
30
|
end
|
25
31
|
|
26
32
|
def self.implements?(hash)
|
@@ -13,18 +13,31 @@ module Bolt
|
|
13
13
|
class GitSpec
|
14
14
|
NAME_REGEX = %r{\A(?:[a-zA-Z0-9]+[-/])?(?<name>[a-z][a-z0-9_]*)\z}.freeze
|
15
15
|
REQUIRED_KEYS = Set.new(%w[git ref]).freeze
|
16
|
+
KNOWN_KEYS = Set.new(%w[git name ref resolve]).freeze
|
16
17
|
|
17
|
-
attr_reader :git, :ref, :type
|
18
|
+
attr_reader :git, :ref, :resolve, :type
|
18
19
|
|
19
20
|
def initialize(init_hash)
|
21
|
+
@resolve = init_hash.key?('resolve') ? init_hash['resolve'] : true
|
20
22
|
@name = parse_name(init_hash['name'])
|
21
23
|
@git, @repo = parse_git(init_hash['git'])
|
22
24
|
@ref = init_hash['ref']
|
23
25
|
@type = :git
|
26
|
+
|
27
|
+
if @name.nil? && @resolve == false
|
28
|
+
raise Bolt::ValidationError,
|
29
|
+
"Missing name for Git module specification: #{@git}. Git module specifications "\
|
30
|
+
"must include a 'name' key when 'resolve' is false."
|
31
|
+
end
|
32
|
+
|
33
|
+
unless @resolve == true || @resolve == false
|
34
|
+
raise Bolt::ValidationError,
|
35
|
+
"Option 'resolve' for module spec #{@git} must be a Boolean"
|
36
|
+
end
|
24
37
|
end
|
25
38
|
|
26
39
|
def self.implements?(hash)
|
27
|
-
|
40
|
+
KNOWN_KEYS.superset?(hash.keys.to_set) && REQUIRED_KEYS.subset?(hash.keys.to_set)
|
28
41
|
end
|
29
42
|
|
30
43
|
# Parses the name into owner and name segments, and formats the full
|
@@ -47,6 +60,8 @@ module Bolt
|
|
47
60
|
# Gets the repo from the git URL.
|
48
61
|
#
|
49
62
|
private def parse_git(git)
|
63
|
+
return [git, nil] unless @resolve
|
64
|
+
|
50
65
|
repo = if git.start_with?('git@github.com:')
|
51
66
|
git.split('git@github.com:').last.split('.git').first
|
52
67
|
elsif git.start_with?('https://github.com')
|
data/lib/bolt/outputter.rb
CHANGED
@@ -2,24 +2,25 @@
|
|
2
2
|
|
3
3
|
module Bolt
|
4
4
|
class Outputter
|
5
|
-
def self.for_format(format, color, verbose, trace)
|
5
|
+
def self.for_format(format, color, verbose, trace, spin)
|
6
6
|
case format
|
7
7
|
when 'human'
|
8
|
-
Bolt::Outputter::Human.new(color, verbose, trace)
|
8
|
+
Bolt::Outputter::Human.new(color, verbose, trace, spin)
|
9
9
|
when 'json'
|
10
|
-
Bolt::Outputter::JSON.new(color, verbose, trace)
|
10
|
+
Bolt::Outputter::JSON.new(color, verbose, trace, false)
|
11
11
|
when 'rainbow'
|
12
|
-
Bolt::Outputter::Rainbow.new(color, verbose, trace)
|
12
|
+
Bolt::Outputter::Rainbow.new(color, verbose, trace, spin)
|
13
13
|
when nil
|
14
14
|
raise "Cannot use outputter before parsing."
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def initialize(color, verbose, trace, stream = $stdout)
|
18
|
+
def initialize(color, verbose, trace, spin, stream = $stdout)
|
19
19
|
@color = color
|
20
20
|
@verbose = verbose
|
21
21
|
@trace = trace
|
22
22
|
@stream = stream
|
23
|
+
@spin = spin
|
23
24
|
end
|
24
25
|
|
25
26
|
def indent(indent, string)
|
@@ -34,6 +35,19 @@ module Bolt
|
|
34
35
|
def print_error
|
35
36
|
raise NotImplementedError, "print_error() must be implemented by the outputter class"
|
36
37
|
end
|
38
|
+
|
39
|
+
def start_spin; end
|
40
|
+
|
41
|
+
def stop_spin; end
|
42
|
+
|
43
|
+
def spin
|
44
|
+
start_spin
|
45
|
+
begin
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
stop_spin
|
49
|
+
end
|
50
|
+
end
|
37
51
|
end
|
38
52
|
end
|
39
53
|
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -14,12 +14,13 @@ module Bolt
|
|
14
14
|
|
15
15
|
def print_head; end
|
16
16
|
|
17
|
-
def initialize(color, verbose, trace, stream = $stdout)
|
17
|
+
def initialize(color, verbose, trace, spin, stream = $stdout)
|
18
18
|
super
|
19
19
|
# Plans and without_default_logging() calls can both be nested, so we
|
20
20
|
# track each of them with a "stack" consisting of an integer.
|
21
21
|
@plan_depth = 0
|
22
22
|
@disable_depth = 0
|
23
|
+
@pinwheel = %w[- \\ | /]
|
23
24
|
end
|
24
25
|
|
25
26
|
def colorize(color, string)
|
@@ -30,6 +31,24 @@ module Bolt
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
34
|
+
def start_spin
|
35
|
+
return unless @spin && @stream.isatty && !@spinning
|
36
|
+
@spinning = true
|
37
|
+
@spin_thread = Thread.new do
|
38
|
+
loop do
|
39
|
+
sleep(0.1)
|
40
|
+
@stream.print(colorize(:cyan, @pinwheel.rotate!.first + "\b"))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop_spin
|
46
|
+
return unless @spin && @stream.isatty && @spinning
|
47
|
+
@spinning = false
|
48
|
+
@spin_thread.terminate
|
49
|
+
@stream.print("\b")
|
50
|
+
end
|
51
|
+
|
33
52
|
def remove_trail(string)
|
34
53
|
string.sub(/\s\z/, '')
|
35
54
|
end
|
@@ -62,6 +81,10 @@ module Bolt
|
|
62
81
|
print_plan_start(event)
|
63
82
|
when :plan_finish
|
64
83
|
print_plan_finish(event)
|
84
|
+
when :start_spin
|
85
|
+
start_spin
|
86
|
+
when :stop_spin
|
87
|
+
stop_spin
|
65
88
|
end
|
66
89
|
end
|
67
90
|
end
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -5,7 +5,7 @@ require 'bolt/pal'
|
|
5
5
|
module Bolt
|
6
6
|
class Outputter
|
7
7
|
class Rainbow < Bolt::Outputter::Human
|
8
|
-
def initialize(color, verbose, trace, stream = $stdout)
|
8
|
+
def initialize(color, verbose, trace, spin, stream = $stdout)
|
9
9
|
begin
|
10
10
|
require 'paint'
|
11
11
|
if Bolt::Util.windows?
|
@@ -62,6 +62,17 @@ module Bolt
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
+
def start_spin
|
66
|
+
return unless @spin && @stream.isatty && !@spinning
|
67
|
+
@spinning = true
|
68
|
+
@spin_thread = Thread.new do
|
69
|
+
loop do
|
70
|
+
sleep(0.1)
|
71
|
+
@stream.print(colorize(:rainbow, @pinwheel.rotate!.first + "\b"))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
65
76
|
def print_summary(results, elapsed_time = nil)
|
66
77
|
ok_set = results.ok_set
|
67
78
|
unless ok_set.empty?
|