bolt 2.23.0 → 2.27.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/Puppetfile +1 -1
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
- data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +1 -1
- data/exe/bolt +1 -0
- data/guides/inventory.txt +19 -0
- data/guides/project.txt +22 -0
- data/lib/bolt/analytics.rb +11 -7
- data/lib/bolt/applicator.rb +11 -10
- data/lib/bolt/bolt_option_parser.rb +75 -13
- data/lib/bolt/catalog.rb +4 -2
- data/lib/bolt/cli.rb +156 -176
- data/lib/bolt/config.rb +55 -25
- data/lib/bolt/config/options.rb +28 -6
- data/lib/bolt/executor.rb +5 -3
- data/lib/bolt/inventory.rb +8 -1
- data/lib/bolt/inventory/group.rb +4 -4
- data/lib/bolt/inventory/inventory.rb +1 -1
- data/lib/bolt/inventory/target.rb +1 -1
- data/lib/bolt/logger.rb +12 -6
- data/lib/bolt/outputter/human.rb +10 -0
- data/lib/bolt/outputter/json.rb +11 -0
- data/lib/bolt/outputter/logger.rb +3 -3
- data/lib/bolt/outputter/rainbow.rb +15 -0
- data/lib/bolt/pal.rb +23 -12
- data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
- data/lib/bolt/pal/yaml_plan/transpiler.rb +11 -3
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +63 -17
- data/lib/bolt/project_migrate.rb +138 -0
- data/lib/bolt/puppetdb/client.rb +1 -1
- data/lib/bolt/puppetdb/config.rb +1 -1
- data/lib/bolt/puppetfile.rb +160 -0
- data/lib/bolt/puppetfile/installer.rb +43 -0
- data/lib/bolt/puppetfile/module.rb +66 -0
- data/lib/bolt/r10k_log_proxy.rb +1 -1
- data/lib/bolt/rerun.rb +2 -2
- data/lib/bolt/result.rb +23 -0
- data/lib/bolt/shell.rb +1 -1
- data/lib/bolt/shell/bash.rb +7 -7
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/base.rb +1 -1
- data/lib/bolt/transport/docker/connection.rb +10 -10
- data/lib/bolt/transport/local/connection.rb +3 -3
- data/lib/bolt/transport/orch.rb +3 -3
- data/lib/bolt/transport/ssh.rb +1 -1
- data/lib/bolt/transport/ssh/connection.rb +6 -6
- data/lib/bolt/transport/ssh/exec_connection.rb +5 -5
- data/lib/bolt/transport/winrm.rb +1 -1
- data/lib/bolt/transport/winrm/connection.rb +9 -9
- data/lib/bolt/util.rb +2 -2
- data/lib/bolt/util/puppet_log_level.rb +4 -3
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +2 -2
- data/lib/bolt_server/config.rb +1 -1
- data/lib/bolt_server/file_cache.rb +1 -1
- data/lib/bolt_server/transport_app.rb +189 -14
- data/lib/bolt_spec/plans.rb +1 -1
- data/lib/bolt_spec/run.rb +3 -0
- metadata +12 -12
data/lib/bolt/outputter/json.rb
CHANGED
@@ -83,6 +83,17 @@ module Bolt
|
|
83
83
|
@stream.puts result.to_json
|
84
84
|
end
|
85
85
|
|
86
|
+
def print_topics(topics)
|
87
|
+
print_table('topics' => topics)
|
88
|
+
end
|
89
|
+
|
90
|
+
def print_guide(guide, topic)
|
91
|
+
@stream.puts({
|
92
|
+
'topic' => topic,
|
93
|
+
'guide' => guide
|
94
|
+
}.to_json)
|
95
|
+
end
|
96
|
+
|
86
97
|
def print_puppetfile_result(success, puppetfile, moduledir)
|
87
98
|
@stream.puts({ "success": success,
|
88
99
|
"puppetfile": puppetfile,
|
@@ -7,7 +7,7 @@ module Bolt
|
|
7
7
|
class Logger < Bolt::Outputter
|
8
8
|
def initialize(verbose, trace)
|
9
9
|
super(false, verbose, trace)
|
10
|
-
@logger =
|
10
|
+
@logger = Bolt::Logger.logger(self)
|
11
11
|
end
|
12
12
|
|
13
13
|
def handle_event(event)
|
@@ -40,13 +40,13 @@ module Bolt
|
|
40
40
|
|
41
41
|
def log_plan_start(event)
|
42
42
|
plan = event[:plan]
|
43
|
-
@logger.
|
43
|
+
@logger.info("Starting: plan #{plan}")
|
44
44
|
end
|
45
45
|
|
46
46
|
def log_plan_finish(event)
|
47
47
|
plan = event[:plan]
|
48
48
|
duration = event[:duration]
|
49
|
-
@logger.
|
49
|
+
@logger.info("Finished: plan #{plan} in #{duration.round(2)} sec")
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -86,6 +86,21 @@ module Bolt
|
|
86
86
|
total_msg << " in #{duration_to_string(elapsed_time)}" unless elapsed_time.nil?
|
87
87
|
@stream.puts colorize(:rainbow, total_msg)
|
88
88
|
end
|
89
|
+
|
90
|
+
def print_guide(guide, _topic)
|
91
|
+
@stream.puts colorize(:rainbow, guide)
|
92
|
+
end
|
93
|
+
|
94
|
+
def print_topics(topics)
|
95
|
+
content = String.new("Available topics are:\n")
|
96
|
+
content += topics.join("\n")
|
97
|
+
content += "\n\nUse `bolt guide <topic>` to view a specific guide."
|
98
|
+
@stream.puts colorize(:rainbow, content)
|
99
|
+
end
|
100
|
+
|
101
|
+
def print_message(message)
|
102
|
+
@stream.puts colorize(:rainbow, message)
|
103
|
+
end
|
89
104
|
end
|
90
105
|
end
|
91
106
|
end
|
data/lib/bolt/pal.rb
CHANGED
@@ -65,9 +65,9 @@ module Bolt
|
|
65
65
|
@resource_types = resource_types
|
66
66
|
@project = project
|
67
67
|
|
68
|
-
@logger =
|
68
|
+
@logger = Bolt::Logger.logger(self)
|
69
69
|
if modulepath && !modulepath.empty?
|
70
|
-
@logger.
|
70
|
+
@logger.debug("Loading modules from #{@modulepath.join(File::PATH_SEPARATOR)}")
|
71
71
|
end
|
72
72
|
|
73
73
|
@loaded = false
|
@@ -76,7 +76,7 @@ module Bolt
|
|
76
76
|
# Puppet logging is global so this is class method to avoid confusion
|
77
77
|
def self.configure_logging
|
78
78
|
Puppet::Util::Log.destinations.clear
|
79
|
-
Puppet::Util::Log.newdestination(
|
79
|
+
Puppet::Util::Log.newdestination(Bolt::Logger.logger('Puppet'))
|
80
80
|
# Defer all log level decisions to the Logging library by telling Puppet
|
81
81
|
# to log everything
|
82
82
|
Puppet.settings[:log_level] = 'debug'
|
@@ -141,6 +141,19 @@ module Bolt
|
|
141
141
|
end
|
142
142
|
end
|
143
143
|
|
144
|
+
def detect_project_conflict(project, environment)
|
145
|
+
return unless project && project.load_as_module?
|
146
|
+
# The environment modulepath has stripped out non-existent directories,
|
147
|
+
# so we don't need to check for them
|
148
|
+
modules = environment.modulepath.flat_map do |path|
|
149
|
+
Dir.children(path).select { |name| Puppet::Module.is_module_directory?(name, path) }
|
150
|
+
end
|
151
|
+
if modules.include?(project.name)
|
152
|
+
Bolt::Logger.warn_once("project shadows module",
|
153
|
+
"The project '#{project.name}' shadows an existing module of the same name")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
144
157
|
# Runs a block in a PAL script compiler configured for Bolt. Catches
|
145
158
|
# exceptions thrown by the block and re-raises them ensuring they are
|
146
159
|
# Bolt::Errors since the script compiler block will squash all exceptions.
|
@@ -149,15 +162,13 @@ module Bolt
|
|
149
162
|
setup
|
150
163
|
r = Puppet::Pal.in_tmp_environment('bolt', modulepath: @modulepath, facts: {}) do |pal|
|
151
164
|
# Only load the project if it a) exists, b) has a name it can be loaded with
|
152
|
-
bolt_project
|
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,
|
165
|
+
Puppet.override(bolt_project: @project,
|
159
166
|
yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
|
160
|
-
|
167
|
+
# Because this has the side effect of loading and caching the list
|
168
|
+
# of modules, it must happen *after* we have overridden
|
169
|
+
# bolt_project or the project will be ignored
|
170
|
+
detect_project_conflict(@project, Puppet.lookup(:environments).get('bolt'))
|
171
|
+
pal.with_script_compiler(set_local_facts: false) do |compiler|
|
161
172
|
alias_types(compiler)
|
162
173
|
register_resource_types(Puppet.lookup(:loaders)) if @resource_types
|
163
174
|
begin
|
@@ -409,7 +420,7 @@ module Bolt
|
|
409
420
|
end
|
410
421
|
params[name] = { 'type' => type_str }
|
411
422
|
params[name]['sensitive'] = param.type_expr.instance_of?(Puppet::Pops::Types::PSensitiveType)
|
412
|
-
params[name]['default_value'] = param.value
|
423
|
+
params[name]['default_value'] = param.value unless param.value.nil?
|
413
424
|
params[name]['description'] = param.description if param.description
|
414
425
|
end
|
415
426
|
{
|
@@ -7,7 +7,7 @@ module Bolt
|
|
7
7
|
class YamlPlan
|
8
8
|
class Evaluator
|
9
9
|
def initialize(analytics = Bolt::Analytics::NoopClient.new)
|
10
|
-
@logger =
|
10
|
+
@logger = Bolt::Logger.logger(self)
|
11
11
|
@analytics = analytics
|
12
12
|
@evaluator = Puppet::Pops::Parser::EvaluatingParser.new
|
13
13
|
end
|
@@ -21,10 +21,18 @@ module Bolt
|
|
21
21
|
validate_path
|
22
22
|
|
23
23
|
plan_object = parse_plan
|
24
|
+
param_descriptions = plan_object.parameters.map do |param|
|
25
|
+
str = String.new("# @param #{param.name}")
|
26
|
+
str << " #{param.description}" if param.description
|
27
|
+
str
|
28
|
+
end.join("\n")
|
24
29
|
|
25
|
-
plan_string = String.new(
|
26
|
-
|
27
|
-
|
30
|
+
plan_string = String.new('')
|
31
|
+
plan_string << "# #{plan_object.description}\n" if plan_object.description
|
32
|
+
plan_string << "# WARNING: This is an autogenerated plan. It may not behave as expected.\n"
|
33
|
+
plan_string << "#{param_descriptions}\n" unless param_descriptions.empty?
|
34
|
+
|
35
|
+
plan_string << "plan #{plan_object.name}("
|
28
36
|
# Parameters are Bolt::PAL::YamlPlan::Parameter
|
29
37
|
plan_object.parameters&.each_with_index do |param, i|
|
30
38
|
plan_string << param.transpile
|
data/lib/bolt/plugin/puppetdb.rb
CHANGED
@@ -19,7 +19,7 @@ module Bolt
|
|
19
19
|
def initialize(config:, context:)
|
20
20
|
pdb_config = Bolt::PuppetDB::Config.load_config(config, context.boltdir)
|
21
21
|
@puppetdb_client = Bolt::PuppetDB::Client.new(pdb_config)
|
22
|
-
@logger =
|
22
|
+
@logger = Bolt::Logger.logger(self)
|
23
23
|
end
|
24
24
|
|
25
25
|
def name
|
data/lib/bolt/project.rb
CHANGED
@@ -17,37 +17,53 @@ module Bolt
|
|
17
17
|
}.freeze
|
18
18
|
|
19
19
|
attr_reader :path, :data, :config_file, :inventory_file, :modulepath, :hiera_config,
|
20
|
-
:puppetfile, :rerunfile, :type, :resource_types, :
|
20
|
+
:puppetfile, :rerunfile, :type, :resource_types, :logs, :project_file,
|
21
21
|
:deprecations, :downloads, :plans_path
|
22
22
|
|
23
|
-
def self.default_project
|
24
|
-
create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
|
23
|
+
def self.default_project(logs = [])
|
24
|
+
create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user', logs)
|
25
25
|
# If homedir isn't defined use the system config path
|
26
26
|
rescue ArgumentError
|
27
|
-
create_project(Bolt::Config.system_path, 'system')
|
27
|
+
create_project(Bolt::Config.system_path, 'system', logs)
|
28
28
|
end
|
29
29
|
|
30
30
|
# Search recursively up the directory hierarchy for the Project. Look for a
|
31
31
|
# directory called Boltdir or a file called bolt.yaml (for a control repo
|
32
32
|
# type Project). Otherwise, repeat the check on each directory up the
|
33
33
|
# hierarchy, falling back to the default if we reach the root.
|
34
|
-
def self.find_boltdir(dir)
|
34
|
+
def self.find_boltdir(dir, logs = [])
|
35
35
|
dir = Pathname.new(dir)
|
36
36
|
|
37
37
|
if (dir + BOLTDIR_NAME).directory?
|
38
|
-
create_project(dir + BOLTDIR_NAME, 'embedded')
|
38
|
+
create_project(dir + BOLTDIR_NAME, 'embedded', logs)
|
39
39
|
elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
|
40
|
-
create_project(dir, 'local')
|
40
|
+
create_project(dir, 'local', logs)
|
41
41
|
elsif dir.root?
|
42
|
-
default_project
|
42
|
+
default_project(logs)
|
43
43
|
else
|
44
|
-
|
44
|
+
logs << { debug: "Did not detect Boltdir, bolt.yaml, or bolt-project.yaml at '#{dir}'. "\
|
45
|
+
"This directory won't be loaded as a project." }
|
46
|
+
find_boltdir(dir.parent, logs)
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
48
|
-
def self.create_project(path, type = 'option')
|
50
|
+
def self.create_project(path, type = 'option', logs = [])
|
49
51
|
fullpath = Pathname.new(path).expand_path
|
50
52
|
|
53
|
+
if type == 'user'
|
54
|
+
begin
|
55
|
+
# This is already expanded if the type is user
|
56
|
+
FileUtils.mkdir_p(path)
|
57
|
+
rescue StandardError
|
58
|
+
logs << { warn: "Could not create default project at #{path}. Continuing without a writeable project. "\
|
59
|
+
"Log and rerun files will not be written." }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if type == 'option' && !File.directory?(path)
|
64
|
+
raise Bolt::Error.new("Could not find project at #{path}", "bolt/project-error")
|
65
|
+
end
|
66
|
+
|
51
67
|
if !Bolt::Util.windows? && type != 'environment' && fullpath.world_writable?
|
52
68
|
raise Bolt::Error.new(
|
53
69
|
"Project directory '#{fullpath}' is world-writable which poses a security risk. Set "\
|
@@ -58,15 +74,18 @@ module Bolt
|
|
58
74
|
|
59
75
|
project_file = File.join(fullpath, 'bolt-project.yaml')
|
60
76
|
data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
|
61
|
-
|
77
|
+
default = type =~ /user|system/ ? 'default ' : ''
|
78
|
+
exist = File.exist?(File.expand_path(project_file))
|
79
|
+
logs << { info: "Loaded #{default}project from '#{fullpath}'" } if exist
|
80
|
+
new(data, path, type, logs)
|
62
81
|
end
|
63
82
|
|
64
|
-
def initialize(raw_data, path, type = 'option')
|
83
|
+
def initialize(raw_data, path, type = 'option', logs = [])
|
65
84
|
@path = Pathname.new(path).expand_path
|
66
85
|
|
67
86
|
@project_file = @path + 'bolt-project.yaml'
|
68
87
|
|
69
|
-
@
|
88
|
+
@logs = logs
|
70
89
|
@deprecations = []
|
71
90
|
if (@path + 'bolt.yaml').file? && project_file?
|
72
91
|
msg = "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
|
@@ -88,7 +107,7 @@ module Bolt
|
|
88
107
|
tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys
|
89
108
|
if tc.any?
|
90
109
|
msg = "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}"
|
91
|
-
@
|
110
|
+
@logs << { warn: msg }
|
92
111
|
end
|
93
112
|
|
94
113
|
@data = raw_data.reject { |k, _| Bolt::Config::INVENTORY_OPTIONS.include?(k) }
|
@@ -98,7 +117,7 @@ module Bolt
|
|
98
117
|
@config_file = if (Bolt::Config::BOLT_OPTIONS & @data.keys).any?
|
99
118
|
if (@path + 'bolt.yaml').file?
|
100
119
|
msg = "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
|
101
|
-
@
|
120
|
+
@logs << { warn: msg }
|
102
121
|
end
|
103
122
|
@project_file
|
104
123
|
else
|
@@ -114,7 +133,9 @@ module Bolt
|
|
114
133
|
# This API is used to prepend the project as a module to Puppet's internal
|
115
134
|
# module_references list. CHANGE AT YOUR OWN RISK
|
116
135
|
def to_h
|
117
|
-
{ path: @path.to_s,
|
136
|
+
{ path: @path.to_s,
|
137
|
+
name: name,
|
138
|
+
load_as_module?: load_as_module? }
|
118
139
|
end
|
119
140
|
|
120
141
|
def eql?(other)
|
@@ -126,6 +147,10 @@ module Bolt
|
|
126
147
|
@project_file.file?
|
127
148
|
end
|
128
149
|
|
150
|
+
def load_as_module?
|
151
|
+
!name.nil?
|
152
|
+
end
|
153
|
+
|
129
154
|
def name
|
130
155
|
@data['name']
|
131
156
|
end
|
@@ -138,6 +163,10 @@ module Bolt
|
|
138
163
|
@data['plans']
|
139
164
|
end
|
140
165
|
|
166
|
+
def modules
|
167
|
+
@data['modules']
|
168
|
+
end
|
169
|
+
|
141
170
|
def validate
|
142
171
|
if name
|
143
172
|
if name !~ Bolt::Module::MODULE_NAME_REGEX
|
@@ -151,7 +180,7 @@ module Bolt
|
|
151
180
|
end
|
152
181
|
else
|
153
182
|
message = "No project name is specified in bolt-project.yaml. Project-level content will not be available."
|
154
|
-
@
|
183
|
+
@logs << { warn: message }
|
155
184
|
end
|
156
185
|
|
157
186
|
%w[tasks plans].each do |conf|
|
@@ -159,6 +188,23 @@ module Bolt
|
|
159
188
|
raise Bolt::ValidationError, "'#{conf}' in bolt-project.yaml must be an array"
|
160
189
|
end
|
161
190
|
end
|
191
|
+
|
192
|
+
if @data['modules']
|
193
|
+
unless @data['modules'].is_a?(Array)
|
194
|
+
raise Bolt::ValidationError, "'modules' in bolt-project.yaml must be an array"
|
195
|
+
end
|
196
|
+
|
197
|
+
@data['modules'].each do |mod|
|
198
|
+
next if mod.is_a?(Hash)
|
199
|
+
raise Bolt::ValidationError, "Module declaration #{mod.inspect} must be a hash"
|
200
|
+
end
|
201
|
+
|
202
|
+
unknown_keys = data['modules'].flat_map(&:keys).uniq - ['name']
|
203
|
+
if unknown_keys.any?
|
204
|
+
@logs << { warn: "Module declarations in bolt-project.yaml only support a name key. Ignoring "\
|
205
|
+
"unsupported keys: #{unknown_keys.join(', ')}." }
|
206
|
+
end
|
207
|
+
end
|
162
208
|
end
|
163
209
|
|
164
210
|
def check_deprecated_file
|
@@ -0,0 +1,138 @@
|
|
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
|