bolt 2.33.1 → 2.37.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 (69) 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 +48 -16
  15. data/lib/bolt/cli.rb +95 -227
  16. data/lib/bolt/config.rb +145 -54
  17. data/lib/bolt/config/options.rb +76 -10
  18. data/lib/bolt/config/transport/base.rb +10 -19
  19. data/lib/bolt/config/transport/local.rb +0 -7
  20. data/lib/bolt/config/transport/options.rb +1 -1
  21. data/lib/bolt/config/transport/ssh.rb +8 -14
  22. data/lib/bolt/config/validator.rb +231 -0
  23. data/lib/bolt/error.rb +33 -3
  24. data/lib/bolt/executor.rb +92 -6
  25. data/lib/bolt/inventory/group.rb +2 -1
  26. data/lib/bolt/module_installer.rb +1 -1
  27. data/lib/bolt/module_installer/specs/forge_spec.rb +5 -4
  28. data/lib/bolt/module_installer/specs/git_spec.rb +4 -3
  29. data/lib/bolt/outputter/human.rb +21 -9
  30. data/lib/bolt/outputter/rainbow.rb +1 -1
  31. data/lib/bolt/pal.rb +19 -7
  32. data/lib/bolt/pal/yaml_plan.rb +7 -0
  33. data/lib/bolt/plan_creator.rb +160 -0
  34. data/lib/bolt/plugin.rb +42 -13
  35. data/lib/bolt/plugin/cache.rb +76 -0
  36. data/lib/bolt/plugin/module.rb +4 -4
  37. data/lib/bolt/project.rb +46 -40
  38. data/lib/bolt/project_manager.rb +199 -0
  39. data/lib/bolt/{project_migrator/config.rb → project_manager/config_migrator.rb} +43 -5
  40. data/lib/bolt/{project_migrator/inventory.rb → project_manager/inventory_migrator.rb} +5 -5
  41. data/lib/bolt/{project_migrator/base.rb → project_manager/migrator.rb} +2 -2
  42. data/lib/bolt/{project_migrator/modules.rb → project_manager/module_migrator.rb} +3 -3
  43. data/lib/bolt/puppetdb/client.rb +3 -2
  44. data/lib/bolt/puppetdb/config.rb +9 -8
  45. data/lib/bolt/rerun.rb +1 -5
  46. data/lib/bolt/shell/bash.rb +8 -2
  47. data/lib/bolt/shell/powershell.rb +17 -1
  48. data/lib/bolt/task/run.rb +1 -1
  49. data/lib/bolt/transport/orch.rb +0 -5
  50. data/lib/bolt/transport/orch/connection.rb +10 -3
  51. data/lib/bolt/transport/remote.rb +1 -1
  52. data/lib/bolt/transport/ssh/exec_connection.rb +6 -2
  53. data/lib/bolt/util.rb +41 -7
  54. data/lib/bolt/version.rb +1 -1
  55. data/lib/bolt/yarn.rb +23 -0
  56. data/lib/bolt_server/base_config.rb +3 -1
  57. data/lib/bolt_server/config.rb +3 -1
  58. data/lib/bolt_server/file_cache.rb +2 -0
  59. data/lib/bolt_server/plugin.rb +13 -0
  60. data/lib/bolt_server/plugin/puppet_connect_data.rb +37 -0
  61. data/lib/bolt_server/schemas/connect-data.json +22 -0
  62. data/lib/bolt_server/schemas/partials/task.json +2 -2
  63. data/lib/bolt_server/transport_app.rb +72 -13
  64. data/lib/bolt_spec/plans/mock_executor.rb +4 -1
  65. data/libexec/apply_catalog.rb +1 -1
  66. data/libexec/custom_facts.rb +1 -1
  67. data/libexec/query_resources.rb +1 -1
  68. metadata +15 -13
  69. data/lib/bolt/project_migrator.rb +0 -80
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'bolt/error'
5
+ require 'bolt/util'
6
+
7
+ module Bolt
8
+ class Plugin
9
+ class Cache
10
+ attr_reader :reference, :cache_file, :default_config, :id
11
+
12
+ def initialize(reference, cache_file, default_config)
13
+ @reference = reference
14
+ @cache_file = cache_file
15
+ @default_config = default_config
16
+ end
17
+
18
+ def read_and_clean_cache
19
+ return if ttl == 0
20
+ validate
21
+
22
+ # Luckily we don't need to use a serious hash algorithm
23
+ require 'digest/bubblebabble'
24
+ r = reference.select { |k, _| k == '_cache' }.sort.to_s
25
+ @id = Digest::SHA2.bubblebabble(r)[0..20]
26
+
27
+ unmodified = true
28
+ # First remove any cache entries past their ttl
29
+ # This prevents removing plugins from leaving orphaned cache entries
30
+ cache.delete_if do |_, entry|
31
+ expired = Time.now - Time.parse(entry['mtime']) >= entry['ttl']
32
+ unmodified = false if expired
33
+ expired
34
+ end
35
+ File.write(cache_file, cache.to_json) unless cache.empty? || unmodified
36
+
37
+ cache.dig(id, 'result')
38
+ end
39
+
40
+ private def cache
41
+ @cache ||= Bolt::Util.read_optional_json_file(@cache_file, 'cache')
42
+ end
43
+
44
+ def write_cache(result)
45
+ cache.merge!({ id => { 'result' => result,
46
+ 'mtime' => Time.now,
47
+ 'ttl' => ttl } })
48
+ FileUtils.touch(cache_file)
49
+ File.write(cache_file, cache.to_json)
50
+ end
51
+
52
+ def validate
53
+ # The default cache `plugin-cache` will be validated by the config
54
+ # validator
55
+ return if reference['_cache'].nil?
56
+ r = reference['_cache']
57
+ unless r.is_a?(Hash)
58
+ raise Bolt::ValidationError,
59
+ "_cache must be a Hash, received #{r.class}: #{r.inspect}"
60
+ end
61
+
62
+ unless r.key?('ttl')
63
+ raise Bolt::ValidationError, "_cache must set 'ttl' key."
64
+ end
65
+
66
+ unless r['ttl'] >= 0
67
+ raise Bolt::ValidationError, "'ttl' key under '_cache' must be a minimum of 0."
68
+ end
69
+ end
70
+
71
+ private def ttl
72
+ @ttl ||= reference.dig('_cache', 'ttl') || default_config['ttl']
73
+ end
74
+ end
75
+ end
76
+ end
@@ -12,15 +12,15 @@ module Bolt
12
12
  end
13
13
  end
14
14
 
15
- def self.load(name, modules, opts)
16
- mod = modules[name]
17
- if mod&.plugin?
15
+ # mod should not be nil
16
+ def self.load(mod, opts)
17
+ if mod.plugin?
18
18
  opts[:mod] = mod
19
19
  plugin = Bolt::Plugin::Module.new(**opts)
20
20
  plugin.setup
21
21
  plugin
22
22
  else
23
- raise PluginError::Unknown, name
23
+ raise PluginError::Unknown, mod.name
24
24
  end
25
25
  end
26
26
 
@@ -2,24 +2,19 @@
2
2
 
3
3
  require 'pathname'
4
4
  require 'bolt/config'
5
+ require 'bolt/config/validator'
5
6
  require 'bolt/pal'
6
7
  require 'bolt/module'
7
8
 
8
9
  module Bolt
9
10
  class Project
10
11
  BOLTDIR_NAME = 'Boltdir'
11
- PROJECT_SETTINGS = {
12
- "name" => "The name of the project",
13
- "plans" => "An array of plan names to show, if they exist in the project."\
14
- "These plans are included in `bolt plan show` output",
15
- "tasks" => "An array of task names to show, if they exist in the project."\
16
- "These tasks are included in `bolt task show` output"
17
- }.freeze
12
+ CONFIG_NAME = 'bolt-project.yaml'
18
13
 
19
14
  attr_reader :path, :data, :config_file, :inventory_file, :hiera_config,
20
15
  :puppetfile, :rerunfile, :type, :resource_types, :logs, :project_file,
21
16
  :deprecations, :downloads, :plans_path, :modulepath, :managed_moduledir,
22
- :backup_dir
17
+ :backup_dir, :cache_file
23
18
 
24
19
  def self.default_project(logs = [])
25
20
  create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user', logs)
@@ -32,23 +27,31 @@ module Bolt
32
27
  # directory called Boltdir or a file called bolt.yaml (for a control repo
33
28
  # type Project). Otherwise, repeat the check on each directory up the
34
29
  # hierarchy, falling back to the default if we reach the root.
35
- def self.find_boltdir(dir, logs = [])
30
+ def self.find_boltdir(dir, logs = [], deprecations = [])
36
31
  dir = Pathname.new(dir)
37
32
 
38
33
  if (dir + BOLTDIR_NAME).directory?
39
34
  create_project(dir + BOLTDIR_NAME, 'embedded', logs)
40
- elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
35
+ elsif (dir + 'bolt.yaml').file?
36
+ command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
37
+ msg = "Configuration file #{dir + 'bolt.yaml'} is deprecated and will be "\
38
+ "removed in Bolt 3.0.\nUpdate your Bolt project to the latest Bolt practices "\
39
+ "using #{command}"
40
+ deprecations << { type: "Project level bolt.yaml",
41
+ msg: msg }
42
+ create_project(dir, 'local', logs, deprecations)
43
+ elsif (dir + CONFIG_NAME).file?
41
44
  create_project(dir, 'local', logs)
42
45
  elsif dir.root?
43
46
  default_project(logs)
44
47
  else
45
48
  logs << { debug: "Did not detect Boltdir, bolt.yaml, or bolt-project.yaml at '#{dir}'. "\
46
49
  "This directory won't be loaded as a project." }
47
- find_boltdir(dir.parent, logs)
50
+ find_boltdir(dir.parent, logs, deprecations)
48
51
  end
49
52
  end
50
53
 
51
- def self.create_project(path, type = 'option', logs = [])
54
+ def self.create_project(path, type = 'option', logs = [], deprecations = [])
52
55
  fullpath = Pathname.new(path).expand_path
53
56
 
54
57
  if type == 'user'
@@ -73,21 +76,36 @@ module Bolt
73
76
  )
74
77
  end
75
78
 
76
- project_file = File.join(fullpath, 'bolt-project.yaml')
77
- data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
78
- default = type =~ /user|system/ ? 'default ' : ''
79
- exist = File.exist?(File.expand_path(project_file))
79
+ project_file = File.join(fullpath, CONFIG_NAME)
80
+ data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
81
+ default = type =~ /user|system/ ? 'default ' : ''
82
+ exist = File.exist?(File.expand_path(project_file))
83
+
80
84
  logs << { info: "Loaded #{default}project from '#{fullpath}'" } if exist
81
- new(data, path, type, logs)
82
- end
83
85
 
84
- def initialize(raw_data, path, type = 'option', logs = [])
85
- @path = Pathname.new(path).expand_path
86
+ # Validate the config against the schema. This will raise a single error
87
+ # with all validation errors.
88
+ schema = Bolt::Config::OPTIONS.slice(*Bolt::Config::BOLT_PROJECT_OPTIONS)
89
+
90
+ Bolt::Config::Validator.new.tap do |validator|
91
+ validator.validate(data, schema, project_file)
92
+
93
+ validator.warnings.each { |warning| logs << { warn: warning } }
94
+
95
+ validator.deprecations.each do |dep|
96
+ deprecations << { type: "#{CONFIG_NAME} #{dep[:option]}", msg: dep[:message] }
97
+ end
98
+ end
86
99
 
87
- @project_file = @path + 'bolt-project.yaml'
100
+ new(data, path, type, logs, deprecations)
101
+ end
102
+
103
+ def initialize(raw_data, path, type = 'option', logs = [], deprecations = [])
104
+ @path = Pathname.new(path).expand_path
105
+ @project_file = @path + CONFIG_NAME
106
+ @logs = logs
107
+ @deprecations = deprecations
88
108
 
89
- @logs = logs
90
- @deprecations = []
91
109
  if (@path + 'bolt.yaml').file? && project_file?
92
110
  msg = "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
93
111
  "Transport config should be set in inventory.yaml, all other config should be set in "\
@@ -105,6 +123,7 @@ module Bolt
105
123
  @plans_path = @path + 'plans'
106
124
  @managed_moduledir = @path + '.modules'
107
125
  @backup_dir = @path + '.bolt-bak'
126
+ @cache_file = @path + '.plugin_cache.json'
108
127
 
109
128
  tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys
110
129
  if tc.any?
@@ -173,6 +192,10 @@ module Bolt
173
192
  @data['plans']
174
193
  end
175
194
 
195
+ def plugin_cache
196
+ @data['plugin-cache']
197
+ end
198
+
176
199
  def modules
177
200
  @modules ||= @data['modules']&.map do |mod|
178
201
  if mod.is_a?(String)
@@ -198,23 +221,6 @@ module Bolt
198
221
  message = "No project name is specified in bolt-project.yaml. Project-level content will not be available."
199
222
  @logs << { warn: message }
200
223
  end
201
-
202
- %w[tasks plans].each do |conf|
203
- unless @data.fetch(conf, []).is_a?(Array)
204
- raise Bolt::ValidationError, "'#{conf}' in bolt-project.yaml must be an array"
205
- end
206
- end
207
-
208
- if @data['modules']
209
- unless @data['modules'].is_a?(Array)
210
- raise Bolt::ValidationError, "'modules' in bolt-project.yaml must be an array"
211
- end
212
-
213
- @data['modules'].each do |spec|
214
- next if spec.is_a?(Hash) || spec.is_a?(String)
215
- raise Bolt::ValidationError, "Module specification #{spec.inspect} must be a hash or string"
216
- end
217
- end
218
224
  end
219
225
 
220
226
  def check_deprecated_file
@@ -0,0 +1,199 @@
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
+ Bolt::ModuleInstaller.new(@outputter, @pal).install(modules, puppetfile, moduledir)
107
+ end
108
+
109
+ data = { 'name' => project_name }
110
+ data['modules'] = modules || []
111
+
112
+ begin
113
+ File.write(config.to_path, data.to_yaml)
114
+ rescue StandardError => e
115
+ raise Bolt::FileError.new("Could not create bolt-project.yaml at #{project}: #{e.message}", nil)
116
+ end
117
+
118
+ unless inventoryfile.exist?
119
+ begin
120
+ File.write(inventoryfile.to_path, INVENTORY_TEMPLATE)
121
+ rescue StandardError => e
122
+ raise Bolt::FileError.new("Could not create inventory.yaml at #{project}: #{e.message}", nil)
123
+ end
124
+ end
125
+
126
+ @outputter.print_message("Successfully created Bolt project at #{project}")
127
+
128
+ 0
129
+ end
130
+
131
+ # Migrates a project to use the latest file versions and best practices.
132
+ #
133
+ def migrate
134
+ unless $stdin.tty?
135
+ raise Bolt::Error.new(
136
+ "stdin is not a tty, unable to migrate project",
137
+ 'bolt/stdin-not-a-tty-error'
138
+ )
139
+ end
140
+
141
+ @outputter.print_message("Migrating project #{@config.project.path}\n\n")
142
+
143
+ @outputter.print_action_step(
144
+ "Migrating a Bolt project may make irreversible changes to the project's "\
145
+ "configuration and inventory files. Before continuing, make sure the "\
146
+ "project has a backup or uses a version control system."
147
+ )
148
+
149
+ return 0 unless Bolt::Util.prompt_yes_no("Continue with project migration?", @outputter)
150
+
151
+ @outputter.print_message('')
152
+
153
+ ok = migrate_inventory && migrate_config && migrate_modules
154
+
155
+ if ok
156
+ @outputter.print_message("Project successfully migrated")
157
+ else
158
+ @outputter.print_error("Project could not be migrated completely")
159
+ end
160
+
161
+ ok ? 0 : 1
162
+ end
163
+
164
+ # Migrates the project-level configuration file to the latest version.
165
+ #
166
+ private def migrate_config
167
+ migrator = ConfigMigrator.new(@outputter)
168
+
169
+ migrator.migrate(
170
+ @config.project.config_file,
171
+ @config.project.project_file,
172
+ @config.inventoryfile || @config.project.inventory_file,
173
+ @config.project.backup_dir
174
+ )
175
+ end
176
+
177
+ # Migrates the inventory file to the latest version.
178
+ #
179
+ private def migrate_inventory
180
+ migrator = InventoryMigrator.new(@outputter)
181
+
182
+ migrator.migrate(
183
+ @config.inventoryfile || @config.project.inventory_file,
184
+ @config.project.backup_dir
185
+ )
186
+ end
187
+
188
+ # Migrates the project's modules to use current best practices.
189
+ #
190
+ private def migrate_modules
191
+ migrator = ModuleMigrator.new(@outputter)
192
+
193
+ migrator.migrate(
194
+ @config.project,
195
+ @config.modulepath
196
+ )
197
+ end
198
+ end
199
+ 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)
@@ -54,14 +55,51 @@ module Bolt
54
55
  @outputter.print_action_step("Renaming bolt.yaml to bolt-project.yaml")
55
56
  FileUtils.mv(config_file, project_file)
56
57
 
58
+ command = Bolt::Util.powershell? ? 'Get-Help about_bolt_project' : 'bolt guide project'
57
59
  @outputter.print_action_step(
58
60
  "Successfully migrated config. Please add a 'name' key to bolt-project.yaml "\
59
61
  "to use project-level tasks and plans. Learn more about projects by running "\
60
- "'bolt guide project'."
62
+ "'#{command}'."
61
63
  )
62
64
 
63
65
  true
64
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
+ [%w[apply_settings apply-settings], %w[plugin_hooks plugin-hooks]].each do |old, new|
76
+ next unless data.key?(old)
77
+
78
+ if data.key?(new)
79
+ @outputter.print_action_step("Removing deprecated option '#{old}'")
80
+ data.delete(old)
81
+ else
82
+ @outputter.print_action_step("Updating deprecated option '#{old}' to '#{new}'")
83
+ data[new] = data.delete(old)
84
+ end
85
+
86
+ modified = true
87
+ end
88
+
89
+ if modified
90
+ begin
91
+ File.write(project_file, data.to_yaml)
92
+ rescue StandardError => e
93
+ raise Bolt::FileError.new("#{e.message}; unable to write config.", project_file)
94
+ end
95
+
96
+ @outputter.print_action_step("Successfully updated project configuration #{project_file}")
97
+ else
98
+ @outputter.print_action_step("Project configuration is up to date, nothing to do.")
99
+ end
100
+
101
+ true
102
+ end
65
103
  end
66
104
  end
67
105
  end