bolt 2.25.0 → 2.30.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 +4 -3
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
- data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +1 -1
- data/lib/bolt/analytics.rb +7 -3
- data/lib/bolt/applicator.rb +21 -21
- data/lib/bolt/bolt_option_parser.rb +116 -26
- data/lib/bolt/catalog.rb +5 -3
- data/lib/bolt/cli.rb +194 -185
- data/lib/bolt/config.rb +61 -26
- data/lib/bolt/config/options.rb +35 -2
- data/lib/bolt/executor.rb +2 -2
- data/lib/bolt/inventory.rb +8 -1
- data/lib/bolt/inventory/group.rb +1 -1
- data/lib/bolt/inventory/inventory.rb +1 -1
- data/lib/bolt/inventory/target.rb +1 -1
- data/lib/bolt/logger.rb +35 -21
- data/lib/bolt/module_installer.rb +172 -0
- data/lib/bolt/outputter.rb +4 -0
- data/lib/bolt/outputter/human.rb +53 -11
- data/lib/bolt/outputter/json.rb +7 -1
- data/lib/bolt/outputter/logger.rb +3 -3
- data/lib/bolt/pal.rb +29 -20
- data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
- data/lib/bolt/plugin/module.rb +1 -1
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +89 -28
- 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 +198 -0
- data/lib/bolt/puppetdb/client.rb +1 -1
- data/lib/bolt/puppetdb/config.rb +1 -1
- data/lib/bolt/puppetfile.rb +142 -0
- data/lib/bolt/puppetfile/installer.rb +43 -0
- data/lib/bolt/puppetfile/module.rb +90 -0
- data/lib/bolt/r10k_log_proxy.rb +1 -1
- data/lib/bolt/rerun.rb +2 -2
- data/lib/bolt/result.rb +23 -0
- data/lib/bolt/shell.rb +1 -1
- data/lib/bolt/shell/bash.rb +1 -1
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/base.rb +5 -5
- data/lib/bolt/transport/docker/connection.rb +1 -1
- data/lib/bolt/transport/local/connection.rb +1 -1
- data/lib/bolt/transport/ssh.rb +1 -1
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +1 -1
- data/lib/bolt/transport/winrm.rb +1 -1
- data/lib/bolt/transport/winrm/connection.rb +1 -1
- data/lib/bolt/util.rb +52 -11
- 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/config.rb +1 -1
- data/lib/bolt_server/file_cache.rb +12 -12
- data/lib/bolt_server/transport_app.rb +125 -26
- data/lib/bolt_spec/bolt_context.rb +4 -4
- data/lib/bolt_spec/plans/mock_executor.rb +1 -1
- metadata +15 -13
- data/lib/bolt/project_migrate.rb +0 -138
data/lib/bolt/config.rb
CHANGED
@@ -19,7 +19,7 @@ module Bolt
|
|
19
19
|
class Config
|
20
20
|
include Bolt::Config::Options
|
21
21
|
|
22
|
-
attr_reader :config_files, :
|
22
|
+
attr_reader :config_files, :logs, :data, :transports, :project, :modified_concurrency, :deprecations
|
23
23
|
|
24
24
|
BOLT_CONFIG_NAME = 'bolt.yaml'
|
25
25
|
BOLT_DEFAULTS_NAME = 'bolt-defaults.yaml'
|
@@ -32,16 +32,19 @@ module Bolt
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def self.from_project(project, overrides = {})
|
35
|
+
logs = []
|
35
36
|
conf = if project.project_file == project.config_file
|
36
37
|
project.data
|
37
38
|
else
|
38
|
-
Bolt::Util.read_optional_yaml_hash(project.config_file, 'config')
|
39
|
+
c = Bolt::Util.read_optional_yaml_hash(project.config_file, 'config')
|
40
|
+
logs << { debug: "Loaded configuration from #{project.config_file}" } if File.exist?(project.config_file)
|
41
|
+
c
|
39
42
|
end
|
40
43
|
|
41
44
|
data = load_defaults(project).push(
|
42
45
|
filepath: project.config_file,
|
43
46
|
data: conf,
|
44
|
-
|
47
|
+
logs: logs,
|
45
48
|
deprecations: []
|
46
49
|
)
|
47
50
|
|
@@ -50,17 +53,20 @@ module Bolt
|
|
50
53
|
|
51
54
|
def self.from_file(configfile, overrides = {})
|
52
55
|
project = Bolt::Project.create_project(Pathname.new(configfile).expand_path.dirname)
|
56
|
+
logs = []
|
53
57
|
|
54
58
|
conf = if project.project_file == project.config_file
|
55
59
|
project.data
|
56
60
|
else
|
57
|
-
Bolt::Util.read_yaml_hash(configfile, 'config')
|
61
|
+
c = Bolt::Util.read_yaml_hash(configfile, 'config')
|
62
|
+
logs << { debug: "Loaded configuration from #{configfile}" }
|
63
|
+
c
|
58
64
|
end
|
59
65
|
|
60
66
|
data = load_defaults(project).push(
|
61
|
-
filepath:
|
67
|
+
filepath: configfile,
|
62
68
|
data: conf,
|
63
|
-
|
69
|
+
logs: logs,
|
64
70
|
deprecations: []
|
65
71
|
)
|
66
72
|
|
@@ -90,13 +96,13 @@ module Bolt
|
|
90
96
|
def self.load_bolt_defaults_yaml(dir)
|
91
97
|
filepath = dir + BOLT_DEFAULTS_NAME
|
92
98
|
data = Bolt::Util.read_yaml_hash(filepath, 'config')
|
93
|
-
|
99
|
+
logs = [{ debug: "Loaded configuration from #{filepath}" }]
|
94
100
|
|
95
101
|
# Warn if 'bolt.yaml' detected in same directory.
|
96
102
|
if File.exist?(bolt_yaml = dir + BOLT_CONFIG_NAME)
|
97
|
-
|
98
|
-
|
99
|
-
|
103
|
+
logs.push(
|
104
|
+
warn: "Detected multiple configuration files: ['#{bolt_yaml}', '#{filepath}']. '#{bolt_yaml}' "\
|
105
|
+
"will be ignored."
|
100
106
|
)
|
101
107
|
end
|
102
108
|
|
@@ -105,9 +111,9 @@ module Bolt
|
|
105
111
|
|
106
112
|
if project_config.any?
|
107
113
|
data.reject! { |key, _| project_config.include?(key) }
|
108
|
-
|
109
|
-
|
110
|
-
|
114
|
+
logs.push(
|
115
|
+
warn: "Unsupported project configuration detected in '#{filepath}': #{project_config.keys}. "\
|
116
|
+
"Project configuration should be set in 'bolt-project.yaml'."
|
111
117
|
)
|
112
118
|
end
|
113
119
|
|
@@ -116,10 +122,10 @@ module Bolt
|
|
116
122
|
|
117
123
|
if transport_config.any?
|
118
124
|
data.reject! { |key, _| transport_config.include?(key) }
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
125
|
+
logs.push(
|
126
|
+
warn: "Unsupported inventory configuration detected in '#{filepath}': #{transport_config.keys}. "\
|
127
|
+
"Transport configuration should be set under the 'inventory-config' option or "\
|
128
|
+
"in 'inventory.yaml'."
|
123
129
|
)
|
124
130
|
end
|
125
131
|
|
@@ -142,7 +148,7 @@ module Bolt
|
|
142
148
|
data = data.merge(data.delete('inventory-config'))
|
143
149
|
end
|
144
150
|
|
145
|
-
{ filepath: filepath, data: data,
|
151
|
+
{ filepath: filepath, data: data, logs: logs, deprecations: [] }
|
146
152
|
end
|
147
153
|
|
148
154
|
# Loads a 'bolt.yaml' file, the legacy configuration file. There's no special munging needed
|
@@ -150,11 +156,12 @@ module Bolt
|
|
150
156
|
def self.load_bolt_yaml(dir)
|
151
157
|
filepath = dir + BOLT_CONFIG_NAME
|
152
158
|
data = Bolt::Util.read_yaml_hash(filepath, 'config')
|
159
|
+
logs = [{ debug: "Loaded configuration from #{filepath}" }]
|
153
160
|
deprecations = [{ type: 'Using bolt.yaml for system configuration',
|
154
161
|
msg: "Configuration file #{filepath} is deprecated and will be removed in a future version "\
|
155
162
|
"of Bolt. Use '#{dir + BOLT_DEFAULTS_NAME}' instead." }]
|
156
163
|
|
157
|
-
{ filepath: filepath, data: data,
|
164
|
+
{ filepath: filepath, data: data, logs: logs, deprecations: deprecations }
|
158
165
|
end
|
159
166
|
|
160
167
|
def self.load_defaults(project)
|
@@ -187,13 +194,13 @@ module Bolt
|
|
187
194
|
unless config_data.is_a?(Array)
|
188
195
|
config_data = [{ filepath: project.config_file,
|
189
196
|
data: config_data,
|
190
|
-
|
197
|
+
logs: [],
|
191
198
|
deprecations: [] }]
|
192
199
|
end
|
193
200
|
|
194
|
-
@logger =
|
201
|
+
@logger = Bolt::Logger.logger(self)
|
195
202
|
@project = project
|
196
|
-
@
|
203
|
+
@logs = @project.logs.dup
|
197
204
|
@deprecations = @project.deprecations.dup
|
198
205
|
@transports = {}
|
199
206
|
@config_files = []
|
@@ -221,7 +228,7 @@ module Bolt
|
|
221
228
|
end
|
222
229
|
|
223
230
|
loaded_data = config_data.each_with_object([]) do |data, acc|
|
224
|
-
@
|
231
|
+
@logs.concat(data[:logs]) if data[:logs].any?
|
225
232
|
@deprecations.concat(data[:deprecations]) if data[:deprecations].any?
|
226
233
|
|
227
234
|
if data[:data].any?
|
@@ -337,10 +344,26 @@ module Bolt
|
|
337
344
|
end
|
338
345
|
|
339
346
|
private def update_logs(logs)
|
347
|
+
begin
|
348
|
+
if logs['bolt-debug.log'] && logs['bolt-debug.log'] != 'disable'
|
349
|
+
FileUtils.touch(File.expand_path('bolt-debug.log', @project.path))
|
350
|
+
end
|
351
|
+
rescue StandardError
|
352
|
+
logs.delete('bolt-debug.log')
|
353
|
+
end
|
354
|
+
|
340
355
|
logs.each_with_object({}) do |(key, val), acc|
|
341
|
-
|
356
|
+
# Remove any disabled logs
|
357
|
+
next if val == 'disable'
|
342
358
|
|
343
359
|
name = normalize_log(key)
|
360
|
+
|
361
|
+
# But otherwise it has to be a Hash
|
362
|
+
unless val.is_a?(Hash)
|
363
|
+
raise Bolt::ValidationError,
|
364
|
+
"config of log #{name} must be a Hash, received #{val.class} #{val.inspect}"
|
365
|
+
end
|
366
|
+
|
344
367
|
acc[name] = val.slice('append', 'level')
|
345
368
|
.transform_keys(&:to_sym)
|
346
369
|
|
@@ -365,7 +388,13 @@ module Bolt
|
|
365
388
|
def validate
|
366
389
|
if @data['future']
|
367
390
|
msg = "Configuration option 'future' no longer exposes future behavior."
|
368
|
-
@
|
391
|
+
@logs << { warn: msg }
|
392
|
+
end
|
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."
|
369
398
|
end
|
370
399
|
|
371
400
|
keys = OPTIONS.keys - %w[plugins plugin_hooks puppetdb]
|
@@ -422,7 +451,13 @@ module Bolt
|
|
422
451
|
end
|
423
452
|
|
424
453
|
def modulepath
|
425
|
-
@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
|
426
461
|
end
|
427
462
|
|
428
463
|
def modulepath=(value)
|
data/lib/bolt/config/options.rb
CHANGED
@@ -176,7 +176,9 @@ module Bolt
|
|
176
176
|
description: "A map of configuration for the logfile output. Under `log`, you can configure log options "\
|
177
177
|
"for `console` and add configuration for individual log files, such as "\
|
178
178
|
"`~/.puppetlabs/bolt/debug.log`. Individual log files must be valid filepaths. If the log "\
|
179
|
-
"file does not exist, then Bolt will create it before logging information."
|
179
|
+
"file does not exist, then Bolt will create it before logging information. Set the value to "\
|
180
|
+
"`disable` to remove a log file defined at an earlier level of the config hierarchy. By "\
|
181
|
+
"default, Bolt logs to a bolt-debug.log file in the Bolt project directory.",
|
180
182
|
type: Hash,
|
181
183
|
properties: {
|
182
184
|
"console" => {
|
@@ -194,7 +196,8 @@ module Bolt
|
|
194
196
|
},
|
195
197
|
additionalProperties: {
|
196
198
|
description: "Configuration for the logfile output.",
|
197
|
-
type: Hash,
|
199
|
+
type: [String, Hash],
|
200
|
+
enum: ['disable'],
|
198
201
|
properties: {
|
199
202
|
"append" => {
|
200
203
|
description: "Whether to append output to an existing log file.",
|
@@ -224,6 +227,35 @@ module Bolt
|
|
224
227
|
_example: ["~/.puppetlabs/bolt/modules", "~/.puppetlabs/bolt/site-modules"],
|
225
228
|
_default: ["project/modules", "project/site-modules", "project/site"]
|
226
229
|
},
|
230
|
+
"modules" => {
|
231
|
+
description: "A list of module dependencies for the project. Each dependency is a map of data specifying "\
|
232
|
+
"the module to install. To install the project's module dependencies, run the `bolt module "\
|
233
|
+
"install` command.",
|
234
|
+
type: Array,
|
235
|
+
items: {
|
236
|
+
type: [Hash, String],
|
237
|
+
required: ["name"],
|
238
|
+
properties: {
|
239
|
+
"name" => {
|
240
|
+
description: "The name of the module.",
|
241
|
+
type: String
|
242
|
+
},
|
243
|
+
"version_requirement" => {
|
244
|
+
description: "The version requirement for the module. Accepts a specific version (1.2.3), version "\
|
245
|
+
"shorthand (1.2.x), or a version range (>= 1.2.0).",
|
246
|
+
type: String
|
247
|
+
}
|
248
|
+
}
|
249
|
+
},
|
250
|
+
_plugin: false,
|
251
|
+
_example: [
|
252
|
+
{ "name" => "puppetlabs-mysql" },
|
253
|
+
"puppetlabs-facts",
|
254
|
+
{ "name" => "puppetlabs-apache", "version_requirement" => "5.5.0" },
|
255
|
+
{ "name" => "puppetlabs-puppetdb", "version_requirement" => "7.x" },
|
256
|
+
{ "name" => "puppetlabs-firewall", "version_requirement" => ">= 1.0.0 < 3.0.0" }
|
257
|
+
]
|
258
|
+
},
|
227
259
|
"name" => {
|
228
260
|
description: "The name of the Bolt project. When this option is configured, the project is considered a "\
|
229
261
|
"[Bolt project](experimental_features.md#bolt-projects), allowing Bolt to load content from "\
|
@@ -473,6 +505,7 @@ module Bolt
|
|
473
505
|
inventoryfile
|
474
506
|
log
|
475
507
|
modulepath
|
508
|
+
modules
|
476
509
|
name
|
477
510
|
plans
|
478
511
|
plugin_hooks
|
data/lib/bolt/executor.rb
CHANGED
@@ -40,7 +40,7 @@ module Bolt
|
|
40
40
|
require 'concurrent'
|
41
41
|
|
42
42
|
@analytics = analytics
|
43
|
-
@logger =
|
43
|
+
@logger = Bolt::Logger.logger(self)
|
44
44
|
|
45
45
|
@transports = Bolt::TRANSPORTS.each_with_object({}) do |(key, val), coll|
|
46
46
|
coll[key.to_s] = if key == :remote
|
@@ -227,7 +227,7 @@ module Bolt
|
|
227
227
|
data[:resource_mean] = sum / resource_counts.length
|
228
228
|
end
|
229
229
|
|
230
|
-
@analytics&.event('Apply', 'ast', data)
|
230
|
+
@analytics&.event('Apply', 'ast', **data)
|
231
231
|
end
|
232
232
|
|
233
233
|
def report_yaml_plan(plan)
|
data/lib/bolt/inventory.rb
CHANGED
@@ -46,10 +46,13 @@ module Bolt
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def self.from_config(config, plugins)
|
49
|
+
logger = Bolt::Logger.logger(self)
|
50
|
+
|
49
51
|
if ENV.include?(ENVIRONMENT_VAR)
|
50
52
|
begin
|
51
53
|
data = YAML.safe_load(ENV[ENVIRONMENT_VAR])
|
52
54
|
raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}" unless data.is_a?(Hash)
|
55
|
+
logger.debug("Loaded inventory from environment variable #{ENVIRONMENT_VAR}")
|
53
56
|
rescue Psych::Exception
|
54
57
|
raise Bolt::ParseError, "Could not parse inventory from $#{ENVIRONMENT_VAR}"
|
55
58
|
end
|
@@ -57,8 +60,12 @@ module Bolt
|
|
57
60
|
data = if config.inventoryfile
|
58
61
|
Bolt::Util.read_yaml_hash(config.inventoryfile, 'inventory')
|
59
62
|
else
|
60
|
-
Bolt::Util.read_optional_yaml_hash(config.default_inventoryfile, 'inventory')
|
63
|
+
i = Bolt::Util.read_optional_yaml_hash(config.default_inventoryfile, 'inventory')
|
64
|
+
logger.debug("Loaded inventory from #{config.default_inventoryfile}") if i
|
65
|
+
i
|
61
66
|
end
|
67
|
+
# This avoids rubocop complaining about identical conditionals
|
68
|
+
logger.debug("Loaded inventory from #{config.inventoryfile}") if config.inventoryfile
|
62
69
|
end
|
63
70
|
|
64
71
|
# Resolve plugin references from transport config
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -19,7 +19,7 @@ module Bolt
|
|
19
19
|
CONFIG_KEYS = Bolt::Config::INVENTORY_OPTIONS.keys
|
20
20
|
|
21
21
|
def initialize(input, plugins)
|
22
|
-
@logger =
|
22
|
+
@logger = Bolt::Logger.logger(self)
|
23
23
|
@plugins = plugins
|
24
24
|
|
25
25
|
input = @plugins.resolve_top_level_references(input) if @plugins.reference?(input)
|
@@ -16,7 +16,7 @@ module Bolt
|
|
16
16
|
|
17
17
|
# TODO: Pass transport config instead of config object
|
18
18
|
def initialize(data, transport, transports, plugins)
|
19
|
-
@logger =
|
19
|
+
@logger = Bolt::Logger.logger(self)
|
20
20
|
@data = data || {}
|
21
21
|
@transport = transport
|
22
22
|
@config = transports
|
@@ -13,7 +13,7 @@ module Bolt
|
|
13
13
|
raise Bolt::Inventory::ValidationError.new("Target must have either a name or uri", nil)
|
14
14
|
end
|
15
15
|
|
16
|
-
@logger =
|
16
|
+
@logger = Bolt::Logger.logger(inventory)
|
17
17
|
|
18
18
|
# If the target isn't mentioned by any groups, it won't have a uri or
|
19
19
|
# name and we will use the target_name as both
|
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,24 +15,29 @@ 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)
|
31
|
-
root_logger =
|
40
|
+
root_logger = Bolt::Logger.logger(:root)
|
32
41
|
|
33
42
|
root_logger.add_appenders Logging.appenders.stderr(
|
34
43
|
'console',
|
@@ -66,6 +75,13 @@ module Bolt
|
|
66
75
|
end
|
67
76
|
end
|
68
77
|
|
78
|
+
# A helper to ensure the Logging library is always initialized with our
|
79
|
+
# custom log levels before retrieving a Logger instance.
|
80
|
+
def self.logger(name)
|
81
|
+
initialize_logging
|
82
|
+
Logging.logger[name]
|
83
|
+
end
|
84
|
+
|
69
85
|
def self.analytics=(analytics)
|
70
86
|
@analytics = analytics
|
71
87
|
end
|
@@ -108,14 +124,12 @@ module Bolt
|
|
108
124
|
end
|
109
125
|
|
110
126
|
def self.warn_once(type, msg)
|
111
|
-
@mutex.synchronize
|
112
|
-
@
|
113
|
-
@
|
114
|
-
unless @warnings.include?(type)
|
127
|
+
@mutex.synchronize do
|
128
|
+
@logger ||= Bolt::Logger.logger(self)
|
129
|
+
if @warnings.add?(type)
|
115
130
|
@logger.warn(msg)
|
116
|
-
@warnings << type
|
117
131
|
end
|
118
|
-
|
132
|
+
end
|
119
133
|
end
|
120
134
|
|
121
135
|
def self.deprecation_warning(type, msg)
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bolt/error'
|
4
|
+
require 'bolt/logger'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
class ModuleInstaller
|
8
|
+
def initialize(outputter, pal)
|
9
|
+
@outputter = outputter
|
10
|
+
@pal = pal
|
11
|
+
@logger = Bolt::Logger.logger(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Adds a single module to the project.
|
15
|
+
#
|
16
|
+
def add(name, modules, puppetfile_path, moduledir, config_path)
|
17
|
+
require 'bolt/puppetfile'
|
18
|
+
|
19
|
+
# If the project configuration file already includes this module,
|
20
|
+
# exit early.
|
21
|
+
puppetfile = Bolt::Puppetfile.new(modules)
|
22
|
+
new_module = Bolt::Puppetfile::Module.from_hash('name' => name)
|
23
|
+
|
24
|
+
if puppetfile.modules.include?(new_module)
|
25
|
+
@outputter.print_message "Project configuration file #{config_path} already "\
|
26
|
+
"includes module #{new_module}. Nothing to do."
|
27
|
+
return true
|
28
|
+
end
|
29
|
+
|
30
|
+
# If the Puppetfile exists, make sure it's managed by Bolt.
|
31
|
+
if puppetfile_path.exist?
|
32
|
+
assert_managed_puppetfile(puppetfile, puppetfile_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Create a Puppetfile object that includes the new module and its
|
36
|
+
# dependencies. We error early here so we don't add the new module to the
|
37
|
+
# project config or modify the Puppetfile.
|
38
|
+
puppetfile = add_new_module_to_puppetfile(new_module, modules, puppetfile_path)
|
39
|
+
|
40
|
+
# Add the module to the project configuration.
|
41
|
+
@outputter.print_message "Updating project configuration file at #{config_path}"
|
42
|
+
|
43
|
+
data = Bolt::Util.read_yaml_hash(config_path, 'project')
|
44
|
+
data['modules'] ||= []
|
45
|
+
data['modules'] << { 'name' => new_module.title }
|
46
|
+
|
47
|
+
begin
|
48
|
+
File.write(config_path, data.to_yaml)
|
49
|
+
rescue SystemCallError => e
|
50
|
+
raise Bolt::FileError.new(
|
51
|
+
"Unable to update project configuration file: #{e.message}",
|
52
|
+
config
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Write the Puppetfile.
|
57
|
+
@outputter.print_message "Writing Puppetfile at #{puppetfile_path}"
|
58
|
+
puppetfile.write(puppetfile_path, moduledir)
|
59
|
+
|
60
|
+
# Install the modules.
|
61
|
+
install_puppetfile(puppetfile_path, moduledir)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Creates a new Puppetfile that includes the new module and its dependencies.
|
65
|
+
#
|
66
|
+
private def add_new_module_to_puppetfile(new_module, modules, path)
|
67
|
+
@outputter.print_message "Resolving module dependencies, this may take a moment"
|
68
|
+
|
69
|
+
# If there is an existing Puppetfile, add the new module and attempt
|
70
|
+
# to resolve. This will not update the versions of any installed modules.
|
71
|
+
if path.exist?
|
72
|
+
puppetfile = Bolt::Puppetfile.parse(path)
|
73
|
+
puppetfile.add_modules(new_module)
|
74
|
+
|
75
|
+
begin
|
76
|
+
puppetfile.resolve
|
77
|
+
return puppetfile
|
78
|
+
rescue Bolt::Error
|
79
|
+
@logger.debug "Unable to find a version of #{new_module} compatible "\
|
80
|
+
"with installed modules. Attempting to re-resolve modules "\
|
81
|
+
"from project configuration; some versions of installed "\
|
82
|
+
"modules may change."
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# If there is not an existing Puppetfile, or resolving with pinned
|
87
|
+
# modules fails, resolve all of the module declarations with the new
|
88
|
+
# module.
|
89
|
+
puppetfile = Bolt::Puppetfile.new(modules)
|
90
|
+
puppetfile.add_modules(new_module)
|
91
|
+
puppetfile.resolve
|
92
|
+
puppetfile
|
93
|
+
end
|
94
|
+
|
95
|
+
# Installs a project's module dependencies.
|
96
|
+
#
|
97
|
+
def install(modules, path, moduledir, force: false, resolve: true)
|
98
|
+
require 'bolt/puppetfile'
|
99
|
+
|
100
|
+
puppetfile = Bolt::Puppetfile.new(modules)
|
101
|
+
|
102
|
+
# If the Puppetfile exists, check if it includes specs for each declared
|
103
|
+
# module, erroring if there are any missing. Otherwise, resolve the
|
104
|
+
# module dependencies and write a new Puppetfile. Users can forcibly
|
105
|
+
# overwrite an existing Puppetfile with the '--force' option, or opt to
|
106
|
+
# install the Puppetfile as-is with --no-resolve.
|
107
|
+
#
|
108
|
+
# This is just if resolve is not false (nil should default to true)
|
109
|
+
if resolve != false
|
110
|
+
if path.exist? && !force
|
111
|
+
assert_managed_puppetfile(puppetfile, path)
|
112
|
+
else
|
113
|
+
@outputter.print_message "Resolving module dependencies, this may take a moment"
|
114
|
+
puppetfile.resolve
|
115
|
+
|
116
|
+
@outputter.print_message "Writing Puppetfile at #{path}"
|
117
|
+
# We get here either through 'bolt module install' which uses the
|
118
|
+
# managed modulepath (which isn't configurable) or through bolt
|
119
|
+
# project init --modules, which uses the default modulepath. This
|
120
|
+
# should be safe to assume that if `.modules/` is the moduledir the
|
121
|
+
# user is using the new workflow
|
122
|
+
if moduledir.basename == '.modules'
|
123
|
+
puppetfile.write(path, moduledir)
|
124
|
+
else
|
125
|
+
puppetfile.write(path)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Install the modules.
|
131
|
+
install_puppetfile(path, moduledir)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Installs the Puppetfile and generates types.
|
135
|
+
#
|
136
|
+
def install_puppetfile(path, moduledir, config = {})
|
137
|
+
require 'bolt/puppetfile/installer'
|
138
|
+
|
139
|
+
@outputter.print_message "Syncing modules from #{path} to #{moduledir}"
|
140
|
+
ok = Bolt::Puppetfile::Installer.new(config).install(path, moduledir)
|
141
|
+
|
142
|
+
# Automatically generate types after installing modules
|
143
|
+
@pal.generate_types
|
144
|
+
|
145
|
+
@outputter.print_puppetfile_result(ok, path, moduledir)
|
146
|
+
|
147
|
+
ok
|
148
|
+
end
|
149
|
+
|
150
|
+
# Asserts that an existing Puppetfile is managed by Bolt.
|
151
|
+
#
|
152
|
+
private def assert_managed_puppetfile(puppetfile, path)
|
153
|
+
existing_puppetfile = Bolt::Puppetfile.parse(path)
|
154
|
+
|
155
|
+
unless existing_puppetfile.modules.superset? puppetfile.modules
|
156
|
+
missing_modules = puppetfile.modules - existing_puppetfile.modules
|
157
|
+
|
158
|
+
message = <<~MESSAGE.chomp
|
159
|
+
Puppetfile #{path} is missing specifications for the following
|
160
|
+
module declarations:
|
161
|
+
|
162
|
+
#{missing_modules.map(&:to_hash).to_yaml.lines.drop(1).join.chomp}
|
163
|
+
|
164
|
+
This may not be a Puppetfile managed by Bolt. To forcibly overwrite the
|
165
|
+
Puppetfile, run 'bolt module install --force'.
|
166
|
+
MESSAGE
|
167
|
+
|
168
|
+
raise Bolt::Error.new(message, 'bolt/missing-module-specs')
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|