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.

Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -0
  3. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +20 -9
  5. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -0
  6. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +123 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +1 -0
  8. data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +1 -0
  9. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +1 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +1 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +1 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +1 -0
  14. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
  15. data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +1 -0
  16. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +1 -0
  17. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +3 -0
  18. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -1
  19. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +7 -4
  20. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +2 -1
  21. data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +1 -0
  22. data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +1 -0
  23. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +1 -0
  24. data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +1 -0
  25. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -0
  26. data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +1 -0
  27. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +1 -0
  28. data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +1 -0
  29. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
  30. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
  31. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +2 -0
  32. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +1 -0
  33. data/bolt-modules/file/lib/puppet/functions/file/join.rb +2 -0
  34. data/bolt-modules/file/lib/puppet/functions/file/read.rb +2 -0
  35. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +2 -0
  36. data/bolt-modules/file/lib/puppet/functions/file/write.rb +2 -0
  37. data/bolt-modules/out/lib/puppet/functions/out/message.rb +2 -0
  38. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +1 -0
  39. data/bolt-modules/system/lib/puppet/functions/system/env.rb +2 -0
  40. data/lib/bolt/applicator.rb +21 -15
  41. data/lib/bolt/apply_result.rb +1 -1
  42. data/lib/bolt/bolt_option_parser.rb +55 -20
  43. data/lib/bolt/catalog.rb +3 -2
  44. data/lib/bolt/cli.rb +116 -47
  45. data/lib/bolt/config.rb +48 -148
  46. data/lib/bolt/config/options.rb +488 -0
  47. data/lib/bolt/config/transport/base.rb +16 -16
  48. data/lib/bolt/config/transport/docker.rb +9 -23
  49. data/lib/bolt/config/transport/local.rb +6 -44
  50. data/lib/bolt/config/transport/options.rb +460 -0
  51. data/lib/bolt/config/transport/orch.rb +9 -18
  52. data/lib/bolt/config/transport/remote.rb +3 -6
  53. data/lib/bolt/config/transport/ssh.rb +74 -154
  54. data/lib/bolt/config/transport/winrm.rb +18 -47
  55. data/lib/bolt/executor.rb +15 -0
  56. data/lib/bolt/inventory/group.rb +4 -3
  57. data/lib/bolt/inventory/inventory.rb +4 -17
  58. data/lib/bolt/inventory/target.rb +18 -5
  59. data/lib/bolt/logger.rb +24 -1
  60. data/lib/bolt/outputter.rb +1 -1
  61. data/lib/bolt/outputter/rainbow.rb +14 -3
  62. data/lib/bolt/pal.rb +31 -11
  63. data/lib/bolt/pal/yaml_plan/evaluator.rb +19 -2
  64. data/lib/bolt/pal/yaml_plan/step.rb +11 -2
  65. data/lib/bolt/pal/yaml_plan/step/download.rb +38 -0
  66. data/lib/bolt/pal/yaml_plan/step/upload.rb +3 -3
  67. data/lib/bolt/plugin/module.rb +2 -4
  68. data/lib/bolt/plugin/puppetdb.rb +3 -2
  69. data/lib/bolt/project.rb +41 -44
  70. data/lib/bolt/puppetdb/client.rb +2 -0
  71. data/lib/bolt/puppetdb/config.rb +16 -0
  72. data/lib/bolt/result.rb +7 -0
  73. data/lib/bolt/shell/bash.rb +53 -45
  74. data/lib/bolt/shell/powershell.rb +23 -12
  75. data/lib/bolt/shell/powershell/snippets.rb +15 -6
  76. data/lib/bolt/transport/base.rb +24 -0
  77. data/lib/bolt/transport/docker.rb +17 -5
  78. data/lib/bolt/transport/docker/connection.rb +20 -2
  79. data/lib/bolt/transport/local/connection.rb +14 -1
  80. data/lib/bolt/transport/orch.rb +20 -0
  81. data/lib/bolt/transport/simple.rb +6 -0
  82. data/lib/bolt/transport/ssh.rb +7 -1
  83. data/lib/bolt/transport/ssh/connection.rb +9 -1
  84. data/lib/bolt/transport/ssh/exec_connection.rb +23 -2
  85. data/lib/bolt/transport/winrm/connection.rb +109 -8
  86. data/lib/bolt/util.rb +26 -11
  87. data/lib/bolt/version.rb +1 -1
  88. data/lib/bolt_server/transport_app.rb +3 -2
  89. data/lib/bolt_spec/bolt_context.rb +7 -2
  90. data/lib/bolt_spec/plans.rb +15 -2
  91. data/lib/bolt_spec/plans/action_stubs.rb +2 -1
  92. data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
  93. data/lib/bolt_spec/plans/mock_executor.rb +14 -1
  94. data/lib/bolt_spec/run.rb +22 -0
  95. data/libexec/bolt_catalog +3 -2
  96. 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
 
@@ -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
- !Logging.level_num(level).nil?
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
@@ -26,5 +26,5 @@ end
26
26
 
27
27
  require 'bolt/outputter/human'
28
28
  require 'bolt/outputter/json'
29
- require 'bolt/outputter/rainbow'
30
29
  require 'bolt/outputter/logger'
30
+ require 'bolt/outputter/rainbow'
@@ -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
- if c == "\e"
42
+ case c
43
+ when "\e"
33
44
  @state = :ansi
34
- elsif c == "\n"
45
+ when "\n"
35
46
  @line_color += 1
36
47
  @color = @line_color
37
48
  c
@@ -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.cause || 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
- e = new(err.message)
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
- Puppet.override(bolt_project: @project,
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
- "unless explicitly defined. (file: #{e.file}, line: #{e.line}, column: #{e.pos})"
161
- PALError.new(message)
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
- if type == 'task'
287
+ case type
288
+ when 'task'
269
289
  param_spec = compiler.task_signature(object_name)&.task_hash&.dig('parameters')
270
- elsif type == 'plan'
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
- @logger.warn(msg)
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 type_keys.to_set == Set['source', 'destination']
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['destination', 'source', 'targets']
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
 
@@ -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 (params.key?('private-key') || params.key?('public-key')) && !@deprecation_warning_issued
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
- Logging.logger[self].warn(message)
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')
@@ -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
- if value.is_a?(String)
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
- elsif value.is_a?(Array) || value.is_a?(Hash)
98
+ when Array, Hash
98
99
  value
99
100
  else
100
101
  raise FactLookupError.new(value, "fact lookups must be a string")
@@ -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 whitelist. Whitelisted plans are included in `bolt plan show` output",
13
- "tasks" => "An array of task names to whitelist. Whitelisted plans are included in `bolt task show` output"
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
- @warnings << { msg: msg }
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::INVENTORY_CONFIG.keys & raw_data.keys
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::INVENTORY_CONFIG.keys.include?(k) }
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::OPTIONS.keys & @data.keys).any?
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
- # If the project is in mymod/Boltdir/bolt-project.yaml, use mymod as the project name
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
- n = @data['name']
138
- if n && !project_directory_name?(n) && !project_namespaced_name?(n)
139
- raise Bolt::ValidationError, <<~ERROR_STRING
140
- Invalid project name '#{n}' in bolt-project.yaml; project names must match either:
141
- An installed project name (ex. projectname) matching the expression /^[a-z][a-z0-9_]*$/ -or-
142
- A namespaced project name (ex. author-projectname) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
143
- ERROR_STRING
144
- elsif !project_directory_name?(name) && !project_namespaced_name?(name)
145
- raise Bolt::ValidationError, <<~ERROR_STRING
146
- Invalid project name '#{name}'; project names must match either:
147
- A project name (ex. projectname) matching the expression /^[a-z][a-z0-9_]*$/ -or-
148
- A namespaced project name (ex. author-projectname) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
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
- logger = Logging.logger[self]
168
- logger.warn "Project configuration file 'project.yaml' is deprecated; use 'bolt-project.yaml' instead."
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