bolt 2.34.0 → 2.40.1

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/applyresult.rb +1 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -3
  5. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +17 -6
  6. data/bolt-modules/boltlib/lib/puppet/functions/parallelize.rb +56 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +24 -6
  8. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +27 -8
  9. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +21 -1
  10. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +18 -1
  11. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +24 -6
  12. data/lib/bolt/analytics.rb +27 -8
  13. data/lib/bolt/apply_result.rb +3 -3
  14. data/lib/bolt/bolt_option_parser.rb +45 -18
  15. data/lib/bolt/cli.rb +98 -116
  16. data/lib/bolt/config.rb +184 -80
  17. data/lib/bolt/config/options.rb +148 -87
  18. data/lib/bolt/config/transport/base.rb +10 -19
  19. data/lib/bolt/config/transport/local.rb +1 -7
  20. data/lib/bolt/config/transport/options.rb +12 -69
  21. data/lib/bolt/config/transport/ssh.rb +8 -19
  22. data/lib/bolt/error.rb +24 -0
  23. data/lib/bolt/executor.rb +92 -18
  24. data/lib/bolt/inventory.rb +25 -0
  25. data/lib/bolt/inventory/group.rb +0 -8
  26. data/lib/bolt/inventory/options.rb +130 -0
  27. data/lib/bolt/inventory/target.rb +10 -11
  28. data/lib/bolt/module_installer.rb +21 -13
  29. data/lib/bolt/module_installer/resolver.rb +1 -1
  30. data/lib/bolt/outputter.rb +19 -5
  31. data/lib/bolt/outputter/human.rb +22 -3
  32. data/lib/bolt/outputter/json.rb +1 -1
  33. data/lib/bolt/outputter/logger.rb +1 -1
  34. data/lib/bolt/outputter/rainbow.rb +13 -2
  35. data/lib/bolt/pal.rb +18 -6
  36. data/lib/bolt/pal/yaml_plan.rb +7 -0
  37. data/lib/bolt/plugin.rb +41 -12
  38. data/lib/bolt/plugin/cache.rb +76 -0
  39. data/lib/bolt/plugin/module.rb +4 -4
  40. data/lib/bolt/plugin/puppetdb.rb +1 -1
  41. data/lib/bolt/project.rb +59 -40
  42. data/lib/bolt/project_manager.rb +201 -0
  43. data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +49 -4
  44. data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +3 -3
  45. data/lib/bolt/{project_migrator/base.rb → project_manager/migrator.rb} +2 -2
  46. data/lib/bolt/{project_migrator/modules.rb → project_manager/module_migrator.rb} +5 -3
  47. data/lib/bolt/puppetdb/client.rb +11 -2
  48. data/lib/bolt/puppetdb/config.rb +4 -3
  49. data/lib/bolt/rerun.rb +1 -5
  50. data/lib/bolt/shell/bash.rb +8 -2
  51. data/lib/bolt/shell/powershell.rb +21 -3
  52. data/lib/bolt/target.rb +4 -0
  53. data/lib/bolt/task/run.rb +1 -1
  54. data/lib/bolt/transport/local.rb +13 -0
  55. data/lib/bolt/transport/orch.rb +0 -5
  56. data/lib/bolt/transport/orch/connection.rb +10 -3
  57. data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
  58. data/lib/bolt/util.rb +36 -7
  59. data/lib/bolt/validator.rb +227 -0
  60. data/lib/bolt/version.rb +1 -1
  61. data/lib/bolt/yarn.rb +23 -0
  62. data/lib/bolt_server/base_config.rb +3 -1
  63. data/lib/bolt_server/config.rb +3 -1
  64. data/lib/bolt_server/plugin.rb +13 -0
  65. data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
  66. data/lib/bolt_server/schemas/connect-data.json +22 -0
  67. data/lib/bolt_server/schemas/partials/task.json +2 -2
  68. data/lib/bolt_server/transport_app.rb +82 -23
  69. data/lib/bolt_spec/plans/mock_executor.rb +4 -1
  70. data/libexec/apply_catalog.rb +1 -1
  71. data/libexec/custom_facts.rb +1 -1
  72. data/libexec/query_resources.rb +1 -1
  73. metadata +22 -14
  74. data/lib/bolt/project_migrator.rb +0 -80
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/project_manager/config_migrator'
4
+ require 'bolt/project_manager/inventory_migrator'
5
+ require 'bolt/project_manager/module_migrator'
6
+
7
+ module Bolt
8
+ class ProjectManager
9
+ INVENTORY_TEMPLATE = <<~INVENTORY
10
+ # This is an example inventory.yaml
11
+ # To read more about inventory files, see https://pup.pt/bolt-inventory
12
+ #
13
+ # groups:
14
+ # - name: linux
15
+ # targets:
16
+ # - target1.example.com
17
+ # - target2.example.com
18
+ # config:
19
+ # transport: ssh
20
+ # ssh:
21
+ # private-key: /path/to/private_key.pem
22
+ # - name: windows
23
+ # targets:
24
+ # - name: win1
25
+ # uri: target3.example.com
26
+ # - name: win2
27
+ # uri: target4.example.com
28
+ # config:
29
+ # transport: winrm
30
+ # config:
31
+ # ssh:
32
+ # host-key-check: false
33
+ # winrm:
34
+ # user: Administrator
35
+ # password: Bolt!
36
+ # ssl: false
37
+ INVENTORY
38
+
39
+ def initialize(config, outputter, pal)
40
+ @config = config
41
+ @outputter = outputter
42
+ @pal = pal
43
+ end
44
+
45
+ # Creates a new project at the specified directory.
46
+ #
47
+ def create(path, name, modules)
48
+ require 'bolt/module_installer'
49
+
50
+ project = Pathname.new(File.expand_path(path))
51
+ old_config = project + 'bolt.yaml'
52
+ config = project + 'bolt-project.yaml'
53
+ puppetfile = project + 'Puppetfile'
54
+ moduledir = project + '.modules'
55
+ inventoryfile = project + 'inventory.yaml'
56
+ project_name = name || File.basename(project)
57
+
58
+ if config.exist?
59
+ if modules
60
+ command = Bolt::Util.powershell? ? 'Add-BoltModule -Module' : 'bolt module add'
61
+ raise Bolt::Error.new(
62
+ "Found existing project directory with #{config.basename} at #{project}, "\
63
+ "unable to initialize project with modules. To add modules to the project, "\
64
+ "run '#{command} <module>' instead.",
65
+ 'bolt/existing-project-error'
66
+ )
67
+ else
68
+ raise Bolt::Error.new(
69
+ "Found existing project directory with #{config.basename} at #{project}, "\
70
+ "unable to initialize project.",
71
+ 'bolt/existing-project-error'
72
+ )
73
+ end
74
+ elsif old_config.exist?
75
+ command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
76
+ raise Bolt::Error.new(
77
+ "Found existing project directory with #{old_config.basename} at #{project}, "\
78
+ "unable to initialize project. #{old_config.basename} is deprecated. To "\
79
+ "update the project to current best practices, run '#{command}'.",
80
+ 'bolt/existing-project-error'
81
+ )
82
+ elsif modules && puppetfile.exist?
83
+ raise Bolt::Error.new(
84
+ "Found existing Puppetfile at #{puppetfile}, unable to initialize project "\
85
+ "with modules.",
86
+ 'bolt/existing-puppetfile-error'
87
+ )
88
+ elsif project_name !~ Bolt::Module::MODULE_NAME_REGEX
89
+ if name
90
+ raise Bolt::ValidationError,
91
+ "The provided project name '#{project_name}' is invalid; project name must "\
92
+ "begin with a lowercase letter and can include lowercase letters, "\
93
+ "numbers, and underscores."
94
+ else
95
+ command = Bolt::Util.powershell? ? 'New-BoltProject -Name' : 'bolt project init'
96
+ raise Bolt::ValidationError,
97
+ "The current directory name '#{project_name}' is an invalid project name. "\
98
+ "Please specify a name using '#{command} <name>'."
99
+ end
100
+ end
101
+
102
+ # If modules were specified, resolve and install first. We want to error
103
+ # early here and not initialize the project if the modules cannot be
104
+ # resolved and installed.
105
+ if modules
106
+ @outputter.start_spin
107
+ Bolt::ModuleInstaller.new(@outputter, @pal).install(modules, puppetfile, moduledir)
108
+ @outputter.stop_spin
109
+ end
110
+
111
+ data = { 'name' => project_name }
112
+ data['modules'] = modules || []
113
+
114
+ begin
115
+ File.write(config.to_path, data.to_yaml)
116
+ rescue StandardError => e
117
+ raise Bolt::FileError.new("Could not create bolt-project.yaml at #{project}: #{e.message}", nil)
118
+ end
119
+
120
+ unless inventoryfile.exist?
121
+ begin
122
+ File.write(inventoryfile.to_path, INVENTORY_TEMPLATE)
123
+ rescue StandardError => e
124
+ raise Bolt::FileError.new("Could not create inventory.yaml at #{project}: #{e.message}", nil)
125
+ end
126
+ end
127
+
128
+ @outputter.print_message("Successfully created Bolt project at #{project}")
129
+
130
+ 0
131
+ end
132
+
133
+ # Migrates a project to use the latest file versions and best practices.
134
+ #
135
+ def migrate
136
+ unless $stdin.tty?
137
+ raise Bolt::Error.new(
138
+ "stdin is not a tty, unable to migrate project",
139
+ 'bolt/stdin-not-a-tty-error'
140
+ )
141
+ end
142
+
143
+ @outputter.print_message("Migrating project #{@config.project.path}\n\n")
144
+
145
+ @outputter.print_action_step(
146
+ "Migrating a Bolt project may make irreversible changes to the project's "\
147
+ "configuration and inventory files. Before continuing, make sure the "\
148
+ "project has a backup or uses a version control system."
149
+ )
150
+
151
+ return 0 unless Bolt::Util.prompt_yes_no("Continue with project migration?", @outputter)
152
+
153
+ @outputter.print_message('')
154
+
155
+ ok = migrate_inventory && migrate_config && migrate_modules
156
+
157
+ if ok
158
+ @outputter.print_message("Project successfully migrated")
159
+ else
160
+ @outputter.print_error("Project could not be migrated completely")
161
+ end
162
+
163
+ ok ? 0 : 1
164
+ end
165
+
166
+ # Migrates the project-level configuration file to the latest version.
167
+ #
168
+ private def migrate_config
169
+ migrator = ConfigMigrator.new(@outputter)
170
+
171
+ migrator.migrate(
172
+ @config.project.config_file,
173
+ @config.project.project_file,
174
+ @config.inventoryfile || @config.project.inventory_file,
175
+ @config.project.backup_dir
176
+ )
177
+ end
178
+
179
+ # Migrates the inventory file to the latest version.
180
+ #
181
+ private def migrate_inventory
182
+ migrator = InventoryMigrator.new(@outputter)
183
+
184
+ migrator.migrate(
185
+ @config.inventoryfile || @config.project.inventory_file,
186
+ @config.project.backup_dir
187
+ )
188
+ end
189
+
190
+ # Migrates the project's modules to use current best practices.
191
+ #
192
+ private def migrate_modules
193
+ migrator = ModuleMigrator.new(@outputter)
194
+
195
+ migrator.migrate(
196
+ @config.project,
197
+ @config.modulepath
198
+ )
199
+ end
200
+ end
201
+ end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bolt/project_migrator/base'
3
+ require 'bolt/project_manager/migrator'
4
4
 
5
5
  module Bolt
6
- class ProjectMigrator
7
- class Config < Base
6
+ class ProjectManager
7
+ class ConfigMigrator < Migrator
8
8
  def migrate(config_file, project_file, inventory_file, backup_dir)
9
- bolt_yaml_to_bolt_project(config_file, project_file, inventory_file, backup_dir)
9
+ bolt_yaml_to_bolt_project(config_file, project_file, inventory_file, backup_dir) &&
10
+ update_options(project_file)
10
11
  end
11
12
 
12
13
  private def bolt_yaml_to_bolt_project(config_file, project_file, inventory_file, backup_dir)
@@ -63,6 +64,50 @@ module Bolt
63
64
 
64
65
  true
65
66
  end
67
+
68
+ private def update_options(project_file)
69
+ return true unless File.exist?(project_file)
70
+
71
+ @outputter.print_message("Updating project configuration options\n\n")
72
+ data = Bolt::Util.read_yaml_hash(project_file, 'config')
73
+ modified = false
74
+
75
+ # Keys to update. The first element is the old key, while the second is
76
+ # the key update it to.
77
+ to_update = [
78
+ %w[apply_settings apply-settings],
79
+ %w[puppetfile module-install],
80
+ %w[plugin_hooks plugin-hooks]
81
+ ]
82
+
83
+ to_update.each do |old, new|
84
+ next unless data.key?(old)
85
+
86
+ if data.key?(new)
87
+ @outputter.print_action_step("Removing deprecated option '#{old}'")
88
+ data.delete(old)
89
+ else
90
+ @outputter.print_action_step("Updating deprecated option '#{old}' to '#{new}'")
91
+ data[new] = data.delete(old)
92
+ end
93
+
94
+ modified = true
95
+ end
96
+
97
+ if modified
98
+ begin
99
+ File.write(project_file, data.to_yaml)
100
+ rescue StandardError => e
101
+ raise Bolt::FileError.new("#{e.message}; unable to write config.", project_file)
102
+ end
103
+
104
+ @outputter.print_action_step("Successfully updated project configuration #{project_file}")
105
+ else
106
+ @outputter.print_action_step("Project configuration is up to date, nothing to do.")
107
+ end
108
+
109
+ true
110
+ end
66
111
  end
67
112
  end
68
113
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bolt/project_migrator/base'
3
+ require 'bolt/project_manager/migrator'
4
4
 
5
5
  module Bolt
6
- class ProjectMigrator
7
- class Inventory < Base
6
+ class ProjectManager
7
+ class InventoryMigrator < Migrator
8
8
  def migrate(inventory_file, backup_dir)
9
9
  inventory1to2(inventory_file, backup_dir)
10
10
  end
@@ -4,8 +4,8 @@ require 'fileutils'
4
4
  require 'bolt/error'
5
5
 
6
6
  module Bolt
7
- class ProjectMigrator
8
- class Base
7
+ class ProjectManager
8
+ class Migrator
9
9
  def initialize(outputter)
10
10
  @outputter = outputter
11
11
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bolt/project_migrator/base'
3
+ require 'bolt/project_manager/migrator'
4
4
 
5
5
  module Bolt
6
- class ProjectMigrator
7
- class Modules < Base
6
+ class ProjectManager
7
+ class ModuleMigrator < Migrator
8
8
  def migrate(project, configured_modulepath)
9
9
  return true unless project.modules.nil?
10
10
 
@@ -61,6 +61,7 @@ module Bolt
61
61
  # Create specs to resolve from
62
62
  specs = Bolt::ModuleInstaller::Specs.new(modules.map(&:to_hash))
63
63
 
64
+ @outputter.start_spin
64
65
  # Attempt to resolve dependencies
65
66
  begin
66
67
  @outputter.print_message('')
@@ -72,6 +73,7 @@ module Bolt
72
73
  end
73
74
 
74
75
  migrate_managed_modules(puppetfile, puppetfile_path, managed_moduledir)
76
+ @outputter.stop_spin
75
77
 
76
78
  # Move remaining modules to 'modules'
77
79
  consolidate_modules(modulepath)
@@ -2,7 +2,6 @@
2
2
 
3
3
  require 'json'
4
4
  require 'logging'
5
- require 'uri'
6
5
 
7
6
  module Bolt
8
7
  module PuppetDB
@@ -19,6 +18,7 @@ module Bolt
19
18
  def query_certnames(query)
20
19
  return [] unless query
21
20
 
21
+ @logger.debug("Querying certnames")
22
22
  results = make_query(query)
23
23
 
24
24
  if results&.first && !results.first&.key?('certname')
@@ -35,6 +35,8 @@ module Bolt
35
35
  certnames.uniq!
36
36
  name_query = certnames.map { |c| ["=", "certname", c] }
37
37
  name_query.insert(0, "or")
38
+
39
+ @logger.debug("Querying certnames")
38
40
  result = make_query(name_query, 'inventory')
39
41
 
40
42
  result&.each_with_object({}) do |node, coll|
@@ -53,6 +55,8 @@ module Bolt
53
55
  facts_query.insert(0, "or")
54
56
 
55
57
  query = ['and', name_query, facts_query]
58
+
59
+ @logger.debug("Querying certnames")
56
60
  result = make_query(query, 'fact-contents')
57
61
  result.map! { |h| h.delete_if { |k, _v| %w[environment name].include?(k) } }
58
62
  result.group_by { |c| c['certname'] }
@@ -64,11 +68,13 @@ module Bolt
64
68
  url += "/#{path}" if path
65
69
 
66
70
  begin
71
+ @logger.debug("Sending PuppetDB query to #{url}")
67
72
  response = http_client.post(url, body: body, header: headers)
68
73
  rescue StandardError => e
69
74
  raise Bolt::PuppetDBFailoverError, "Failed to query PuppetDB: #{e}"
70
75
  end
71
76
 
77
+ @logger.debug("Got response code #{response.code} from PuppetDB")
72
78
  if response.code != 200
73
79
  msg = "Failed to query PuppetDB: #{response.body}"
74
80
  if response.code == 400
@@ -93,6 +99,7 @@ module Bolt
93
99
  return @http if @http
94
100
  # lazy-load expensive gem code
95
101
  require 'httpclient'
102
+ @logger.trace("Creating HTTP Client")
96
103
  @http = HTTPClient.new
97
104
  @http.ssl_config.set_client_cert_file(@config.cert, @config.key) if @config.cert
98
105
  @http.ssl_config.add_trust_ca(@config.cacert)
@@ -108,13 +115,15 @@ module Bolt
108
115
  end
109
116
 
110
117
  def uri
118
+ require 'addressable/uri'
119
+
111
120
  @current_url ||= (@config.server_urls - @bad_urls).first
112
121
  unless @current_url
113
122
  msg = "Failed to connect to all PuppetDB server_urls: #{@config.server_urls.to_a.join(', ')}."
114
123
  raise Bolt::PuppetDBError, msg
115
124
  end
116
125
 
117
- uri = URI.parse(@current_url)
126
+ uri = Addressable::URI.parse(@current_url)
118
127
  uri.port ||= 8081
119
128
  uri
120
129
  end
@@ -18,8 +18,7 @@ module Bolt
18
18
  end
19
19
 
20
20
  def self.default_windows_config
21
- require 'win32/dir'
22
- File.expand_path(File.join(Dir::COMMON_APPDATA, 'PuppetLabs/client-tools/puppetdb.conf'))
21
+ File.expand_path(File.join(ENV['ALLUSERSPROFILE'], 'PuppetLabs/client-tools/puppetdb.conf'))
23
22
  end
24
23
 
25
24
  def self.load_config(options, project_path = nil)
@@ -89,6 +88,8 @@ module Bolt
89
88
 
90
89
  def uri
91
90
  return @uri if @uri
91
+ require 'addressable/uri'
92
+
92
93
  uri = case @settings['server_urls']
93
94
  when String
94
95
  @settings['server_urls']
@@ -100,7 +101,7 @@ module Bolt
100
101
  raise Bolt::PuppetDBError, "server_urls must be a string or array"
101
102
  end
102
103
 
103
- @uri = URI.parse(uri)
104
+ @uri = Addressable::URI.parse(uri)
104
105
  @uri.port ||= 8081
105
106
  @uri
106
107
  end
@@ -12,15 +12,11 @@ module Bolt
12
12
  end
13
13
 
14
14
  def data
15
- @data ||= JSON.parse(File.read(@path))
15
+ @data ||= Bolt::Util.read_json_file(@path, 'rerun')
16
16
  unless @data.is_a?(Array) && @data.all? { |r| r['target'] && r['status'] }
17
17
  raise Bolt::FileError.new("Missing data in rerun file: #{@path}", @path)
18
18
  end
19
19
  @data
20
- rescue JSON::ParserError
21
- raise Bolt::FileError.new("Could not parse rerun file: #{@path}", @path)
22
- rescue IOError, SystemCallError
23
- raise Bolt::FileError.new("Could not read rerun file: #{@path}", @path)
24
20
  end
25
21
 
26
22
  def get_targets(filter)
@@ -199,7 +199,7 @@ module Bolt
199
199
  lines = buffer.split(/(?<=\n)/)
200
200
  # handle_sudo will return the line if it is not a sudo prompt or error
201
201
  lines.map! { |line| handle_sudo(inp, line, stdin) }
202
- lines.join("")
202
+ lines.join
203
203
  # If stream has reached EOF, no password prompt is expected
204
204
  # return an empty string
205
205
  rescue EOFError
@@ -436,8 +436,14 @@ module Bolt
436
436
  result_output.stderr << read_streams[err]
437
437
  result_output.exit_code = t.value.respond_to?(:exitstatus) ? t.value.exitstatus : t.value
438
438
 
439
- if result_output.exit_code == 0
439
+ case result_output.exit_code
440
+ when 0
440
441
  @logger.trace { "Command `#{command_str}` returned successfully" }
442
+ when 126
443
+ msg = "\n\nThis may be caused by the default tmpdir being mounted "\
444
+ "using 'noexec'. See http://pup.pt/task-failure for details and workarounds."
445
+ result_output.stderr << msg
446
+ @logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
441
447
  else
442
448
  @logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
443
449
  end