bolt 2.27.0 → 2.32.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 +13 -12
- data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +2 -2
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +44 -1
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +3 -0
- data/guides/module.txt +19 -0
- data/guides/modulepath.txt +25 -0
- data/lib/bolt/applicator.rb +14 -14
- data/lib/bolt/bolt_option_parser.rb +74 -22
- data/lib/bolt/catalog.rb +1 -1
- data/lib/bolt/cli.rb +178 -127
- data/lib/bolt/config.rb +13 -1
- data/lib/bolt/config/modulepath.rb +30 -0
- data/lib/bolt/config/options.rb +38 -9
- data/lib/bolt/config/transport/options.rb +1 -1
- data/lib/bolt/executor.rb +1 -1
- data/lib/bolt/inventory.rb +11 -10
- data/lib/bolt/logger.rb +26 -19
- data/lib/bolt/module_installer.rb +197 -0
- data/lib/bolt/{puppetfile → module_installer}/installer.rb +3 -2
- data/lib/bolt/module_installer/puppetfile.rb +117 -0
- data/lib/bolt/module_installer/puppetfile/forge_module.rb +54 -0
- data/lib/bolt/module_installer/puppetfile/git_module.rb +37 -0
- data/lib/bolt/module_installer/puppetfile/module.rb +26 -0
- data/lib/bolt/module_installer/resolver.rb +76 -0
- data/lib/bolt/module_installer/specs.rb +93 -0
- data/lib/bolt/module_installer/specs/forge_spec.rb +84 -0
- data/lib/bolt/module_installer/specs/git_spec.rb +178 -0
- data/lib/bolt/outputter.rb +2 -45
- data/lib/bolt/outputter/human.rb +78 -18
- data/lib/bolt/outputter/json.rb +22 -7
- data/lib/bolt/outputter/logger.rb +2 -2
- data/lib/bolt/pal.rb +29 -25
- data/lib/bolt/plugin.rb +1 -1
- data/lib/bolt/plugin/module.rb +1 -1
- data/lib/bolt/project.rb +32 -22
- data/lib/bolt/project_migrator.rb +80 -0
- data/lib/bolt/project_migrator/base.rb +39 -0
- data/lib/bolt/project_migrator/config.rb +67 -0
- data/lib/bolt/project_migrator/inventory.rb +67 -0
- data/lib/bolt/project_migrator/modules.rb +200 -0
- data/lib/bolt/shell/bash.rb +4 -3
- data/lib/bolt/transport/base.rb +4 -4
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/util.rb +51 -10
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/acl.rb +2 -2
- data/lib/bolt_server/base_config.rb +3 -3
- data/lib/bolt_server/file_cache.rb +11 -11
- data/lib/bolt_server/schemas/partials/task.json +17 -2
- data/lib/bolt_server/transport_app.rb +93 -13
- data/lib/bolt_spec/bolt_context.rb +8 -6
- data/lib/bolt_spec/plans.rb +1 -1
- data/lib/bolt_spec/plans/mock_executor.rb +1 -1
- data/lib/bolt_spec/run.rb +1 -1
- metadata +30 -11
- data/lib/bolt/project_migrate.rb +0 -138
- data/lib/bolt/puppetfile.rb +0 -160
- data/lib/bolt/puppetfile/module.rb +0 -66
- data/lib/bolt_server/pe/pal.rb +0 -67
data/lib/bolt/config.rb
CHANGED
@@ -391,6 +391,12 @@ module Bolt
|
|
391
391
|
@logs << { warn: msg }
|
392
392
|
end
|
393
393
|
|
394
|
+
if @project.modules && @data['modulepath']&.include?(@project.managed_moduledir.to_s)
|
395
|
+
raise Bolt::ValidationError,
|
396
|
+
"Found invalid path in modulepath: #{@project.managed_moduledir}. This path "\
|
397
|
+
"is automatically appended to the modulepath and cannot be configured."
|
398
|
+
end
|
399
|
+
|
394
400
|
keys = OPTIONS.keys - %w[plugins plugin_hooks puppetdb]
|
395
401
|
keys.each do |key|
|
396
402
|
next unless Bolt::Util.references?(@data[key])
|
@@ -445,7 +451,13 @@ module Bolt
|
|
445
451
|
end
|
446
452
|
|
447
453
|
def modulepath
|
448
|
-
@data['modulepath'] || @project.modulepath
|
454
|
+
path = @data['modulepath'] || @project.modulepath
|
455
|
+
|
456
|
+
if @project.modules
|
457
|
+
path + [@project.managed_moduledir.to_s]
|
458
|
+
else
|
459
|
+
path
|
460
|
+
end
|
449
461
|
end
|
450
462
|
|
451
463
|
def modulepath=(value)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/config'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class Config
|
7
|
+
class Modulepath
|
8
|
+
BOLTLIB_PATH = File.expand_path('../../../bolt-modules', __dir__)
|
9
|
+
MODULES_PATH = File.expand_path('../../../modules', __dir__)
|
10
|
+
|
11
|
+
# The user_modulepath only includes the original modulepath and is used during pluginsync.
|
12
|
+
# We don't want to pluginsync any of the content from BOLT_MODULES since that content
|
13
|
+
# includes core modules that can conflict with modules installed with an agent.
|
14
|
+
attr_reader :user_modulepath
|
15
|
+
|
16
|
+
def initialize(user_modulepath, boltlib_path: BOLTLIB_PATH, builtin_content_path: MODULES_PATH)
|
17
|
+
@user_modulepath = Array(user_modulepath).flatten
|
18
|
+
@boltlib_path = Array(boltlib_path).flatten
|
19
|
+
@builtin_content_path = Array(builtin_content_path).flatten
|
20
|
+
end
|
21
|
+
|
22
|
+
# The full_modulepath includes both the BOLTLIB
|
23
|
+
# path and the MODULES_PATH to ensure bolt functions and
|
24
|
+
# built-in content are available in the compliler
|
25
|
+
def full_modulepath
|
26
|
+
@boltlib_path + @user_modulepath + @builtin_content_path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/bolt/config/options.rb
CHANGED
@@ -233,17 +233,46 @@ module Bolt
|
|
233
233
|
"install` command.",
|
234
234
|
type: Array,
|
235
235
|
items: {
|
236
|
-
type: Hash,
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
236
|
+
type: [Hash, String],
|
237
|
+
oneOf: [
|
238
|
+
{
|
239
|
+
required: ["name"],
|
240
|
+
properties: {
|
241
|
+
"name" => {
|
242
|
+
description: "The name of the module.",
|
243
|
+
type: String
|
244
|
+
},
|
245
|
+
"version_requirement" => {
|
246
|
+
description: "The version requirement for the module. Accepts a specific version (1.2.3), version "\
|
247
|
+
"shorthand (1.2.x), or a version range (>= 1.2.0).",
|
248
|
+
type: String
|
249
|
+
}
|
250
|
+
}
|
251
|
+
},
|
252
|
+
{
|
253
|
+
required: %w[git ref],
|
254
|
+
properties: {
|
255
|
+
"git" => {
|
256
|
+
description: "The URL to the public git repository.",
|
257
|
+
type: String
|
258
|
+
},
|
259
|
+
"ref" => {
|
260
|
+
description: "The git reference to check out. Can be either a branch, tag, or commit SHA.",
|
261
|
+
type: String
|
262
|
+
}
|
263
|
+
}
|
242
264
|
}
|
243
|
-
|
265
|
+
]
|
244
266
|
},
|
245
267
|
_plugin: false,
|
246
|
-
_example: [
|
268
|
+
_example: [
|
269
|
+
"puppetlabs-facts",
|
270
|
+
{ "name" => "puppetlabs-mysql" },
|
271
|
+
{ "name" => "puppetlabs-apache", "version_requirement" => "5.5.0" },
|
272
|
+
{ "name" => "puppetlabs-puppetdb", "version_requirement" => "7.x" },
|
273
|
+
{ "name" => "puppetlabs-firewall", "version_requirement" => ">= 1.0.0 < 3.0.0" },
|
274
|
+
{ "git" => "https://github.com/puppetlabs/puppetlabs-apt", "ref" => "7.6.0" }
|
275
|
+
]
|
247
276
|
},
|
248
277
|
"name" => {
|
249
278
|
description: "The name of the Bolt project. When this option is configured, the project is considered a "\
|
@@ -318,7 +347,7 @@ module Bolt
|
|
318
347
|
"server_urls" => {
|
319
348
|
description: "An array containing the PuppetDB host to connect to. Include the protocol `https` "\
|
320
349
|
"and the port, which is usually `8081`. For example, "\
|
321
|
-
"`https://my-
|
350
|
+
"`https://my-puppetdb-server.com:8081`.",
|
322
351
|
type: Array,
|
323
352
|
_example: ["https://puppet.example.com:8081"]
|
324
353
|
},
|
@@ -374,7 +374,7 @@ module Bolt
|
|
374
374
|
},
|
375
375
|
"ssh-command" => {
|
376
376
|
type: [Array, String],
|
377
|
-
description: "The command and
|
377
|
+
description: "The command and options to use when SSHing. This option is used when you need support for "\
|
378
378
|
"features or algorithms that are not supported by the net-ssh Ruby library. **This option "\
|
379
379
|
"is experimental.** You can read more about this option in [Native SSH "\
|
380
380
|
"transport](experimental_features.md#native-ssh-transport).",
|
data/lib/bolt/executor.rb
CHANGED
data/lib/bolt/inventory.rb
CHANGED
@@ -46,7 +46,7 @@ module Bolt
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def self.from_config(config, plugins)
|
49
|
-
logger =
|
49
|
+
logger = Bolt::Logger.logger(self)
|
50
50
|
|
51
51
|
if ENV.include?(ENVIRONMENT_VAR)
|
52
52
|
begin
|
@@ -56,16 +56,17 @@ module Bolt
|
|
56
56
|
rescue Psych::Exception
|
57
57
|
raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}"
|
58
58
|
end
|
59
|
+
elsif config.inventoryfile
|
60
|
+
data = Bolt::Util.read_yaml_hash(config.inventoryfile, 'inventory')
|
61
|
+
logger.debug("Loaded inventory from #{config.inventoryfile}")
|
59
62
|
else
|
60
|
-
data =
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
# This avoids rubocop complaining about identical conditionals
|
68
|
-
logger.debug("Loaded inventory from #{config.inventoryfile}") if config.inventoryfile
|
63
|
+
data = Bolt::Util.read_optional_yaml_hash(config.default_inventoryfile, 'inventory')
|
64
|
+
|
65
|
+
if config.default_inventoryfile.exist?
|
66
|
+
logger.debug("Loaded inventory from #{config.default_inventoryfile}")
|
67
|
+
else
|
68
|
+
logger.debug("Tried to load inventory from #{config.default_inventoryfile}, but the file does not exist")
|
69
|
+
end
|
69
70
|
end
|
70
71
|
|
71
72
|
# Resolve plugin references from transport config
|
data/lib/bolt/logger.rb
CHANGED
@@ -4,6 +4,10 @@ require 'logging'
|
|
4
4
|
|
5
5
|
module Bolt
|
6
6
|
module Logger
|
7
|
+
LEVELS = %w[trace debug info notice warn error fatal].freeze
|
8
|
+
@mutex = Mutex.new
|
9
|
+
@warnings = Set.new
|
10
|
+
|
7
11
|
# This method provides a single point-of-entry to setup logging for both
|
8
12
|
# the CLI and for tests. This is necessary because we define custom log
|
9
13
|
# levels which create corresponding methods on the logger instances;
|
@@ -11,20 +15,25 @@ module Bolt
|
|
11
15
|
# will fail.
|
12
16
|
def self.initialize_logging
|
13
17
|
# Initialization isn't idempotent and will result in warnings about const
|
14
|
-
# redefs, so skip it if it's
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
# redefs, so skip it if the log levels we expect are present. If it's
|
19
|
+
# already been initialized with an insufficient set of levels, go ahead
|
20
|
+
# and call init anyway or we'll have failures when calling log methods
|
21
|
+
# for missing levels.
|
22
|
+
unless levels & LEVELS == LEVELS
|
23
|
+
Logging.init(*LEVELS)
|
24
|
+
end
|
25
|
+
|
26
|
+
# As above, only create the color scheme if we haven't already created it.
|
27
|
+
unless Logging.color_scheme('bolt')
|
28
|
+
Logging.color_scheme(
|
29
|
+
'bolt',
|
30
|
+
lines: {
|
31
|
+
warn: :yellow,
|
32
|
+
error: :red,
|
33
|
+
fatal: %i[white on_red]
|
34
|
+
}
|
35
|
+
)
|
36
|
+
end
|
28
37
|
end
|
29
38
|
|
30
39
|
def self.configure(destinations, color)
|
@@ -115,14 +124,12 @@ module Bolt
|
|
115
124
|
end
|
116
125
|
|
117
126
|
def self.warn_once(type, msg)
|
118
|
-
@mutex.synchronize
|
119
|
-
@warnings ||= []
|
127
|
+
@mutex.synchronize do
|
120
128
|
@logger ||= Bolt::Logger.logger(self)
|
121
|
-
|
129
|
+
if @warnings.add?(type)
|
122
130
|
@logger.warn(msg)
|
123
|
-
@warnings << type
|
124
131
|
end
|
125
|
-
|
132
|
+
end
|
126
133
|
end
|
127
134
|
|
128
135
|
def self.deprecation_warning(type, msg)
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/error'
|
4
|
+
require 'bolt/logger'
|
5
|
+
require 'bolt/module_installer/installer'
|
6
|
+
require 'bolt/module_installer/puppetfile'
|
7
|
+
require 'bolt/module_installer/resolver'
|
8
|
+
require 'bolt/module_installer/specs'
|
9
|
+
|
10
|
+
module Bolt
|
11
|
+
class ModuleInstaller
|
12
|
+
def initialize(outputter, pal)
|
13
|
+
@outputter = outputter
|
14
|
+
@pal = pal
|
15
|
+
@logger = Bolt::Logger.logger(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Adds a single module to the project.
|
19
|
+
#
|
20
|
+
def add(name, specs, puppetfile_path, moduledir, config_path)
|
21
|
+
project_specs = Specs.new(specs)
|
22
|
+
|
23
|
+
# Exit early if project config already includes a spec with this name.
|
24
|
+
if project_specs.include?(name)
|
25
|
+
@outputter.print_message(
|
26
|
+
"Project configuration file #{config_path} already includes specification with name "\
|
27
|
+
"#{name}. Nothing to do."
|
28
|
+
)
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
|
32
|
+
@outputter.print_message("Adding module #{name} to project\n\n")
|
33
|
+
|
34
|
+
# Generate the specs to resolve from. If a Puppetfile exists, parse it and
|
35
|
+
# convert the modules to specs. Otherwise, use the project specs.
|
36
|
+
resolve_specs = if puppetfile_path.exist?
|
37
|
+
existing_puppetfile = Puppetfile.parse(puppetfile_path)
|
38
|
+
existing_puppetfile.assert_satisfies(project_specs)
|
39
|
+
Specs.from_puppetfile(existing_puppetfile)
|
40
|
+
else
|
41
|
+
project_specs
|
42
|
+
end
|
43
|
+
|
44
|
+
# Resolve module dependencies. Attempt to first resolve with resolve
|
45
|
+
# specss. If that fails, fall back to resolving from project specs.
|
46
|
+
# This prevents Bolt from modifying installed modules unless there is
|
47
|
+
# a version conflict.
|
48
|
+
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
49
|
+
|
50
|
+
begin
|
51
|
+
resolve_specs.add_specs('name' => name)
|
52
|
+
puppetfile = Resolver.new.resolve(resolve_specs)
|
53
|
+
rescue Bolt::Error
|
54
|
+
project_specs.add_specs('name' => name)
|
55
|
+
puppetfile = Resolver.new.resolve(project_specs)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Display the diff between the existing Puppetfile and the new Puppetfile.
|
59
|
+
print_puppetfile_diff(existing_puppetfile, puppetfile)
|
60
|
+
|
61
|
+
# Add the module to the project configuration.
|
62
|
+
@outputter.print_action_step("Updating project configuration file at #{config_path}")
|
63
|
+
|
64
|
+
data = Bolt::Util.read_yaml_hash(config_path, 'project')
|
65
|
+
data['modules'] ||= []
|
66
|
+
data['modules'] << name
|
67
|
+
|
68
|
+
begin
|
69
|
+
File.write(config_path, data.to_yaml)
|
70
|
+
rescue SystemCallError => e
|
71
|
+
raise Bolt::FileError.new(
|
72
|
+
"Unable to update project configuration file: #{e.message}",
|
73
|
+
config
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Write the Puppetfile.
|
78
|
+
@outputter.print_action_step("Writing Puppetfile at #{puppetfile_path}")
|
79
|
+
puppetfile.write(puppetfile_path, moduledir)
|
80
|
+
|
81
|
+
# Install the modules.
|
82
|
+
install_puppetfile(puppetfile_path, moduledir)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Outputs a diff of an old Puppetfile and a new Puppetfile.
|
86
|
+
#
|
87
|
+
def print_puppetfile_diff(old, new)
|
88
|
+
# Build hashes mapping the module name to the module object. This makes it
|
89
|
+
# a little easier to determine which modules have been added, removed, or
|
90
|
+
# modified.
|
91
|
+
old = (old&.modules || []).each_with_object({}) do |mod, acc|
|
92
|
+
next unless mod.type == :forge
|
93
|
+
acc[mod.full_name] = mod
|
94
|
+
end
|
95
|
+
|
96
|
+
new = new.modules.each_with_object({}) do |mod, acc|
|
97
|
+
next unless mod.type == :forge
|
98
|
+
acc[mod.full_name] = mod
|
99
|
+
end
|
100
|
+
|
101
|
+
# New modules are those present in new but not in old.
|
102
|
+
added = new.reject { |full_name, _mod| old.include?(full_name) }.values
|
103
|
+
|
104
|
+
if added.any?
|
105
|
+
diff = "Adding the following modules:\n"
|
106
|
+
added.each { |mod| diff += "#{mod.full_name} #{mod.version}\n" }
|
107
|
+
@outputter.print_action_step(diff)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Upgraded modules are those that have a newer version in new than old.
|
111
|
+
upgraded = new.select do |full_name, mod|
|
112
|
+
if old.include?(full_name)
|
113
|
+
mod.version > old[full_name].version
|
114
|
+
end
|
115
|
+
end.keys
|
116
|
+
|
117
|
+
if upgraded.any?
|
118
|
+
diff = "Upgrading the following modules:\n"
|
119
|
+
upgraded.each { |full_name| diff += "#{full_name} #{old[full_name].version} to #{new[full_name].version}\n" }
|
120
|
+
@outputter.print_action_step(diff)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Downgraded modules are those that have an older version in new than old.
|
124
|
+
downgraded = new.select do |full_name, mod|
|
125
|
+
if old.include?(full_name)
|
126
|
+
mod.version < old[full_name].version
|
127
|
+
end
|
128
|
+
end.keys
|
129
|
+
|
130
|
+
if downgraded.any?
|
131
|
+
diff = "Downgrading the following modules: \n"
|
132
|
+
downgraded.each { |full_name| diff += "#{full_name} #{old[full_name].version} to #{new[full_name].version}\n" }
|
133
|
+
@outputter.print_action_step(diff)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Removed modules are those present in old but not in new.
|
137
|
+
removed = old.reject { |full_name, _mod| new.include?(full_name) }.values
|
138
|
+
|
139
|
+
if removed.any?
|
140
|
+
diff = "Removing the following modules:\n"
|
141
|
+
removed.each { |mod| diff += "#{mod.full_name} #{mod.version}\n" }
|
142
|
+
@outputter.print_action_step(diff)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Installs a project's module dependencies.
|
147
|
+
#
|
148
|
+
def install(specs, path, moduledir, force: false, resolve: true)
|
149
|
+
@outputter.print_message("Installing project modules\n\n")
|
150
|
+
|
151
|
+
if resolve != false
|
152
|
+
specs = Specs.new(specs)
|
153
|
+
|
154
|
+
# If forcibly installing or if there is no Puppetfile, resolve
|
155
|
+
# and write a Puppetfile.
|
156
|
+
if force || !path.exist?
|
157
|
+
@outputter.print_action_step("Resolving module dependencies, this may take a moment")
|
158
|
+
puppetfile = Resolver.new.resolve(specs)
|
159
|
+
|
160
|
+
# We get here either through 'bolt module install' which uses the
|
161
|
+
# managed modulepath (which isn't configurable) or through bolt
|
162
|
+
# project init --modules, which uses the default modulepath. This
|
163
|
+
# should be safe to assume that if `.modules/` is the moduledir the
|
164
|
+
# user is using the new workflow
|
165
|
+
@outputter.print_action_step("Writing Puppetfile at #{path}")
|
166
|
+
if moduledir.basename.to_s == '.modules'
|
167
|
+
puppetfile.write(path, moduledir)
|
168
|
+
else
|
169
|
+
puppetfile.write(path)
|
170
|
+
end
|
171
|
+
# If not forcibly installing and there is a Puppetfile, assert
|
172
|
+
# that it satisfies the specs.
|
173
|
+
else
|
174
|
+
puppetfile = Puppetfile.parse(path)
|
175
|
+
puppetfile.assert_satisfies(specs)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Install the modules.
|
180
|
+
install_puppetfile(path, moduledir)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Installs the Puppetfile and generates types.
|
184
|
+
#
|
185
|
+
def install_puppetfile(path, moduledir, config = {})
|
186
|
+
@outputter.print_action_step("Syncing modules from #{path} to #{moduledir}")
|
187
|
+
ok = Installer.new(config).install(path, moduledir)
|
188
|
+
|
189
|
+
# Automatically generate types after installing modules
|
190
|
+
@pal.generate_types
|
191
|
+
|
192
|
+
@outputter.print_puppetfile_result(ok, path, moduledir)
|
193
|
+
|
194
|
+
ok
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -1,19 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'r10k/cli'
|
4
3
|
require 'bolt/r10k_log_proxy'
|
5
4
|
require 'bolt/error'
|
6
5
|
|
7
6
|
# This class is used to install modules from a Puppetfile to a module directory.
|
8
7
|
#
|
9
8
|
module Bolt
|
10
|
-
class
|
9
|
+
class ModuleInstaller
|
11
10
|
class Installer
|
12
11
|
def initialize(config = {})
|
13
12
|
@config = config
|
14
13
|
end
|
15
14
|
|
16
15
|
def install(path, moduledir)
|
16
|
+
require 'r10k/cli'
|
17
|
+
|
17
18
|
unless File.exist?(path)
|
18
19
|
raise Bolt::FileError.new(
|
19
20
|
"Could not find a Puppetfile at #{path}",
|