bolt 2.15.0 → 2.20.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/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +20 -9
- data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +123 -0
- data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +3 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +7 -4
- data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +1 -0
- data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
- data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +2 -0
- data/bolt-modules/file/lib/puppet/functions/file/exists.rb +1 -0
- data/bolt-modules/file/lib/puppet/functions/file/join.rb +2 -0
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +2 -0
- data/bolt-modules/file/lib/puppet/functions/file/readable.rb +2 -0
- data/bolt-modules/file/lib/puppet/functions/file/write.rb +2 -0
- data/bolt-modules/out/lib/puppet/functions/out/message.rb +2 -0
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +1 -0
- data/bolt-modules/system/lib/puppet/functions/system/env.rb +2 -0
- data/lib/bolt/applicator.rb +21 -15
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +55 -20
- data/lib/bolt/catalog.rb +3 -2
- data/lib/bolt/cli.rb +116 -47
- data/lib/bolt/config.rb +48 -148
- data/lib/bolt/config/options.rb +488 -0
- data/lib/bolt/config/transport/base.rb +16 -16
- data/lib/bolt/config/transport/docker.rb +9 -23
- data/lib/bolt/config/transport/local.rb +6 -44
- data/lib/bolt/config/transport/options.rb +460 -0
- data/lib/bolt/config/transport/orch.rb +9 -18
- data/lib/bolt/config/transport/remote.rb +3 -6
- data/lib/bolt/config/transport/ssh.rb +74 -154
- data/lib/bolt/config/transport/winrm.rb +18 -47
- data/lib/bolt/executor.rb +15 -0
- data/lib/bolt/inventory/group.rb +4 -3
- data/lib/bolt/inventory/inventory.rb +4 -17
- data/lib/bolt/inventory/target.rb +18 -5
- data/lib/bolt/logger.rb +24 -1
- data/lib/bolt/outputter.rb +1 -1
- data/lib/bolt/outputter/rainbow.rb +14 -3
- data/lib/bolt/pal.rb +31 -11
- data/lib/bolt/pal/yaml_plan/evaluator.rb +19 -2
- data/lib/bolt/pal/yaml_plan/step.rb +11 -2
- data/lib/bolt/pal/yaml_plan/step/download.rb +38 -0
- data/lib/bolt/pal/yaml_plan/step/upload.rb +3 -3
- data/lib/bolt/plugin/module.rb +2 -4
- data/lib/bolt/plugin/puppetdb.rb +3 -2
- data/lib/bolt/project.rb +41 -44
- data/lib/bolt/puppetdb/client.rb +2 -0
- data/lib/bolt/puppetdb/config.rb +16 -0
- data/lib/bolt/result.rb +7 -0
- data/lib/bolt/shell/bash.rb +53 -45
- data/lib/bolt/shell/powershell.rb +23 -12
- data/lib/bolt/shell/powershell/snippets.rb +15 -6
- data/lib/bolt/transport/base.rb +24 -0
- data/lib/bolt/transport/docker.rb +17 -5
- data/lib/bolt/transport/docker/connection.rb +20 -2
- data/lib/bolt/transport/local/connection.rb +14 -1
- data/lib/bolt/transport/orch.rb +20 -0
- data/lib/bolt/transport/simple.rb +6 -0
- data/lib/bolt/transport/ssh.rb +7 -1
- data/lib/bolt/transport/ssh/connection.rb +9 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +23 -2
- data/lib/bolt/transport/winrm/connection.rb +109 -8
- data/lib/bolt/util.rb +26 -11
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +3 -2
- data/lib/bolt_spec/bolt_context.rb +7 -2
- data/lib/bolt_spec/plans.rb +15 -2
- data/lib/bolt_spec/plans/action_stubs.rb +2 -1
- data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
- data/lib/bolt_spec/plans/mock_executor.rb +14 -1
- data/lib/bolt_spec/run.rb +22 -0
- data/libexec/bolt_catalog +3 -2
- metadata +20 -29
@@ -30,6 +30,10 @@ module Bolt
|
|
30
30
|
@safe_name = @uri_obj.omit(:password).to_str.sub(%r{^//}, '')
|
31
31
|
end
|
32
32
|
|
33
|
+
if @name == 'localhost'
|
34
|
+
target_data = localhost_defaults(target_data)
|
35
|
+
end
|
36
|
+
|
33
37
|
@config = target_data['config'] || {}
|
34
38
|
@vars = target_data['vars'] || {}
|
35
39
|
@facts = target_data['facts'] || {}
|
@@ -45,6 +49,20 @@ module Bolt
|
|
45
49
|
validate
|
46
50
|
end
|
47
51
|
|
52
|
+
def localhost_defaults(data)
|
53
|
+
defaults = {
|
54
|
+
'config' => {
|
55
|
+
'transport' => 'local',
|
56
|
+
'local' => { 'interpreters' => { '.rb' => RbConfig.ruby } }
|
57
|
+
},
|
58
|
+
'features' => ['puppet-agent']
|
59
|
+
}
|
60
|
+
data = Bolt::Util.deep_merge(defaults, data)
|
61
|
+
# If features is an empty array deep_merge won't add the puppet-agent
|
62
|
+
data['features'] += ['puppet-agent'] if data['features'].empty?
|
63
|
+
data
|
64
|
+
end
|
65
|
+
|
48
66
|
# rubocop:disable Naming/AccessorMethodName
|
49
67
|
def set_resource(resource)
|
50
68
|
if (existing_resource = resources[resource.reference])
|
@@ -218,11 +236,6 @@ module Bolt
|
|
218
236
|
'target_alias' => []
|
219
237
|
}
|
220
238
|
|
221
|
-
# This should be handled by `get_targets`
|
222
|
-
if @name == 'localhost'
|
223
|
-
group_data = Bolt::Inventory::Inventory.localhost_defaults(group_data)
|
224
|
-
end
|
225
|
-
|
226
239
|
@group_cache = group_data
|
227
240
|
end
|
228
241
|
|
data/lib/bolt/logger.rb
CHANGED
@@ -15,6 +15,7 @@ module Bolt
|
|
15
15
|
return if Logging.initialized?
|
16
16
|
|
17
17
|
Logging.init :debug, :info, :notice, :warn, :error, :fatal, :any
|
18
|
+
@mutex = Mutex.new
|
18
19
|
|
19
20
|
Logging.color_scheme(
|
20
21
|
'bolt',
|
@@ -66,6 +67,10 @@ module Bolt
|
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
70
|
+
def self.analytics=(analytics)
|
71
|
+
@analytics = analytics
|
72
|
+
end
|
73
|
+
|
69
74
|
def self.console_layout(color)
|
70
75
|
color_scheme = :bolt if color
|
71
76
|
Logging.layouts.pattern(
|
@@ -89,8 +94,10 @@ module Bolt
|
|
89
94
|
:notice
|
90
95
|
end
|
91
96
|
|
97
|
+
# Explicitly check the log level names instead of the log level number, as levels
|
98
|
+
# that are stringified integers (e.g. "level" => "42") will return a truthy value
|
92
99
|
def self.valid_level?(level)
|
93
|
-
|
100
|
+
Logging::LEVELS.include?(Logging.levelify(level))
|
94
101
|
end
|
95
102
|
|
96
103
|
def self.levels
|
@@ -100,5 +107,21 @@ module Bolt
|
|
100
107
|
def self.reset_logging
|
101
108
|
Logging.reset
|
102
109
|
end
|
110
|
+
|
111
|
+
def self.warn_once(type, msg)
|
112
|
+
@mutex.synchronize {
|
113
|
+
@warnings ||= []
|
114
|
+
@logger ||= Logging.logger[self]
|
115
|
+
unless @warnings.include?(type)
|
116
|
+
@logger.warn(msg)
|
117
|
+
@warnings << type
|
118
|
+
end
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.deprecation_warning(type, msg)
|
123
|
+
@analytics&.event('Warn', 'deprecation', label: type)
|
124
|
+
warn_once(type, msg)
|
125
|
+
end
|
103
126
|
end
|
104
127
|
end
|
data/lib/bolt/outputter.rb
CHANGED
@@ -1,12 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'bolt/pal'
|
4
|
-
require 'paint'
|
5
4
|
|
6
5
|
module Bolt
|
7
6
|
class Outputter
|
8
7
|
class Rainbow < Bolt::Outputter::Human
|
9
8
|
def initialize(color, verbose, trace, stream = $stdout)
|
9
|
+
begin
|
10
|
+
require 'paint'
|
11
|
+
if Bolt::Util.windows?
|
12
|
+
# the Paint gem thinks that windows does not support ansi colors
|
13
|
+
# but windows 10 or later does
|
14
|
+
# we can display colors if we force mode to TRUE_COLOR
|
15
|
+
Paint.mode = 0xFFFFFF
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
raise "The 'paint' gem is required to use the rainbow outputter."
|
19
|
+
end
|
10
20
|
super
|
11
21
|
@line_color = 0
|
12
22
|
@color = 0
|
@@ -29,9 +39,10 @@ module Bolt
|
|
29
39
|
a = string.chars.map do |c|
|
30
40
|
case @state
|
31
41
|
when :normal
|
32
|
-
|
42
|
+
case c
|
43
|
+
when "\e"
|
33
44
|
@state = :ansi
|
34
|
-
|
45
|
+
when "\n"
|
35
46
|
@line_color += 1
|
36
47
|
@color = @line_color
|
37
48
|
c
|
data/lib/bolt/pal.rb
CHANGED
@@ -15,25 +15,36 @@ module Bolt
|
|
15
15
|
# PALError is used to convert errors from executing puppet code into
|
16
16
|
# Bolt::Errors
|
17
17
|
class PALError < Bolt::Error
|
18
|
-
# Puppet sometimes rescues exceptions notes the location and reraises.
|
19
|
-
# Return the original error.
|
20
18
|
def self.from_preformatted_error(err)
|
21
19
|
if err.cause&.is_a? Bolt::Error
|
22
20
|
err.cause
|
23
21
|
else
|
24
|
-
from_error(err
|
22
|
+
from_error(err)
|
25
23
|
end
|
26
24
|
end
|
27
25
|
|
28
26
|
# Generate a Bolt::Pal::PALError for non-bolt errors
|
29
27
|
def self.from_error(err)
|
30
|
-
|
28
|
+
# Use the original error message if available
|
29
|
+
message = err.cause ? err.cause.message : err.message
|
30
|
+
|
31
|
+
# Provide the location of an error if it came from a plan
|
32
|
+
details = if defined?(err.file) && err.file
|
33
|
+
{ file: err.file,
|
34
|
+
line: err.line,
|
35
|
+
column: err.pos }.compact
|
36
|
+
else
|
37
|
+
{}
|
38
|
+
end
|
39
|
+
|
40
|
+
e = new(message, details)
|
41
|
+
|
31
42
|
e.set_backtrace(err.backtrace)
|
32
43
|
e
|
33
44
|
end
|
34
45
|
|
35
|
-
def initialize(msg)
|
36
|
-
super(msg, 'bolt/pal-error')
|
46
|
+
def initialize(msg, details = {})
|
47
|
+
super(msg, 'bolt/pal-error', details)
|
37
48
|
end
|
38
49
|
end
|
39
50
|
|
@@ -137,7 +148,14 @@ module Bolt
|
|
137
148
|
# TODO: If we always call this inside a bolt_executor we can remove this here
|
138
149
|
setup
|
139
150
|
r = Puppet::Pal.in_tmp_environment('bolt', modulepath: @modulepath, facts: {}) do |pal|
|
140
|
-
|
151
|
+
# Only load the project if it a) exists, b) has a name it can be loaded with
|
152
|
+
bolt_project = @project if @project&.name
|
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,
|
141
159
|
yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
|
142
160
|
pal.with_script_compiler do |compiler|
|
143
161
|
alias_types(compiler)
|
@@ -157,8 +175,9 @@ module Bolt
|
|
157
175
|
if e.issue_code == :UNKNOWN_VARIABLE &&
|
158
176
|
%w[facts trusted server_facts settings].include?(e.arguments[:name])
|
159
177
|
message = "Evaluation Error: Variable '#{e.arguments[:name]}' is not available in the current scope "\
|
160
|
-
|
161
|
-
|
178
|
+
"unless explicitly defined."
|
179
|
+
details = { file: e.file, line: e.line, column: e.pos }
|
180
|
+
PALError.new(message, details)
|
162
181
|
else
|
163
182
|
PALError.from_preformatted_error(e)
|
164
183
|
end
|
@@ -265,9 +284,10 @@ module Bolt
|
|
265
284
|
|
266
285
|
def parse_params(type, object_name, params)
|
267
286
|
in_bolt_compiler do |compiler|
|
268
|
-
|
287
|
+
case type
|
288
|
+
when 'task'
|
269
289
|
param_spec = compiler.task_signature(object_name)&.task_hash&.dig('parameters')
|
270
|
-
|
290
|
+
when 'plan'
|
271
291
|
plan = compiler.plan_signature(object_name)
|
272
292
|
param_spec = plan.params_type.elements&.each_with_object({}) { |t, h| h[t.name] = t.value_type } if plan
|
273
293
|
end
|
@@ -73,7 +73,7 @@ module Bolt
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def upload_step(scope, step)
|
76
|
-
source = step['source']
|
76
|
+
source = step['upload'] || step['source']
|
77
77
|
destination = step['destination']
|
78
78
|
targets = step['targets'] || step['target']
|
79
79
|
description = step['description']
|
@@ -83,6 +83,17 @@ module Bolt
|
|
83
83
|
scope.call_function('upload_file', args)
|
84
84
|
end
|
85
85
|
|
86
|
+
def download_step(scope, step)
|
87
|
+
source = step['download']
|
88
|
+
destination = step['destination']
|
89
|
+
targets = step['targets'] || step['target']
|
90
|
+
description = step['description']
|
91
|
+
|
92
|
+
args = [source, destination, targets]
|
93
|
+
args << description if description
|
94
|
+
scope.call_function('download_file', args)
|
95
|
+
end
|
96
|
+
|
86
97
|
def eval_step(_scope, step)
|
87
98
|
step['eval']
|
88
99
|
end
|
@@ -142,7 +153,13 @@ module Bolt
|
|
142
153
|
if plan.steps.any? { |step| step.body.key?('target') }
|
143
154
|
msg = "The 'target' parameter for YAML plan steps is deprecated and will be removed "\
|
144
155
|
"in a future version of Bolt. Use the 'targets' parameter instead."
|
145
|
-
|
156
|
+
Bolt::Logger.deprecation_warning("Using 'target' parameter for YAML plan steps, not 'targets'", msg)
|
157
|
+
end
|
158
|
+
|
159
|
+
if plan.steps.any? { |step| step.body.key?('source') }
|
160
|
+
msg = "The 'source' parameter for YAML plan upload steps is deprecated and will be removed "\
|
161
|
+
"in a future version of Bolt. Use the 'upload' parameter instead."
|
162
|
+
Bolt::Logger.deprecation_warning("Using 'source' parameter for YAML upload steps, not 'upload'", msg)
|
146
163
|
end
|
147
164
|
|
148
165
|
plan_result = closure_scope.with_local_scope(args_hash) do |scope|
|
@@ -12,7 +12,7 @@ module Bolt
|
|
12
12
|
Set['name', 'description', 'target', 'targets']
|
13
13
|
end
|
14
14
|
|
15
|
-
STEP_KEYS = %w[command script task plan source destination eval resources].freeze
|
15
|
+
STEP_KEYS = %w[command script task plan source destination eval resources upload download].freeze
|
16
16
|
|
17
17
|
def self.create(step_body, step_number)
|
18
18
|
type_keys = (STEP_KEYS & step_body.keys)
|
@@ -22,8 +22,10 @@ module Bolt
|
|
22
22
|
when 1
|
23
23
|
type = type_keys.first
|
24
24
|
else
|
25
|
-
if
|
25
|
+
if [Set['source', 'destination'], Set['upload', 'destination']].include?(type_keys.to_set)
|
26
26
|
type = 'upload'
|
27
|
+
elsif type_keys.to_set == Set['download', 'destination']
|
28
|
+
type = 'download'
|
27
29
|
else
|
28
30
|
raise step_error("Multiple action keys detected: #{type_keys.inspect}", step_body['name'], step_number)
|
29
31
|
end
|
@@ -89,6 +91,12 @@ module Bolt
|
|
89
91
|
missing_keys -= ['targets']
|
90
92
|
end
|
91
93
|
|
94
|
+
# Handle cases where upload step uses deprecated 'source' key instead of 'upload'
|
95
|
+
# TODO: Remove when 'source' is removed
|
96
|
+
if body.include?('source')
|
97
|
+
missing_keys -= ['upload']
|
98
|
+
end
|
99
|
+
|
92
100
|
if missing_keys.any?
|
93
101
|
error_message = "The #{step_type.inspect} step requires: #{missing_keys.to_a.inspect} key(s)"
|
94
102
|
err = step_error(error_message, body['name'], step_number)
|
@@ -156,3 +164,4 @@ require 'bolt/pal/yaml_plan/step/resources'
|
|
156
164
|
require 'bolt/pal/yaml_plan/step/script'
|
157
165
|
require 'bolt/pal/yaml_plan/step/task'
|
158
166
|
require 'bolt/pal/yaml_plan/step/upload'
|
167
|
+
require 'bolt/pal/yaml_plan/step/download'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bolt
|
4
|
+
class PAL
|
5
|
+
class YamlPlan
|
6
|
+
class Step
|
7
|
+
class Download < Step
|
8
|
+
def self.allowed_keys
|
9
|
+
super + Set['download', 'destination']
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.required_keys
|
13
|
+
Set['download', 'destination', 'targets']
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(step_body)
|
17
|
+
super
|
18
|
+
@source = step_body['download']
|
19
|
+
@destination = step_body['destination']
|
20
|
+
end
|
21
|
+
|
22
|
+
def transpile
|
23
|
+
code = String.new(" ")
|
24
|
+
code << "$#{@name} = " if @name
|
25
|
+
|
26
|
+
fn = 'download_file'
|
27
|
+
args = [@source, @destination, @targets]
|
28
|
+
args << @description if @description
|
29
|
+
|
30
|
+
code << function_call(fn, args)
|
31
|
+
|
32
|
+
code << "\n"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -6,16 +6,16 @@ module Bolt
|
|
6
6
|
class Step
|
7
7
|
class Upload < Step
|
8
8
|
def self.allowed_keys
|
9
|
-
super + Set['source', 'destination']
|
9
|
+
super + Set['source', 'destination', 'upload']
|
10
10
|
end
|
11
11
|
|
12
12
|
def self.required_keys
|
13
|
-
Set['
|
13
|
+
Set['upload', 'destination', 'targets']
|
14
14
|
end
|
15
15
|
|
16
16
|
def initialize(step_body)
|
17
17
|
super
|
18
|
-
@source = step_body['source']
|
18
|
+
@source = step_body['upload'] || step_body['source']
|
19
19
|
@destination = step_body['destination']
|
20
20
|
end
|
21
21
|
|
data/lib/bolt/plugin/module.rb
CHANGED
@@ -184,12 +184,10 @@ module Bolt
|
|
184
184
|
# Raises a deprecation warning if the pkcs7 plugin is using deprecated keys and
|
185
185
|
# modifies the keys so they are the correct format
|
186
186
|
def handle_deprecated_pkcs7_keys(params)
|
187
|
-
if
|
188
|
-
@deprecation_warning_issued = true
|
189
|
-
|
187
|
+
if params.key?('private-key') || params.key?('public-key')
|
190
188
|
message = "pkcs7 keys 'private-key' and 'public-key' have been deprecated and will be "\
|
191
189
|
"removed in a future version of Bolt; use 'private_key' and 'public_key' instead."
|
192
|
-
|
190
|
+
Bolt::Logger.deprecation_warning('PKCS7 keys using hyphens, not underscores', message)
|
193
191
|
end
|
194
192
|
|
195
193
|
params['private_key'] = params.delete('private-key') if params.key?('private-key')
|
data/lib/bolt/plugin/puppetdb.rb
CHANGED
@@ -85,7 +85,8 @@ module Bolt
|
|
85
85
|
|
86
86
|
def resolve_facts(config, certname, target_data)
|
87
87
|
Bolt::Util.walk_vals(config) do |value|
|
88
|
-
|
88
|
+
case value
|
89
|
+
when String
|
89
90
|
if value == 'certname'
|
90
91
|
certname
|
91
92
|
else
|
@@ -94,7 +95,7 @@ module Bolt
|
|
94
95
|
# If there's no fact data this will be nil
|
95
96
|
data&.fetch('value', nil)
|
96
97
|
end
|
97
|
-
|
98
|
+
when Array, Hash
|
98
99
|
value
|
99
100
|
else
|
100
101
|
raise FactLookupError.new(value, "fact lookups must be a string")
|
data/lib/bolt/project.rb
CHANGED
@@ -1,20 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'pathname'
|
4
|
-
require 'bolt/pal'
|
5
4
|
require 'bolt/config'
|
5
|
+
require 'bolt/pal'
|
6
6
|
|
7
7
|
module Bolt
|
8
8
|
class Project
|
9
9
|
BOLTDIR_NAME = 'Boltdir'
|
10
10
|
PROJECT_SETTINGS = {
|
11
11
|
"name" => "The name of the project",
|
12
|
-
"plans" => "An array of plan names to
|
13
|
-
|
12
|
+
"plans" => "An array of plan names to show, if they exist in the project."\
|
13
|
+
"These plans are included in `bolt plan show` output",
|
14
|
+
"tasks" => "An array of task names to show, if they exist in the project."\
|
15
|
+
"These tasks are included in `bolt task show` output"
|
14
16
|
}.freeze
|
15
17
|
|
16
18
|
attr_reader :path, :data, :config_file, :inventory_file, :modulepath, :hiera_config,
|
17
|
-
:puppetfile, :rerunfile, :type, :resource_types, :warnings, :project_file
|
19
|
+
:puppetfile, :rerunfile, :type, :resource_types, :warnings, :project_file,
|
20
|
+
:deprecations, :downloads
|
18
21
|
|
19
22
|
def self.default_project
|
20
23
|
create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
|
@@ -29,6 +32,7 @@ module Bolt
|
|
29
32
|
# hierarchy, falling back to the default if we reach the root.
|
30
33
|
def self.find_boltdir(dir)
|
31
34
|
dir = Pathname.new(dir)
|
35
|
+
|
32
36
|
if (dir + BOLTDIR_NAME).directory?
|
33
37
|
create_project(dir + BOLTDIR_NAME, 'embedded')
|
34
38
|
elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
|
@@ -42,6 +46,15 @@ module Bolt
|
|
42
46
|
|
43
47
|
def self.create_project(path, type = 'option')
|
44
48
|
fullpath = Pathname.new(path).expand_path
|
49
|
+
|
50
|
+
if !Bolt::Util.windows? && type != 'environment' && fullpath.world_writable?
|
51
|
+
raise Bolt::Error.new(
|
52
|
+
"Project directory '#{fullpath}' is world-writable which poses a security risk. Set "\
|
53
|
+
"BOLT_PROJECT='#{fullpath}' to force the use of this project directory.",
|
54
|
+
"bolt/world-writable-error"
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
45
58
|
project_file = File.join(fullpath, 'bolt-project.yaml')
|
46
59
|
data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
|
47
60
|
new(data, path, type)
|
@@ -49,14 +62,16 @@ module Bolt
|
|
49
62
|
|
50
63
|
def initialize(raw_data, path, type = 'option')
|
51
64
|
@path = Pathname.new(path).expand_path
|
65
|
+
|
52
66
|
@project_file = @path + 'bolt-project.yaml'
|
53
67
|
|
54
68
|
@warnings = []
|
69
|
+
@deprecations = []
|
55
70
|
if (@path + 'bolt.yaml').file? && project_file?
|
56
71
|
msg = "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
|
57
72
|
"Transport config should be set in inventory.yaml, all other config should be set in "\
|
58
73
|
"bolt-project.yaml."
|
59
|
-
@
|
74
|
+
@deprecations << { type: 'Using bolt.yaml for project configuration', msg: msg }
|
60
75
|
end
|
61
76
|
|
62
77
|
@inventory_file = @path + 'inventory.yaml'
|
@@ -66,18 +81,19 @@ module Bolt
|
|
66
81
|
@rerunfile = @path + '.rerun.json'
|
67
82
|
@resource_types = @path + '.resource_types'
|
68
83
|
@type = type
|
84
|
+
@downloads = @path + 'downloads'
|
69
85
|
|
70
|
-
tc = Bolt::Config::
|
86
|
+
tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys
|
71
87
|
if tc.any?
|
72
88
|
msg = "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}"
|
73
89
|
@warnings << { msg: msg }
|
74
90
|
end
|
75
91
|
|
76
|
-
@data = raw_data.reject { |k, _| Bolt::Config::
|
92
|
+
@data = raw_data.reject { |k, _| Bolt::Config::INVENTORY_OPTIONS.include?(k) }
|
77
93
|
|
78
94
|
# Once bolt.yaml deprecation is removed, this attribute should be removed
|
79
95
|
# and replaced with .project_file in lib/bolt/config.rb
|
80
|
-
@config_file = if (Bolt::Config::
|
96
|
+
@config_file = if (Bolt::Config::BOLT_OPTIONS & @data.keys).any?
|
81
97
|
if (@path + 'bolt.yaml').file?
|
82
98
|
msg = "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
|
83
99
|
@warnings << { msg: msg }
|
@@ -96,7 +112,7 @@ module Bolt
|
|
96
112
|
# This API is used to prepend the project as a module to Puppet's internal
|
97
113
|
# module_references list. CHANGE AT YOUR OWN RISK
|
98
114
|
def to_h
|
99
|
-
{ path: @path, name: name }
|
115
|
+
{ path: @path.to_s, name: name }
|
100
116
|
end
|
101
117
|
|
102
118
|
def eql?(other)
|
@@ -109,10 +125,7 @@ module Bolt
|
|
109
125
|
end
|
110
126
|
|
111
127
|
def name
|
112
|
-
|
113
|
-
dirname = @path.basename.to_s == 'Boltdir' ? @path.parent.basename.to_s : @path.basename.to_s
|
114
|
-
pname = @data['name'] || dirname
|
115
|
-
pname.include?('-') ? pname.split('-', 2)[1] : pname
|
128
|
+
@data['name']
|
116
129
|
end
|
117
130
|
|
118
131
|
def tasks
|
@@ -123,36 +136,20 @@ module Bolt
|
|
123
136
|
@data['plans']
|
124
137
|
end
|
125
138
|
|
126
|
-
def project_directory_name?(name)
|
127
|
-
# it must match an installed project name according to forge validator
|
128
|
-
name =~ /^[a-z][a-z0-9_]*$/
|
129
|
-
end
|
130
|
-
|
131
|
-
def project_namespaced_name?(name)
|
132
|
-
# it must match the full project name according to forge validator
|
133
|
-
name =~ /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
|
134
|
-
end
|
135
|
-
|
136
139
|
def validate
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
Configure project name in <project_dir>/bolt-project.yaml
|
151
|
-
ERROR_STRING
|
152
|
-
# If the project name is the same as one of the built-in modules raise a warning
|
153
|
-
elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
|
154
|
-
raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
|
155
|
-
"with a built-in Bolt module of the same name."
|
140
|
+
if name
|
141
|
+
name_regex = /^[a-z][a-z0-9_]*$/
|
142
|
+
if name !~ name_regex
|
143
|
+
raise Bolt::ValidationError, <<~ERROR_STRING
|
144
|
+
Invalid project name '#{name}' in bolt-project.yaml; project name must match #{name_regex.inspect}
|
145
|
+
ERROR_STRING
|
146
|
+
elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
|
147
|
+
raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
|
148
|
+
"with a built-in Bolt module of the same name."
|
149
|
+
end
|
150
|
+
else
|
151
|
+
message = "No project name is specified in bolt-project.yaml. Project-level content will not be available."
|
152
|
+
@warnings << { msg: message }
|
156
153
|
end
|
157
154
|
|
158
155
|
%w[tasks plans].each do |conf|
|
@@ -164,8 +161,8 @@ module Bolt
|
|
164
161
|
|
165
162
|
def check_deprecated_file
|
166
163
|
if (@path + 'project.yaml').file?
|
167
|
-
|
168
|
-
|
164
|
+
msg = "Project configuration file 'project.yaml' is deprecated; use 'bolt-project.yaml' instead."
|
165
|
+
Bolt::Logger.deprecation_warning('Using project.yaml instead of bolt-project.yaml', msg)
|
169
166
|
end
|
170
167
|
end
|
171
168
|
end
|