bolt 2.44.0 → 3.0.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.

@@ -17,6 +17,7 @@ module Bolt
17
17
  OPTIONS = WINDOWS_OPTIONS.dup.concat(RUN_AS_OPTIONS).sort.freeze
18
18
 
19
19
  DEFAULTS = {
20
+ 'bundled-ruby' => true,
20
21
  'cleanup' => true
21
22
  }.freeze
22
23
 
@@ -21,7 +21,7 @@ module Bolt
21
21
  type: [TrueClass, FalseClass],
22
22
  _plugin: false,
23
23
  _example: true,
24
- _default: false
24
+ _default: true
25
25
  },
26
26
  "cacert" => {
27
27
  type: String,
data/lib/bolt/logger.rb CHANGED
@@ -4,7 +4,7 @@ require 'logging'
4
4
 
5
5
  module Bolt
6
6
  module Logger
7
- LEVELS = %w[trace debug info notice warn error fatal].freeze
7
+ LEVELS = %w[trace debug info warn error fatal].freeze
8
8
 
9
9
  # This module is treated as a global singleton so that multiple classes
10
10
  # in Bolt can log warnings with IDs. Access to the following variables
@@ -392,7 +392,7 @@ module Bolt
392
392
 
393
393
  def print_target_info(targets)
394
394
  @stream.puts ::JSON.pretty_generate(
395
- "targets": targets.map(&:detail)
395
+ targets: targets.map(&:detail)
396
396
  )
397
397
  count = "#{targets.count} target#{'s' unless targets.count == 1}"
398
398
  @stream.puts colorize(:green, count)
@@ -95,38 +95,38 @@ module Bolt
95
95
  end
96
96
 
97
97
  def print_puppetfile_result(success, puppetfile, moduledir)
98
- @stream.puts({ "success": success,
99
- "puppetfile": puppetfile,
100
- "moduledir": moduledir.to_s }.to_json)
98
+ @stream.puts({ success: success,
99
+ puppetfile: puppetfile,
100
+ moduledir: moduledir.to_s }.to_json)
101
101
  end
102
102
 
103
103
  def print_targets(target_list, inventoryfile)
104
104
  @stream.puts ::JSON.pretty_generate(
105
- "inventory": {
106
- "targets": target_list[:inventory].map(&:name),
107
- "count": target_list[:inventory].count,
108
- "file": inventoryfile.to_s
105
+ inventory: {
106
+ targets: target_list[:inventory].map(&:name),
107
+ count: target_list[:inventory].count,
108
+ file: inventoryfile.to_s
109
109
  },
110
- "adhoc": {
111
- "targets": target_list[:adhoc].map(&:name),
112
- "count": target_list[:adhoc].count
110
+ adhoc: {
111
+ targets: target_list[:adhoc].map(&:name),
112
+ count: target_list[:adhoc].count
113
113
  },
114
- "targets": target_list.values.flatten.map(&:name),
115
- "count": target_list.values.flatten.count
114
+ targets: target_list.values.flatten.map(&:name),
115
+ count: target_list.values.flatten.count
116
116
  )
117
117
  end
118
118
 
119
119
  def print_target_info(targets)
120
120
  @stream.puts ::JSON.pretty_generate(
121
- "targets": targets.map(&:detail),
122
- "count": targets.count
121
+ targets: targets.map(&:detail),
122
+ count: targets.count
123
123
  )
124
124
  end
125
125
 
126
126
  def print_groups(groups)
127
127
  count = groups.count
128
- @stream.puts({ "groups": groups,
129
- "count": count }.to_json)
128
+ @stream.puts({ groups: groups,
129
+ count: count }.to_json)
130
130
  end
131
131
 
132
132
  def fatal_error(err)
data/lib/bolt/pal.rb CHANGED
@@ -449,7 +449,7 @@ module Bolt
449
449
  else
450
450
  Bolt::Logger.warn(
451
451
  "missing_plan_parameter",
452
- "The documented parameter '#{name}' does not exist in plan signature"
452
+ "The documented parameter '#{name}' does not exist in signature for plan '#{plan.name}'"
453
453
  )
454
454
  end
455
455
  end
@@ -24,7 +24,7 @@ module Bolt
24
24
 
25
25
  def task_step(scope, step)
26
26
  task = step['task']
27
- targets = step['targets'] || step['target']
27
+ targets = step['targets']
28
28
  description = step['description']
29
29
  params = step['parameters'] || {}
30
30
 
@@ -48,7 +48,7 @@ module Bolt
48
48
 
49
49
  def script_step(scope, step)
50
50
  script = step['script']
51
- targets = step['targets'] || step['target']
51
+ targets = step['targets']
52
52
  description = step['description']
53
53
  arguments = step['arguments'] || []
54
54
 
@@ -64,7 +64,7 @@ module Bolt
64
64
 
65
65
  def command_step(scope, step)
66
66
  command = step['command']
67
- targets = step['targets'] || step['target']
67
+ targets = step['targets']
68
68
  description = step['description']
69
69
 
70
70
  args = [command, targets]
@@ -73,9 +73,9 @@ module Bolt
73
73
  end
74
74
 
75
75
  def upload_step(scope, step)
76
- source = step['upload'] || step['source']
76
+ source = step['upload']
77
77
  destination = step['destination']
78
- targets = step['targets'] || step['target']
78
+ targets = step['targets']
79
79
  description = step['description']
80
80
 
81
81
  args = [source, destination, targets]
@@ -86,7 +86,7 @@ module Bolt
86
86
  def download_step(scope, step)
87
87
  source = step['download']
88
88
  destination = step['destination']
89
- targets = step['targets'] || step['target']
89
+ targets = step['targets']
90
90
  description = step['description']
91
91
 
92
92
  args = [source, destination, targets]
@@ -99,7 +99,7 @@ module Bolt
99
99
  end
100
100
 
101
101
  def resources_step(scope, step)
102
- targets = step['targets'] || step['target']
102
+ targets = step['targets']
103
103
 
104
104
  # TODO: Only call apply_prep when needed
105
105
  scope.call_function('apply_prep', targets)
@@ -154,18 +154,6 @@ module Bolt
154
154
  # This is the method that Puppet calls to evaluate the plan. The name
155
155
  # makes more sense for .pp plans.
156
156
  def evaluate_block_with_bindings(closure_scope, args_hash, plan)
157
- if plan.steps.any? { |step| step.body.key?('target') }
158
- msg = "The 'target' parameter for YAML plan steps is deprecated and will be removed "\
159
- "in a future version of Bolt. Use the 'targets' parameter instead."
160
- Bolt::Logger.deprecate("yaml_plan_target", msg)
161
- end
162
-
163
- if plan.steps.any? { |step| step.body.key?('source') }
164
- msg = "The 'source' parameter for YAML plan upload steps is deprecated and will be removed "\
165
- "in a future version of Bolt. Use the 'upload' parameter instead."
166
- Bolt::Logger.deprecate("yaml_plan_source", msg)
167
- end
168
-
169
157
  plan_result = closure_scope.with_local_scope(args_hash) do |scope|
170
158
  plan.steps.each do |step|
171
159
  step_result = dispatch_step(scope, step)
@@ -9,19 +9,17 @@ module Bolt
9
9
  attr_reader :name, :type, :body, :targets
10
10
 
11
11
  def self.allowed_keys
12
- Set['name', 'description', 'target', 'targets']
12
+ Set['name', 'description', 'targets']
13
13
  end
14
14
 
15
15
  STEP_KEYS = %w[
16
16
  command
17
- destination
18
17
  download
19
18
  eval
20
19
  message
21
20
  plan
22
21
  resources
23
22
  script
24
- source
25
23
  task
26
24
  upload
27
25
  ].freeze
@@ -34,13 +32,7 @@ module Bolt
34
32
  when 1
35
33
  type = type_keys.first
36
34
  else
37
- if [Set['source', 'destination'], Set['upload', 'destination']].include?(type_keys.to_set)
38
- type = 'upload'
39
- elsif type_keys.to_set == Set['download', 'destination']
40
- type = 'download'
41
- else
42
- raise step_error("Multiple action keys detected: #{type_keys.inspect}", step_body['name'], step_number)
43
- end
35
+ raise step_error("Multiple action keys detected: #{type_keys.inspect}", step_body['name'], step_number)
44
36
  end
45
37
 
46
38
  step_class = const_get("Bolt::PAL::YamlPlan::Step::#{type.capitalize}")
@@ -51,7 +43,7 @@ module Bolt
51
43
  def initialize(step_body)
52
44
  @name = step_body['name']
53
45
  @description = step_body['description']
54
- @targets = step_body['targets'] || step_body['target']
46
+ @targets = step_body['targets']
55
47
  @body = step_body
56
48
  end
57
49
 
@@ -96,19 +88,6 @@ module Bolt
96
88
  # Ensure all required keys are present
97
89
  missing_keys = required_keys - body.keys
98
90
 
99
- # Handle cases where steps with a required 'targets' key are using the deprecated
100
- # 'target' key instead.
101
- # TODO: Remove this when 'target' is removed
102
- if body.include?('target')
103
- missing_keys -= ['targets']
104
- end
105
-
106
- # Handle cases where upload step uses deprecated 'source' key instead of 'upload'
107
- # TODO: Remove when 'source' is removed
108
- if body.include?('source')
109
- missing_keys -= ['upload']
110
- end
111
-
112
91
  if missing_keys.any?
113
92
  error_message = "The #{step_type.inspect} step requires: #{missing_keys.to_a.inspect} key(s)"
114
93
  err = step_error(error_message, body['name'], step_number)
@@ -6,7 +6,7 @@ module Bolt
6
6
  class Step
7
7
  class Upload < Step
8
8
  def self.allowed_keys
9
- super + Set['source', 'destination', 'upload']
9
+ super + Set['destination', 'upload']
10
10
  end
11
11
 
12
12
  def self.required_keys
@@ -15,7 +15,7 @@ module Bolt
15
15
 
16
16
  def initialize(step_body)
17
17
  super
18
- @source = step_body['upload'] || step_body['source']
18
+ @source = step_body['upload']
19
19
  @destination = step_body['destination']
20
20
  end
21
21
 
@@ -30,10 +30,6 @@ module Bolt
30
30
  @module = mod
31
31
  @config = config
32
32
  @context = context
33
-
34
- if @module.name == 'pkcs7'
35
- @config = handle_deprecated_pkcs7_keys(@config)
36
- end
37
33
  end
38
34
 
39
35
  # This method interacts with the module on disk so it's separate from initialize
@@ -160,10 +156,6 @@ module Bolt
160
156
  # out now.
161
157
  meta, params = opts.partition { |key, _val| key.start_with?('_') }.map(&:to_h)
162
158
 
163
- if task.module_name == 'pkcs7'
164
- params = handle_deprecated_pkcs7_keys(params)
165
- end
166
-
167
159
  # Reject parameters from config that are not accepted by the task and
168
160
  # merge in parameter defaults
169
161
  params = if task.parameters
@@ -181,21 +173,6 @@ module Bolt
181
173
  [params, meta]
182
174
  end
183
175
 
184
- # Raises a deprecation warning if the pkcs7 plugin is using deprecated keys and
185
- # modifies the keys so they are the correct format
186
- def handle_deprecated_pkcs7_keys(params)
187
- if params.key?('private-key') || params.key?('public-key')
188
- message = "pkcs7 keys 'private-key' and 'public-key' have been deprecated and will be "\
189
- "removed in a future version of Bolt; use 'private_key' and 'public_key' instead."
190
- Bolt::Logger.deprecate("pkcs7_params", message)
191
- end
192
-
193
- params['private_key'] = params.delete('private-key') if params.key?('private-key')
194
- params['public_key'] = params.delete('public-key') if params.key?('public-key')
195
-
196
- params
197
- end
198
-
199
176
  def extract_task_parameter_schema
200
177
  # Get the intersection of expected types (using Set)
201
178
  type_set = @hook_map.each_with_object({}) do |(_hook, task), acc|
@@ -3,12 +3,45 @@
3
3
  module Bolt
4
4
  class Plugin
5
5
  class PuppetConnectData
6
+ INPUT_DATA_VAR = 'PUPPET_CONNECT_INPUT_DATA'
7
+
6
8
  def initialize(context:, **_opts)
7
- puppet_connect_data_yaml_path = File.join(context.boltdir, 'puppet_connect_data.yaml')
9
+ if ENV.key?(INPUT_DATA_VAR)
10
+ # The user provided input data that they will copy-paste into the Puppet Connect UI
11
+ # for inventory syncing. This environment variable will likely be set when invoking a
12
+ # general "test Puppet Connect input data" command. That command tests that parsing
13
+ # the inventory with the given input data results in connectable targets. Part of
14
+ # that requires validating that the input data contains all of the referenced keys,
15
+ # which is what this plugin will do in validate_resolve_reference.
16
+ @input_data_path = ENV[INPUT_DATA_VAR]
17
+ data_path = @input_data_path
18
+ else
19
+ # The user is using this plugin during a regular Bolt invocation, so fetch the (minimal)
20
+ # required data from the default location. This data should typically be non-autoloadable
21
+ # secrets like WinRM passwords.
22
+ #
23
+ # Note that any unspecified keys will be resolved to nil.
24
+ data_path = File.join(context.boltdir, 'puppet_connect_data.yaml')
25
+ end
26
+
8
27
  @data = Bolt::Util.read_optional_yaml_hash(
9
- puppet_connect_data_yaml_path,
10
- 'puppet_connect_data.yaml'
28
+ data_path,
29
+ File.basename(data_path)
11
30
  )
31
+
32
+ if @input_data_path
33
+ # Validate that the data does not contain any plugin-reference
34
+ # values
35
+ @data.each do |key, toplevel_value|
36
+ # Use walk_vals to check for nested plugin references
37
+ Bolt::Util.walk_vals(toplevel_value) do |current_value|
38
+ if current_value.is_a?(Hash) && current_value.key?('_plugin')
39
+ raise invalid_input_data_err("the #{key} key's value contains a plugin reference")
40
+ end
41
+ current_value
42
+ end
43
+ end
44
+ end
12
45
  end
13
46
 
14
47
  def name
@@ -29,6 +62,15 @@ module Bolt
29
62
  raise Bolt::ValidationError,
30
63
  "puppet_connect_data plugin requires that 'key' be specified"
31
64
  end
65
+ if @input_data_path && !@data.key?(opts['key'])
66
+ # Input data for Puppet Connect was provided and opts['key'] does not have a
67
+ # value specified. Raise an error for this case.
68
+ raise invalid_input_data_err("a value for the #{opts['key']} key is not specified")
69
+ end
70
+ end
71
+
72
+ def invalid_input_data_err(msg)
73
+ Bolt::ValidationError.new("invalid input data #{@input_data_path}: #{msg}")
32
74
  end
33
75
  end
34
76
  end
data/lib/bolt/project.rb CHANGED
@@ -11,7 +11,7 @@ module Bolt
11
11
  BOLTDIR_NAME = 'Boltdir'
12
12
  CONFIG_NAME = 'bolt-project.yaml'
13
13
 
14
- attr_reader :path, :data, :config_file, :inventory_file, :hiera_config,
14
+ attr_reader :path, :data, :inventory_file, :hiera_config,
15
15
  :puppetfile, :rerunfile, :type, :resource_types, :project_file,
16
16
  :downloads, :plans_path, :modulepath, :managed_moduledir,
17
17
  :backup_dir, :plugin_cache_file, :plan_cache_file
@@ -24,31 +24,21 @@ module Bolt
24
24
  end
25
25
 
26
26
  # Search recursively up the directory hierarchy for the Project. Look for a
27
- # directory called Boltdir or a file called bolt.yaml (for a control repo
28
- # type Project). Otherwise, repeat the check on each directory up the
27
+ # directory called Boltdir or a file called bolt-project.yaml (for a control
28
+ # repo type Project). Otherwise, repeat the check on each directory up the
29
29
  # hierarchy, falling back to the default if we reach the root.
30
30
  def self.find_boltdir(dir)
31
31
  dir = Pathname.new(dir)
32
32
 
33
33
  if (dir + BOLTDIR_NAME).directory?
34
34
  create_project(dir + BOLTDIR_NAME, 'embedded')
35
- elsif (dir + 'bolt.yaml').file?
36
- command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
37
- Bolt::Logger.deprecate(
38
- "bolt_yaml",
39
- "Configuration file #{dir + 'bolt.yaml'} is deprecated and will be "\
40
- "removed in Bolt 3.0.\nUpdate your Bolt project to the latest Bolt practices "\
41
- "using #{command}."
42
- )
43
- create_project(dir, 'local')
44
35
  elsif (dir + CONFIG_NAME).file?
45
36
  create_project(dir, 'local')
46
37
  elsif dir.root?
47
38
  default_project
48
39
  else
49
40
  Bolt::Logger.debug(
50
- "Did not detect Boltdir, bolt.yaml, or bolt-project.yaml at '#{dir}'. "\
51
- "This directory won't be loaded as a project."
41
+ "Did not detect Boltdir or bolt-project.yaml at '#{dir}'. This directory won't be loaded as a project."
52
42
  )
53
43
  find_boltdir(dir.parent)
54
44
  end
@@ -85,9 +75,8 @@ module Bolt
85
75
  project_file = File.join(fullpath, CONFIG_NAME)
86
76
  data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
87
77
  default = type =~ /user|system/ ? 'default ' : ''
88
- exist = File.exist?(File.expand_path(project_file))
89
78
 
90
- if exist
79
+ if File.exist?(File.expand_path(project_file))
91
80
  Bolt::Logger.info("Loaded #{default}project from '#{fullpath}'")
92
81
  end
93
82
 
@@ -105,67 +94,37 @@ module Bolt
105
94
  def self.schema
106
95
  {
107
96
  type: Hash,
108
- properties: Bolt::Config::BOLT_PROJECT_OPTIONS.map { |opt| [opt, _ref: opt] }.to_h,
97
+ properties: Bolt::Config::PROJECT_OPTIONS.map { |opt| [opt, _ref: opt] }.to_h,
109
98
  definitions: Bolt::Config::OPTIONS
110
99
  }
111
100
  end
112
101
 
113
- def initialize(raw_data, path, type = 'option')
114
- @path = Pathname.new(path).expand_path
115
- @project_file = @path + CONFIG_NAME
116
-
117
- if (@path + 'bolt.yaml').file? && project_file?
118
- Bolt::Logger.deprecate(
119
- "bolt_yaml",
120
- "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
121
- "Transport config should be set in inventory.yaml, all other config should be set in "\
122
- "bolt-project.yaml."
123
- )
124
- end
125
-
102
+ def initialize(data, path, type = 'option')
103
+ @type = type
104
+ @path = Pathname.new(path).expand_path
105
+ @project_file = @path + CONFIG_NAME
126
106
  @inventory_file = @path + 'inventory.yaml'
127
107
  @hiera_config = @path + 'hiera.yaml'
128
108
  @puppetfile = @path + 'Puppetfile'
129
109
  @rerunfile = @path + '.rerun.json'
130
110
  @resource_types = @path + '.resource_types'
131
- @type = type
132
111
  @downloads = @path + 'downloads'
133
112
  @plans_path = @path + 'plans'
134
113
  @managed_moduledir = @path + '.modules'
135
114
  @backup_dir = @path + '.bolt-bak'
136
115
  @plugin_cache_file = @path + '.plugin_cache.json'
137
116
  @plan_cache_file = @path + '.plan_cache.json'
117
+ @modulepath = [(@path + 'modules').to_s]
138
118
 
139
- if (tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys).any?
119
+ if (tc = Bolt::Config::INVENTORY_OPTIONS.keys & data.keys).any?
140
120
  Bolt::Logger.warn(
141
121
  "project_transport_config",
142
122
  "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}."
143
123
  )
144
124
  end
145
125
 
146
- @data = raw_data.reject { |k, _| Bolt::Config::INVENTORY_OPTIONS.include?(k) }
147
-
148
- # If the 'modules' key is present in the project configuration file,
149
- # use the new, shorter modulepath.
150
- @modulepath = if @data.key?('modules')
151
- [(@path + 'modules').to_s]
152
- else
153
- [(@path + 'modules').to_s, (@path + 'site-modules').to_s, (@path + 'site').to_s]
154
- end
155
-
156
- # Once bolt.yaml deprecation is removed, this attribute should be removed
157
- # and replaced with .project_file in lib/bolt/config.rb
158
- @config_file = if (Bolt::Config::BOLT_OPTIONS & @data.keys).any?
159
- if (@path + 'bolt.yaml').file?
160
- Bolt::Logger.warn(
161
- "project_config_conflict",
162
- "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
163
- )
164
- end
165
- @project_file
166
- else
167
- @path + 'bolt.yaml'
168
- end
126
+ @data = data.slice(*Bolt::Config::PROJECT_OPTIONS)
127
+
169
128
  validate if project_file?
170
129
  end
171
130
 
@@ -219,7 +178,8 @@ module Bolt
219
178
  end
220
179
 
221
180
  def modules
222
- @modules ||= @data['modules']&.map do |mod|
181
+ mod_data = @data['modules'] || []
182
+ @modules ||= mod_data.map do |mod|
223
183
  if mod.is_a?(String)
224
184
  { 'name' => mod }
225
185
  else