bolt 2.24.0 → 2.28.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 +1 -1
- 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 +75 -7
- data/lib/bolt/catalog.rb +4 -2
- data/lib/bolt/cli.rb +126 -147
- data/lib/bolt/config.rb +56 -26
- data/lib/bolt/config/options.rb +24 -2
- data/lib/bolt/executor.rb +1 -1
- 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 +9 -2
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/pal.rb +21 -10
- data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +63 -17
- data/lib/bolt/puppetdb/client.rb +1 -1
- data/lib/bolt/puppetdb/config.rb +1 -1
- data/lib/bolt/puppetfile.rb +160 -0
- data/lib/bolt/puppetfile/installer.rb +43 -0
- data/lib/bolt/puppetfile/module.rb +66 -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/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 +11 -11
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +1 -1
- 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/run.rb +3 -0
- metadata +9 -12
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 = []
|
@@ -204,7 +211,7 @@ module Bolt
|
|
204
211
|
'compile-concurrency' => Etc.nprocessors,
|
205
212
|
'concurrency' => default_concurrency,
|
206
213
|
'format' => 'human',
|
207
|
-
'log' => { 'console' => {}
|
214
|
+
'log' => { 'console' => {} },
|
208
215
|
'plugin_hooks' => {},
|
209
216
|
'plugins' => {},
|
210
217
|
'puppetdb' => {},
|
@@ -213,8 +220,15 @@ module Bolt
|
|
213
220
|
'transport' => 'ssh'
|
214
221
|
}
|
215
222
|
|
223
|
+
if project.path.directory?
|
224
|
+
default_data['log']['bolt-debug.log'] = {
|
225
|
+
'level' => 'debug',
|
226
|
+
'append' => false
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
216
230
|
loaded_data = config_data.each_with_object([]) do |data, acc|
|
217
|
-
@
|
231
|
+
@logs.concat(data[:logs]) if data[:logs].any?
|
218
232
|
@deprecations.concat(data[:deprecations]) if data[:deprecations].any?
|
219
233
|
|
220
234
|
if data[:data].any?
|
@@ -330,10 +344,26 @@ module Bolt
|
|
330
344
|
end
|
331
345
|
|
332
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
|
+
|
333
355
|
logs.each_with_object({}) do |(key, val), acc|
|
334
|
-
|
356
|
+
# Remove any disabled logs
|
357
|
+
next if val == 'disable'
|
335
358
|
|
336
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
|
+
|
337
367
|
acc[name] = val.slice('append', 'level')
|
338
368
|
.transform_keys(&:to_sym)
|
339
369
|
|
@@ -358,7 +388,7 @@ module Bolt
|
|
358
388
|
def validate
|
359
389
|
if @data['future']
|
360
390
|
msg = "Configuration option 'future' no longer exposes future behavior."
|
361
|
-
@
|
391
|
+
@logs << { warn: msg }
|
362
392
|
end
|
363
393
|
|
364
394
|
keys = OPTIONS.keys - %w[plugins plugin_hooks puppetdb]
|
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,24 @@ 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,
|
237
|
+
required: ["name"],
|
238
|
+
properties: {
|
239
|
+
"name" => {
|
240
|
+
description: "The name of the module.",
|
241
|
+
type: String
|
242
|
+
}
|
243
|
+
}
|
244
|
+
},
|
245
|
+
_plugin: false,
|
246
|
+
_example: [{ "name" => "puppetlabs-mysql" }, { "name" => "puppetlabs-apache" }]
|
247
|
+
},
|
227
248
|
"name" => {
|
228
249
|
description: "The name of the Bolt project. When this option is configured, the project is considered a "\
|
229
250
|
"[Bolt project](experimental_features.md#bolt-projects), allowing Bolt to load content from "\
|
@@ -473,6 +494,7 @@ module Bolt
|
|
473
494
|
inventoryfile
|
474
495
|
log
|
475
496
|
modulepath
|
497
|
+
modules
|
476
498
|
name
|
477
499
|
plans
|
478
500
|
plugin_hooks
|
data/lib/bolt/executor.rb
CHANGED
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
@@ -28,7 +28,7 @@ module Bolt
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.configure(destinations, color)
|
31
|
-
root_logger =
|
31
|
+
root_logger = Bolt::Logger.logger(:root)
|
32
32
|
|
33
33
|
root_logger.add_appenders Logging.appenders.stderr(
|
34
34
|
'console',
|
@@ -66,6 +66,13 @@ module Bolt
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
# A helper to ensure the Logging library is always initialized with our
|
70
|
+
# custom log levels before retrieving a Logger instance.
|
71
|
+
def self.logger(name)
|
72
|
+
initialize_logging
|
73
|
+
Logging.logger[name]
|
74
|
+
end
|
75
|
+
|
69
76
|
def self.analytics=(analytics)
|
70
77
|
@analytics = analytics
|
71
78
|
end
|
@@ -110,7 +117,7 @@ module Bolt
|
|
110
117
|
def self.warn_once(type, msg)
|
111
118
|
@mutex.synchronize {
|
112
119
|
@warnings ||= []
|
113
|
-
@logger ||=
|
120
|
+
@logger ||= Bolt::Logger.logger(self)
|
114
121
|
unless @warnings.include?(type)
|
115
122
|
@logger.warn(msg)
|
116
123
|
@warnings << type
|
data/lib/bolt/pal.rb
CHANGED
@@ -65,7 +65,7 @@ module Bolt
|
|
65
65
|
@resource_types = resource_types
|
66
66
|
@project = project
|
67
67
|
|
68
|
-
@logger =
|
68
|
+
@logger = Bolt::Logger.logger(self)
|
69
69
|
if modulepath && !modulepath.empty?
|
70
70
|
@logger.debug("Loading modules from #{@modulepath.join(File::PATH_SEPARATOR)}")
|
71
71
|
end
|
@@ -76,7 +76,7 @@ module Bolt
|
|
76
76
|
# Puppet logging is global so this is class method to avoid confusion
|
77
77
|
def self.configure_logging
|
78
78
|
Puppet::Util::Log.destinations.clear
|
79
|
-
Puppet::Util::Log.newdestination(
|
79
|
+
Puppet::Util::Log.newdestination(Bolt::Logger.logger('Puppet'))
|
80
80
|
# Defer all log level decisions to the Logging library by telling Puppet
|
81
81
|
# to log everything
|
82
82
|
Puppet.settings[:log_level] = 'debug'
|
@@ -141,6 +141,19 @@ module Bolt
|
|
141
141
|
end
|
142
142
|
end
|
143
143
|
|
144
|
+
def detect_project_conflict(project, environment)
|
145
|
+
return unless project && project.load_as_module?
|
146
|
+
# The environment modulepath has stripped out non-existent directories,
|
147
|
+
# so we don't need to check for them
|
148
|
+
modules = environment.modulepath.flat_map do |path|
|
149
|
+
Dir.children(path).select { |name| Puppet::Module.is_module_directory?(name, path) }
|
150
|
+
end
|
151
|
+
if modules.include?(project.name)
|
152
|
+
Bolt::Logger.warn_once("project shadows module",
|
153
|
+
"The project '#{project.name}' shadows an existing module of the same name")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
144
157
|
# Runs a block in a PAL script compiler configured for Bolt. Catches
|
145
158
|
# exceptions thrown by the block and re-raises them ensuring they are
|
146
159
|
# Bolt::Errors since the script compiler block will squash all exceptions.
|
@@ -149,15 +162,13 @@ module Bolt
|
|
149
162
|
setup
|
150
163
|
r = Puppet::Pal.in_tmp_environment('bolt', modulepath: @modulepath, facts: {}) do |pal|
|
151
164
|
# Only load the project if it a) exists, b) has a name it can be loaded with
|
152
|
-
bolt_project
|
153
|
-
# Puppet currently won't receive the project unless it is a named project. Since
|
154
|
-
# the download_file plan function needs access to the project path, add it to the
|
155
|
-
# context.
|
156
|
-
bolt_project_data = @project
|
157
|
-
Puppet.override(bolt_project: bolt_project,
|
158
|
-
bolt_project_data: bolt_project_data,
|
165
|
+
Puppet.override(bolt_project: @project,
|
159
166
|
yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
|
160
|
-
|
167
|
+
# Because this has the side effect of loading and caching the list
|
168
|
+
# of modules, it must happen *after* we have overridden
|
169
|
+
# bolt_project or the project will be ignored
|
170
|
+
detect_project_conflict(@project, Puppet.lookup(:environments).get('bolt'))
|
171
|
+
pal.with_script_compiler(set_local_facts: false) do |compiler|
|
161
172
|
alias_types(compiler)
|
162
173
|
register_resource_types(Puppet.lookup(:loaders)) if @resource_types
|
163
174
|
begin
|
@@ -7,7 +7,7 @@ module Bolt
|
|
7
7
|
class YamlPlan
|
8
8
|
class Evaluator
|
9
9
|
def initialize(analytics = Bolt::Analytics::NoopClient.new)
|
10
|
-
@logger =
|
10
|
+
@logger = Bolt::Logger.logger(self)
|
11
11
|
@analytics = analytics
|
12
12
|
@evaluator = Puppet::Pops::Parser::EvaluatingParser.new
|
13
13
|
end
|
data/lib/bolt/plugin/puppetdb.rb
CHANGED
@@ -19,7 +19,7 @@ module Bolt
|
|
19
19
|
def initialize(config:, context:)
|
20
20
|
pdb_config = Bolt::PuppetDB::Config.load_config(config, context.boltdir)
|
21
21
|
@puppetdb_client = Bolt::PuppetDB::Client.new(pdb_config)
|
22
|
-
@logger =
|
22
|
+
@logger = Bolt::Logger.logger(self)
|
23
23
|
end
|
24
24
|
|
25
25
|
def name
|
data/lib/bolt/project.rb
CHANGED
@@ -17,37 +17,53 @@ module Bolt
|
|
17
17
|
}.freeze
|
18
18
|
|
19
19
|
attr_reader :path, :data, :config_file, :inventory_file, :modulepath, :hiera_config,
|
20
|
-
:puppetfile, :rerunfile, :type, :resource_types, :
|
20
|
+
:puppetfile, :rerunfile, :type, :resource_types, :logs, :project_file,
|
21
21
|
:deprecations, :downloads, :plans_path
|
22
22
|
|
23
|
-
def self.default_project
|
24
|
-
create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
|
23
|
+
def self.default_project(logs = [])
|
24
|
+
create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user', logs)
|
25
25
|
# If homedir isn't defined use the system config path
|
26
26
|
rescue ArgumentError
|
27
|
-
create_project(Bolt::Config.system_path, 'system')
|
27
|
+
create_project(Bolt::Config.system_path, 'system', logs)
|
28
28
|
end
|
29
29
|
|
30
30
|
# Search recursively up the directory hierarchy for the Project. Look for a
|
31
31
|
# directory called Boltdir or a file called bolt.yaml (for a control repo
|
32
32
|
# type Project). Otherwise, repeat the check on each directory up the
|
33
33
|
# hierarchy, falling back to the default if we reach the root.
|
34
|
-
def self.find_boltdir(dir)
|
34
|
+
def self.find_boltdir(dir, logs = [])
|
35
35
|
dir = Pathname.new(dir)
|
36
36
|
|
37
37
|
if (dir + BOLTDIR_NAME).directory?
|
38
|
-
create_project(dir + BOLTDIR_NAME, 'embedded')
|
38
|
+
create_project(dir + BOLTDIR_NAME, 'embedded', logs)
|
39
39
|
elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
|
40
|
-
create_project(dir, 'local')
|
40
|
+
create_project(dir, 'local', logs)
|
41
41
|
elsif dir.root?
|
42
|
-
default_project
|
42
|
+
default_project(logs)
|
43
43
|
else
|
44
|
-
|
44
|
+
logs << { debug: "Did not detect Boltdir, bolt.yaml, or bolt-project.yaml at '#{dir}'. "\
|
45
|
+
"This directory won't be loaded as a project." }
|
46
|
+
find_boltdir(dir.parent, logs)
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
48
|
-
def self.create_project(path, type = 'option')
|
50
|
+
def self.create_project(path, type = 'option', logs = [])
|
49
51
|
fullpath = Pathname.new(path).expand_path
|
50
52
|
|
53
|
+
if type == 'user'
|
54
|
+
begin
|
55
|
+
# This is already expanded if the type is user
|
56
|
+
FileUtils.mkdir_p(path)
|
57
|
+
rescue StandardError
|
58
|
+
logs << { warn: "Could not create default project at #{path}. Continuing without a writeable project. "\
|
59
|
+
"Log and rerun files will not be written." }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if type == 'option' && !File.directory?(path)
|
64
|
+
raise Bolt::Error.new("Could not find project at #{path}", "bolt/project-error")
|
65
|
+
end
|
66
|
+
|
51
67
|
if !Bolt::Util.windows? && type != 'environment' && fullpath.world_writable?
|
52
68
|
raise Bolt::Error.new(
|
53
69
|
"Project directory '#{fullpath}' is world-writable which poses a security risk. Set "\
|
@@ -58,15 +74,18 @@ module Bolt
|
|
58
74
|
|
59
75
|
project_file = File.join(fullpath, 'bolt-project.yaml')
|
60
76
|
data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
|
61
|
-
|
77
|
+
default = type =~ /user|system/ ? 'default ' : ''
|
78
|
+
exist = File.exist?(File.expand_path(project_file))
|
79
|
+
logs << { info: "Loaded #{default}project from '#{fullpath}'" } if exist
|
80
|
+
new(data, path, type, logs)
|
62
81
|
end
|
63
82
|
|
64
|
-
def initialize(raw_data, path, type = 'option')
|
83
|
+
def initialize(raw_data, path, type = 'option', logs = [])
|
65
84
|
@path = Pathname.new(path).expand_path
|
66
85
|
|
67
86
|
@project_file = @path + 'bolt-project.yaml'
|
68
87
|
|
69
|
-
@
|
88
|
+
@logs = logs
|
70
89
|
@deprecations = []
|
71
90
|
if (@path + 'bolt.yaml').file? && project_file?
|
72
91
|
msg = "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
|
@@ -88,7 +107,7 @@ module Bolt
|
|
88
107
|
tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys
|
89
108
|
if tc.any?
|
90
109
|
msg = "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}"
|
91
|
-
@
|
110
|
+
@logs << { warn: msg }
|
92
111
|
end
|
93
112
|
|
94
113
|
@data = raw_data.reject { |k, _| Bolt::Config::INVENTORY_OPTIONS.include?(k) }
|
@@ -98,7 +117,7 @@ module Bolt
|
|
98
117
|
@config_file = if (Bolt::Config::BOLT_OPTIONS & @data.keys).any?
|
99
118
|
if (@path + 'bolt.yaml').file?
|
100
119
|
msg = "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
|
101
|
-
@
|
120
|
+
@logs << { warn: msg }
|
102
121
|
end
|
103
122
|
@project_file
|
104
123
|
else
|
@@ -114,7 +133,9 @@ module Bolt
|
|
114
133
|
# This API is used to prepend the project as a module to Puppet's internal
|
115
134
|
# module_references list. CHANGE AT YOUR OWN RISK
|
116
135
|
def to_h
|
117
|
-
{ path: @path.to_s,
|
136
|
+
{ path: @path.to_s,
|
137
|
+
name: name,
|
138
|
+
load_as_module?: load_as_module? }
|
118
139
|
end
|
119
140
|
|
120
141
|
def eql?(other)
|
@@ -126,6 +147,10 @@ module Bolt
|
|
126
147
|
@project_file.file?
|
127
148
|
end
|
128
149
|
|
150
|
+
def load_as_module?
|
151
|
+
!name.nil?
|
152
|
+
end
|
153
|
+
|
129
154
|
def name
|
130
155
|
@data['name']
|
131
156
|
end
|
@@ -138,6 +163,10 @@ module Bolt
|
|
138
163
|
@data['plans']
|
139
164
|
end
|
140
165
|
|
166
|
+
def modules
|
167
|
+
@data['modules']
|
168
|
+
end
|
169
|
+
|
141
170
|
def validate
|
142
171
|
if name
|
143
172
|
if name !~ Bolt::Module::MODULE_NAME_REGEX
|
@@ -151,7 +180,7 @@ module Bolt
|
|
151
180
|
end
|
152
181
|
else
|
153
182
|
message = "No project name is specified in bolt-project.yaml. Project-level content will not be available."
|
154
|
-
@
|
183
|
+
@logs << { warn: message }
|
155
184
|
end
|
156
185
|
|
157
186
|
%w[tasks plans].each do |conf|
|
@@ -159,6 +188,23 @@ module Bolt
|
|
159
188
|
raise Bolt::ValidationError, "'#{conf}' in bolt-project.yaml must be an array"
|
160
189
|
end
|
161
190
|
end
|
191
|
+
|
192
|
+
if @data['modules']
|
193
|
+
unless @data['modules'].is_a?(Array)
|
194
|
+
raise Bolt::ValidationError, "'modules' in bolt-project.yaml must be an array"
|
195
|
+
end
|
196
|
+
|
197
|
+
@data['modules'].each do |mod|
|
198
|
+
next if mod.is_a?(Hash)
|
199
|
+
raise Bolt::ValidationError, "Module declaration #{mod.inspect} must be a hash"
|
200
|
+
end
|
201
|
+
|
202
|
+
unknown_keys = data['modules'].flat_map(&:keys).uniq - ['name']
|
203
|
+
if unknown_keys.any?
|
204
|
+
@logs << { warn: "Module declarations in bolt-project.yaml only support a name key. Ignoring "\
|
205
|
+
"unsupported keys: #{unknown_keys.join(', ')}." }
|
206
|
+
end
|
207
|
+
end
|
162
208
|
end
|
163
209
|
|
164
210
|
def check_deprecated_file
|