bolt 2.29.0 → 2.30.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

@@ -19,7 +19,7 @@ module Bolt
19
19
  # Loads a Puppetfile and parses its module specifications, returning a
20
20
  # Bolt::Puppetfile object with the modules set.
21
21
  #
22
- def self.parse(path)
22
+ def self.parse(path, skip_unsupported_modules: false)
23
23
  require 'puppetfile-resolver'
24
24
  require 'puppetfile-resolver/puppetfile/parser/r10k_eval'
25
25
 
@@ -33,12 +33,24 @@ module Bolt
33
33
  end
34
34
 
35
35
  unless parsed.valid?
36
- raise Bolt::ValidationError,
37
- "Unable to parse Puppetfile #{path}"
36
+ # valid? Just checks if validation_errors is empty, so if we get here we know it's not.
37
+ raise Bolt::ValidationError, <<~MSG
38
+ Unable to parse Puppetfile #{path}:
39
+ #{parsed.validation_errors.join("\n\n")}.
40
+ This may not be a Puppetfile managed by Bolt.
41
+ MSG
38
42
  end
39
43
 
40
- modules = parsed.modules.map do |mod|
41
- Bolt::Puppetfile::Module.new(mod.owner, mod.name, mod.version)
44
+ modules = parsed.modules.each_with_object([]) do |mod, acc|
45
+ unless mod.instance_of? PuppetfileResolver::Puppetfile::ForgeModule
46
+ next if skip_unsupported_modules
47
+
48
+ raise Bolt::ValidationError,
49
+ "Module '#{mod.title}' is not a Puppet Forge module. Unable to "\
50
+ "parse Puppetfile #{path}."
51
+ end
52
+
53
+ acc << Bolt::Puppetfile::Module.new(mod.owner, mod.name, mod.version)
42
54
  end
43
55
 
44
56
  new(modules)
@@ -47,19 +59,11 @@ module Bolt
47
59
  # Writes a Puppetfile that includes specifications for each of the
48
60
  # modules.
49
61
  #
50
- def write(path, force: false)
51
- if File.exist?(path) && !force
52
- raise Bolt::FileError.new(
53
- "Cannot overwrite existing Puppetfile at #{path}. To forcibly overwrite, "\
54
- "run with the '--force' option.",
55
- path
56
- )
57
- end
58
-
62
+ def write(path, moduledir = nil)
59
63
  File.open(path, 'w') do |file|
60
64
  file.puts '# This Puppetfile is managed by Bolt. Do not edit.'
65
+ file.puts "moduledir '#{moduledir.basename}'" if moduledir
61
66
  modules.each { |mod| file.puts mod.to_spec }
62
- file.puts
63
67
  end
64
68
  rescue SystemCallError => e
65
69
  raise Bolt::FileError.new(
@@ -104,46 +108,24 @@ module Bolt
104
108
  cache: nil,
105
109
  ui: nil,
106
110
  module_paths: [],
107
- allow_missing_modules: true
111
+ allow_missing_modules: false
108
112
  )
109
113
  rescue StandardError => e
110
114
  raise Bolt::Error.new(e.message, 'bolt/puppetfile-resolver-error')
111
115
  end
112
116
 
113
- # Validate that the modules exist.
114
- missing_graph = result.specifications.select do |_name, spec|
115
- spec.instance_of? PuppetfileResolver::Models::MissingModuleSpecification
117
+ # Turn specifications into module objects. This will skip over anything that is not
118
+ # a module specification (i.e. a Puppet version specification).
119
+ @modules = result.specifications.each_with_object(Set.new) do |(_name, spec), acc|
120
+ next unless spec.instance_of? PuppetfileResolver::Models::ModuleSpecification
121
+ acc << Bolt::Puppetfile::Module.new(spec.owner, spec.name, spec.version.to_s)
116
122
  end
117
-
118
- if missing_graph.any?
119
- titles = model.modules.each_with_object({}) do |mod, acc|
120
- acc[mod.name] = mod.title
121
- end
122
-
123
- names = titles.values_at(*missing_graph.keys)
124
- plural = names.count == 1 ? '' : 's'
125
-
126
- raise Bolt::Error.new(
127
- "Unknown module name#{plural} #{names.join(', ')}",
128
- 'bolt/unknown-modules'
129
- )
130
- end
131
-
132
- # Filter the dependency graph to only include module specifications. This
133
- # will only remove the Puppet version specification, which is not needed.
134
- specs = result.specifications.select do |_name, spec|
135
- spec.instance_of? PuppetfileResolver::Models::ModuleSpecification
136
- end
137
-
138
- @modules = specs.map do |_name, spec|
139
- Bolt::Puppetfile::Module.new(spec.owner, spec.name, spec.version.to_s)
140
- end.to_set
141
123
  end
142
124
 
143
125
  # Adds to the set of modules.
144
126
  #
145
127
  def add_modules(modules)
146
- modules.each do |mod|
128
+ Array(modules).each do |mod|
147
129
  case mod
148
130
  when Bolt::Puppetfile::Module
149
131
  @modules << mod
@@ -13,7 +13,7 @@ module Bolt
13
13
  def initialize(owner, name, version = nil)
14
14
  @owner = owner
15
15
  @name = name
16
- @version = version
16
+ @version = version unless version == :latest
17
17
  end
18
18
 
19
19
  # Creates a new module from a hash.
@@ -38,6 +38,7 @@ module Bolt
38
38
  def title
39
39
  "#{@owner}-#{@name}"
40
40
  end
41
+ alias to_s title
41
42
 
42
43
  # Checks two modules for equality.
43
44
  #
@@ -143,7 +143,7 @@ module Bolt
143
143
 
144
144
  execute_options[:stdin] = stdin
145
145
  execute_options[:sudoable] = true if run_as
146
- output = execute(remote_task_path, execute_options)
146
+ output = execute(remote_task_path, **execute_options)
147
147
  end
148
148
  Bolt::Result.for_task(target, output.stdout.string,
149
149
  output.stderr.string,
@@ -299,6 +299,28 @@ module Bolt
299
299
  raise Bolt::ValidationError, "path must be a String, received #{path.class} #{path}" unless path.is_a?(String)
300
300
  path.split(%r{[/\\]}).last
301
301
  end
302
+
303
+ # Prompts yes or no, returning true for yes and false for no.
304
+ #
305
+ def prompt_yes_no(prompt, outputter)
306
+ choices = {
307
+ 'y' => true,
308
+ 'yes' => true,
309
+ 'n' => false,
310
+ 'no' => false
311
+ }
312
+
313
+ loop do
314
+ outputter.print_prompt("#{prompt} ([y]es/[n]o) ")
315
+ response = $stdin.gets.to_s.downcase.chomp
316
+
317
+ if choices.key?(response)
318
+ return choices[response]
319
+ else
320
+ outputter.print_prompt_error("Invalid response, must pick [y]es or [n]o")
321
+ end
322
+ end
323
+ end
302
324
  end
303
325
  end
304
326
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.29.0'
4
+ VERSION = '2.30.0'
5
5
  end
@@ -16,9 +16,9 @@ module BoltServer
16
16
  end
17
17
  end
18
18
 
19
- def initialize(app, whitelist)
19
+ def initialize(app, allowlist)
20
20
  acls = []
21
- whitelist.each do |entry|
21
+ allowlist.each do |entry|
22
22
  acls << {
23
23
  'resources' => [
24
24
  {
@@ -7,7 +7,7 @@ module BoltServer
7
7
  class BaseConfig
8
8
  def config_keys
9
9
  %w[host port ssl-cert ssl-key ssl-ca-cert
10
- ssl-cipher-suites loglevel logfile whitelist projects-dir]
10
+ ssl-cipher-suites loglevel logfile allowlist projects-dir]
11
11
  end
12
12
 
13
13
  def env_keys
@@ -98,8 +98,8 @@ module BoltServer
98
98
  raise Bolt::ValidationError, "Configured 'ssl-cipher-suites' must be an array of cipher suite names"
99
99
  end
100
100
 
101
- unless @data['whitelist'].nil? || @data['whitelist'].is_a?(Array)
102
- raise Bolt::ValidationError, "Configured 'whitelist' must be an array of names"
101
+ unless @data['allowlist'].nil? || @data['allowlist'].is_a?(Array)
102
+ raise Bolt::ValidationError, "Configured 'allowlist' must be an array of names"
103
103
  end
104
104
  end
105
105
 
@@ -40,7 +40,7 @@ module BoltSpec
40
40
 
41
41
  def module_file_id(file)
42
42
  modpath = @modulepath.select { |path| file =~ /^#{path}/ }
43
- raise "Could not identify module path containing #{file}: #{modpath}" unless modpath.size == 1
43
+ raise "Could not identify modulepath containing #{file}: #{modpath}" unless modpath.size == 1
44
44
 
45
45
  path = Pathname.new(file)
46
46
  relative = path.relative_path_from(Pathname.new(modpath.first))
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.29.0
4
+ version: 2.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-21 00:00:00.000000000 Z
11
+ date: 2020-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -461,6 +461,7 @@ files:
461
461
  - lib/bolt/inventory/target.rb
462
462
  - lib/bolt/logger.rb
463
463
  - lib/bolt/module.rb
464
+ - lib/bolt/module_installer.rb
464
465
  - lib/bolt/node/errors.rb
465
466
  - lib/bolt/node/output.rb
466
467
  - lib/bolt/outputter.rb
@@ -494,7 +495,11 @@ files:
494
495
  - lib/bolt/plugin/puppetdb.rb
495
496
  - lib/bolt/plugin/task.rb
496
497
  - lib/bolt/project.rb
497
- - lib/bolt/project_migrate.rb
498
+ - lib/bolt/project_migrator.rb
499
+ - lib/bolt/project_migrator/base.rb
500
+ - lib/bolt/project_migrator/config.rb
501
+ - lib/bolt/project_migrator/inventory.rb
502
+ - lib/bolt/project_migrator/modules.rb
498
503
  - lib/bolt/puppetdb.rb
499
504
  - lib/bolt/puppetdb/client.rb
500
505
  - lib/bolt/puppetdb/config.rb
@@ -1,138 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Bolt
4
- class ProjectMigrate
5
- attr_reader :path, :project_file, :backup_dir, :outputter, :inventory_file, :config_file
6
-
7
- # This init mostly makes testing easier
8
- def initialize(path, outputter, configured_inventory = nil)
9
- @path = Pathname.new(path).expand_path
10
- @project_file = @path + 'bolt-project.yaml'
11
- @config_file = @path + 'bolt.yaml'
12
- @backup_dir = @path + '.bolt-bak'
13
- @inventory_file = configured_inventory || @path + 'inventory.yaml'
14
- @outputter = outputter
15
- end
16
-
17
- def migrate_project
18
- inv_ok = inventory_1_to_2(inventory_file, outputter) if inventory_file.file?
19
- config_ok = bolt_yaml_to_bolt_project(inventory_file, outputter)
20
- inv_ok && config_ok ? 0 : 1
21
- end
22
-
23
- # This could be made public and used elsewhere if the need arises
24
- private def backup_file(origin_path)
25
- unless File.exist?(origin_path)
26
- outputter.print_message "Could not find file #{origin_path}, skipping backup."
27
- return
28
- end
29
-
30
- date = Time.new.strftime("%Y%m%d_%H%M%S%L")
31
- FileUtils.mkdir_p(backup_dir)
32
-
33
- filename = File.basename(origin_path)
34
- backup_path = File.join(backup_dir, "#{filename}.#{date}.bak")
35
-
36
- outputter.print_message "Backing up #{filename} from #{origin_path} to #{backup_path}"
37
-
38
- begin
39
- FileUtils.cp(origin_path, backup_path)
40
- rescue StandardError => e
41
- raise Bolt::FileError.new("#{e.message}; unable to create backup of #{filename}.", origin_path)
42
- end
43
- end
44
-
45
- private def bolt_yaml_to_bolt_project(inventory_file, outputter)
46
- # If bolt-project.yaml already exists
47
- if project_file.file?
48
- outputter.print_message "bolt-project.yaml already exists in Bolt "\
49
- "project at #{path}. Skipping project file update."
50
-
51
- # If bolt.yaml doesn't exist
52
- elsif !config_file.file?
53
- outputter.print_message "Could not find bolt.yaml in project at "\
54
- "#{path}. Skipping project file update."
55
-
56
- else
57
- config_data = Bolt::Util.read_optional_yaml_hash(config_file, 'config')
58
- transport_data, project_data = config_data.partition do |k, _|
59
- Bolt::Config::INVENTORY_OPTIONS.keys.include?(k)
60
- end.map(&:to_h)
61
-
62
- if transport_data.any?
63
- if File.exist?(inventory_file)
64
- inventory_data = Bolt::Util.read_yaml_hash(inventory_file, 'inventory')
65
- merged = Bolt::Util.deep_merge(transport_data, inventory_data['config'] || {})
66
- inventory_data['config'] = merged
67
- backup_file(inventory_file)
68
- else
69
- FileUtils.touch(inventory_file)
70
- inventory_data = { 'config' => transport_data }
71
- end
72
-
73
- backup_file(config_file)
74
-
75
- begin
76
- outputter.print_message "Moving transportation configuration options "\
77
- "'#{transport_data.keys.join(', ')}' from bolt.yaml to inventory.yaml"
78
- File.write(inventory_file, inventory_data.to_yaml)
79
- File.write(config_file, project_data.to_yaml)
80
- rescue StandardError => e
81
- raise Bolt::FileError.new("#{e.message}; unable to write inventory.", inventory_file)
82
- end
83
- end
84
-
85
- outputter.print_message "Renaming bolt.yaml to bolt-project.yaml"
86
- FileUtils.mv(config_file, project_file)
87
- outputter.print_message "Successfully updated project. Please add a "\
88
- "'name' key to bolt-project.yaml to use project-level tasks and plans. "\
89
- "Learn more about projects by running 'bolt guide project'."
90
- # If nothing errored, this succeeded
91
- true
92
- end
93
- end
94
-
95
- private def inventory_1_to_2(inventory_file, outputter)
96
- data = Bolt::Util.read_yaml_hash(inventory_file, 'inventory')
97
- data.delete('version') if data['version'] != 2
98
- migrated = migrate_group(data)
99
-
100
- ok = if migrated
101
- backup_file(inventory_file)
102
- File.write(inventory_file, data.to_yaml)
103
- end
104
-
105
- result = if migrated && ok
106
- "Successfully migrated Bolt inventory to the latest version."
107
- elsif !migrated
108
- "Bolt inventory is already on the latest version. Skipping inventory update."
109
- else
110
- "Could not migrate Bolt inventory to the latest version. See "\
111
- "https://puppet.com/docs/bolt/latest/inventory_file_v2.html to manually update."
112
- end
113
- outputter.print_message(result)
114
- ok
115
- end
116
-
117
- # Walks an inventory hash and replaces all 'nodes' keys with 'targets' keys
118
- # and all 'name' keys nested in a 'targets' hash with 'uri' keys. Data is
119
- # modified in place.
120
- private def migrate_group(group)
121
- migrated = false
122
- if group.key?('nodes')
123
- migrated = true
124
- targets = group['nodes'].map do |target|
125
- target['uri'] = target.delete('name') if target.is_a?(Hash)
126
- target
127
- end
128
- group.delete('nodes')
129
- group['targets'] = targets
130
- end
131
- (group['groups'] || []).each do |subgroup|
132
- migrated_group = migrate_group(subgroup)
133
- migrated ||= migrated_group
134
- end
135
- migrated
136
- end
137
- end
138
- end