vagrant-bolt 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +124 -0
  5. data/.travis.yml +28 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +37 -0
  8. data/LICENSE +12 -0
  9. data/Puppetfile +7 -0
  10. data/README.md +431 -0
  11. data/Rakefile +19 -0
  12. data/Vagrantfile +47 -0
  13. data/acceptance/artifacts/.keep +0 -0
  14. data/acceptance/components/bolt_spec.rb +98 -0
  15. data/acceptance/skeletons/advanced/Vagrantfile +26 -0
  16. data/acceptance/skeletons/base/Vagrantfile +11 -0
  17. data/acceptance/skeletons/base/modules/facts/CHANGELOG.md +26 -0
  18. data/acceptance/skeletons/base/modules/facts/CONTRIBUTING.md +279 -0
  19. data/acceptance/skeletons/base/modules/facts/Gemfile +98 -0
  20. data/acceptance/skeletons/base/modules/facts/LICENSE +201 -0
  21. data/acceptance/skeletons/base/modules/facts/README.md +45 -0
  22. data/acceptance/skeletons/base/modules/facts/Rakefile +8 -0
  23. data/acceptance/skeletons/base/modules/facts/checksums.json +42 -0
  24. data/acceptance/skeletons/base/modules/facts/lib/puppet/functions/facts/group_by.rb +14 -0
  25. data/acceptance/skeletons/base/modules/facts/metadata.json +62 -0
  26. data/acceptance/skeletons/base/modules/facts/plans/info.pp +16 -0
  27. data/acceptance/skeletons/base/modules/facts/plans/init.pp +13 -0
  28. data/acceptance/skeletons/base/modules/facts/tasks/bash.json +5 -0
  29. data/acceptance/skeletons/base/modules/facts/tasks/bash.sh +93 -0
  30. data/acceptance/skeletons/base/modules/facts/tasks/init.json +10 -0
  31. data/acceptance/skeletons/base/modules/facts/tasks/powershell.json +4 -0
  32. data/acceptance/skeletons/base/modules/facts/tasks/powershell.ps1 +56 -0
  33. data/acceptance/skeletons/base/modules/facts/tasks/ruby.json +4 -0
  34. data/acceptance/skeletons/base/modules/facts/tasks/ruby.rb +40 -0
  35. data/acceptance/skeletons/provisioner/Vagrantfile +19 -0
  36. data/acceptance/skeletons/trigger/Vagrantfile +22 -0
  37. data/acceptance/vagrant-spec.config.rb +22 -0
  38. data/lib/vagrant-bolt.rb +57 -0
  39. data/lib/vagrant-bolt/command.rb +65 -0
  40. data/lib/vagrant-bolt/config.rb +6 -0
  41. data/lib/vagrant-bolt/config/bolt.rb +135 -0
  42. data/lib/vagrant-bolt/config/global.rb +172 -0
  43. data/lib/vagrant-bolt/config_builder.rb +11 -0
  44. data/lib/vagrant-bolt/config_builder/config.rb +150 -0
  45. data/lib/vagrant-bolt/config_builder/monkey_patches.rb +71 -0
  46. data/lib/vagrant-bolt/config_builder/provisioner.rb +106 -0
  47. data/lib/vagrant-bolt/config_builder/triggers.rb +29 -0
  48. data/lib/vagrant-bolt/plugin.rb +39 -0
  49. data/lib/vagrant-bolt/provisioner.rb +18 -0
  50. data/lib/vagrant-bolt/runner.rb +88 -0
  51. data/lib/vagrant-bolt/util/bolt.rb +139 -0
  52. data/lib/vagrant-bolt/util/config.rb +43 -0
  53. data/lib/vagrant-bolt/util/machine.rb +73 -0
  54. data/lib/vagrant-bolt/version.rb +5 -0
  55. data/spec/spec_helper.rb +12 -0
  56. data/spec/unit/config/bolt_spec.rb +150 -0
  57. data/spec/unit/config/global_spec.rb +95 -0
  58. data/spec/unit/provisioner/bolt_spec.rb +39 -0
  59. data/spec/unit/runner/runner_spec.rb +122 -0
  60. data/spec/unit/util/bolt_spec.rb +148 -0
  61. data/spec/unit/util/config_spec.rb +53 -0
  62. data/spec/unit/vagrant_spec.rb +9 -0
  63. data/tasks/acceptance.rake +45 -0
  64. data/tasks/spec.rake +5 -0
  65. data/templates/locales/en.yml +24 -0
  66. data/vagrant-bolt.gemspec +24 -0
  67. metadata +109 -0
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # VM level requires overriding to_proc to allow access to the node config object
4
+ # @!visibility private
5
+ module VagrantBolt::ConfigBuilder::MonkeyPatches
6
+ def to_proc
7
+ proc do |config|
8
+ super.call(config)
9
+ eval_bolt_root(config)
10
+ eval_bolt_triggers_root(config)
11
+ end
12
+ end
13
+
14
+ def eval_bolt(config)
15
+ # noop
16
+ end
17
+
18
+ def eval_bolt_root(vm_root_config)
19
+ # Configure the vm bolt object if the options exist
20
+ with_attr(:bolt) do |bolt_config|
21
+ f = VagrantBolt::ConfigBuilder::Config.new_from_hash(bolt_config)
22
+ f.call(vm_root_config)
23
+ end
24
+ end
25
+
26
+ def eval_bolt_triggers(config)
27
+ # noop
28
+ end
29
+
30
+ def eval_bolt_triggers_root(vm_root_config)
31
+ # Configure the vm bolt object if the options exist
32
+ triggers = attr(:bolt_triggers) || [] # rubocop:disable Style/Attr
33
+ triggers.each do |config|
34
+ f = VagrantBolt::ConfigBuilder::Triggers.new_from_hash(config)
35
+ f.call(vm_root_config)
36
+ end
37
+ end
38
+ end
39
+
40
+ class ConfigBuilder::Model::VM
41
+ def_model_delegator :bolt
42
+ def_model_delegator :bolt_triggers
43
+ end
44
+
45
+ ConfigBuilder::Model::VM.prepend(VagrantBolt::ConfigBuilder::MonkeyPatches)
46
+
47
+ # Allow for the role filter to handle bolt configs and bolt_triggers
48
+ # @!visibility private
49
+ module VagrantBolt::ConfigBuilder::MonkeyPatches::FilterRoles
50
+ def merge_nodes!(left, right)
51
+ super.tap do |result|
52
+ array_keys = ['bolt_triggers']
53
+ array_keys.each do |key|
54
+ next unless right.key?(key)
55
+
56
+ result[key] ||= []
57
+ result[key].unshift(*right[key])
58
+ end
59
+
60
+ hash_keys = ['bolt']
61
+ hash_keys.each do |key|
62
+ next unless right.key?(key)
63
+
64
+ result[key] ||= {}
65
+ result[key] = right[key].merge(result[key])
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ ConfigBuilder::Filter::Roles.prepend(VagrantBolt::ConfigBuilder::MonkeyPatches::FilterRoles)
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'config_builder/model'
4
+ require_relative 'monkey_patches'
5
+
6
+ class VagrantBolt::ConfigBuilder::Provisioner < ConfigBuilder::Model::Provisioner::Base
7
+ # @!attribute [rw] args
8
+ # @return [String] Additional arguments for the bolt command
9
+ def_model_attribute :args
10
+
11
+ # @!attribute [rw] bolt_exe
12
+ # @return [String] The full path to the bolt command. If not passed in, the default from PATH will be used.
13
+ def_model_attribute :bolt_exe
14
+
15
+ # @!attribute [rw] boltdir
16
+ # @return [String] The bolt working directory. Defaults to `.`
17
+ def_model_attribute :boltdir
18
+
19
+ # @!attribute [rw] debug
20
+ # @return [Boolean] Shows debug logging
21
+ def_model_attribute :debug
22
+
23
+ # @!attribute [rw] host_key_check
24
+ # @return [Boolean] If the connection should check the host key on the remote host (linux)
25
+ def_model_attribute :host_key_check
26
+
27
+ # @!attribute [rw] modulepath
28
+ # @return [String] The path to the modules. Defaults to `modules`.
29
+ def_model_attribute :modulepath
30
+
31
+ # @!attribute [rw] name
32
+ # @return [String] The name of task or plan to run
33
+ def_model_attribute :name
34
+
35
+ # @!attribute [rw] nodes
36
+ # Note: The `node_list` will override this setting.
37
+ # @return [Array<String, Symbol>, "ALL"] The nodes to run the task or plan on.
38
+ # Valid values are an array of machine names or the string "ALL".
39
+ def_model_attribute :nodes
40
+
41
+ # @!attribute [rw] noop
42
+ # @return [Boolean] If the command should be run with noop. Only valid with tasks and apply.
43
+ def_model_attribute :noop
44
+
45
+ # @!attribute [rw] excludes
46
+ # Note: The `node_list` will override this setting.
47
+ # Note: This will be merged with `nodes`, with `excludes` taking precidence.
48
+ # @return [Array<String, Symbol>] The nodes to exclude from running this task or plan on.
49
+ # Valid values are an array of machine names.
50
+ def_model_attribute :excludes
51
+
52
+ # @!attribute [rw] node_list
53
+ # This setting overrides `nodes` and needs to be in the `protocol://ipaddress:port` URI format
54
+ # @return [String] The bolt node list. This defaults to the currnet node.
55
+ def_model_attribute :node_list
56
+
57
+ # @!attribute [rw] params
58
+ # @return [Hash] The paramater hash for the task or plan
59
+ def_model_attribute :params
60
+
61
+ # @!attribute [rw] command
62
+ # @return [Symbol] Whether bolt should use a task or plan
63
+ def_model_attribute :command
64
+
65
+ # @!attribute [rw] user
66
+ # @return [String] The user to authenticate on the machine.
67
+ def_model_attribute :user
68
+
69
+ # @!attribute [rw] password
70
+ # @return [String] The password to authenticate on the machine.
71
+ def_model_attribute :password
72
+
73
+ # @!attribute [rw] port
74
+ # @return [String] The port to connect to the machine.
75
+ def_model_attribute :port
76
+
77
+ # @!attribute [rw] private_key
78
+ # @return [String] The path of the private_key to authenticate on the machine.
79
+ def_model_attribute :private_key
80
+
81
+ # @!attribute [rw] run_as
82
+ # @return [String] User to run as using privilege escalation.
83
+ def_model_attribute :run_as
84
+
85
+ # @!attribute [rw] sudo_password
86
+ # @return [String] The password to authenticate sudo on the machine.
87
+ def_model_attribute :sudo_password
88
+
89
+ # @!attribute [rw] ssl
90
+ # @return [Boolean] If the connection should use SSL on with WinRM (Windows)
91
+ def_model_attribute :ssl
92
+
93
+ # @!attribute [rw] ssl_verify
94
+ # @return [Boolean] If the connection should verify SSL on with WinRM (Windows)
95
+ def_model_attribute :ssl_verify
96
+
97
+ # @!attribute [rw] tmpdir
98
+ # @return [String] The directory to upload and execute temporary files on the target
99
+ def_model_attribute :tmpdir
100
+
101
+ # @!attribute [rw] verbose
102
+ # @return [Boolean] Shows verbose logging
103
+ def_model_attribute :verbose
104
+
105
+ ConfigBuilder::Model::Provisioner.register('bolt', self)
106
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'config_builder/model'
4
+ require_relative 'monkey_patches'
5
+
6
+ class VagrantBolt::ConfigBuilder::Triggers < VagrantBolt::ConfigBuilder::Config
7
+ # @!attribute [rw] trigger_type
8
+ # @return [Symbol] A symbol containing the the trigger action. Valid values are `:before` and `:after`
9
+ def_model_attribute :trigger_type
10
+
11
+ # @!attribute [rw] trigger_commands
12
+ # @return [Array<Symbol>] The commands that the trigger should run on. E.g. `[:up, :provision]`
13
+ def_model_attribute :trigger_commands
14
+
15
+ def to_proc
16
+ options = @attrs.dup
17
+ trigger_type = options.delete(:trigger_type)
18
+ trigger_commands = options.delete(:trigger_commands)
19
+ command = options.delete(:command)
20
+ name = options.delete(:name)
21
+ proc do |config|
22
+ config.trigger.send(trigger_type, trigger_commands) do |trigger|
23
+ trigger.ruby do |env, machine|
24
+ VagrantBolt.send(command, name, env, machine, **options)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'vagrant'
4
+ require_relative 'version'
5
+
6
+ raise "vagrant-bolt version #{VagrantBolt::VERSION} requires Vagrant 2.2 or later" if Vagrant::VERSION < "2.2.0"
7
+
8
+ class VagrantBolt::Plugin < Vagrant.plugin('2')
9
+ name 'bolt'
10
+
11
+ description <<-DESC
12
+ Vagrant provisioning with Puppet Bolt
13
+ DESC
14
+
15
+ config(:bolt) do
16
+ require_relative 'config'
17
+ VagrantBolt::Config::Global
18
+ end
19
+
20
+ config(:bolt, :provisioner) do
21
+ require_relative 'config'
22
+ VagrantBolt::Config::Bolt
23
+ end
24
+
25
+ provisioner(:bolt) do
26
+ require_relative 'provisioner'
27
+ VagrantBolt::Provisioner
28
+ end
29
+
30
+ command(:bolt) do
31
+ require_relative 'command'
32
+ VagrantBolt::Command
33
+ end
34
+
35
+ # Enables config builder loading of this plugin
36
+ def self.config_builder_hook
37
+ require_relative 'config_builder'
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'vagrant'
4
+ require 'vagrant/errors'
5
+
6
+ class VagrantBolt::Provisioner < Vagrant.plugin('2', :provisioner)
7
+ # Provision VMs with Bolt
8
+ # Creates a trigger for each bolt provisioner
9
+
10
+ def provision
11
+ runner = VagrantBolt::Runner.new(@machine.env, @machine, @config)
12
+ runner.run(@config.command, @config.name)
13
+ end
14
+
15
+ def cleanup
16
+ # We don't do any clean up for this provisioner
17
+ end
18
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'util/bolt'
4
+ require_relative 'util/config'
5
+ require_relative 'util/machine'
6
+
7
+ class VagrantBolt::Runner
8
+ def initialize(env, machine, boltconfig = nil)
9
+ @env = env
10
+ @machine = machine
11
+ @boltconfig = boltconfig.nil? ? VagrantBolt::Config::Bolt.new : boltconfig
12
+ @inventory_path = VagrantBolt::Util::Bolt.inventory_file(@env)
13
+ end
14
+
15
+ # Run a bolt task or plan
16
+ # @param [Symbol, String] command The command of bolt to run; task or plan
17
+ # @param [String] name The name of the bolt task or plan to run
18
+ # @param [Hash] args A optional hash of bolt config overrides. No merging will be done with the overrides
19
+ # @example run('task', 'facts', {node_list: "machinename"})
20
+ def run(command, name, **args)
21
+ @boltconfig = setup_overrides(command, name, **args)
22
+ # Don't run anything if there are nodes to run it on
23
+ # TODO: Gate this in a more efficient manner. It is possible to run plans without a node list.
24
+ return if @boltconfig.node_list.nil?
25
+
26
+ @inventory_path = VagrantBolt::Util::Bolt.update_inventory_file(@env)
27
+ validate
28
+ command = VagrantBolt::Util::Bolt.generate_bolt_command(@boltconfig, @inventory_path)
29
+ VagrantBolt::Util::Machine.run_command(command, @machine.ui)
30
+ end
31
+
32
+ private
33
+
34
+ # Set up config overrides
35
+ # @param [Symbol, String] command The command of bolt to run; task or plan
36
+ # @param [String] name The name of the bolt task or plan to run
37
+ # @param [Hash] args A optional hash of bolt config overrides; {run_as: "vagrant"}
38
+ # @return [Object] Bolt config with ssh info populated
39
+ def setup_overrides(command, name, **args)
40
+ config = @boltconfig.dup
41
+ config.command = command
42
+ config.name = name
43
+ # Merge the root config to get the defaults for the environment
44
+ config = VagrantBolt::Util::Config.merge_config(config, @env.vagrantfile.config.bolt)
45
+ # Add any additional arguments to the config object
46
+ config.set_options(args) unless args.nil?
47
+ # Configure the node_list based on the config
48
+ config.node_list ||= [config.nodes - config.excludes].flatten.join(',') unless config.nodes.empty? || config.nodes.to_s.casecmp("all").zero?
49
+ config.node_list ||= [VagrantBolt::Util::Machine.nodes_in_environment(@env).map(&:name) - config.excludes].flatten.join(',') if config.nodes.to_s.casecmp("all").zero?
50
+ config.node_list ||= @machine.name.to_s unless config.excludes.include?(@machine.name.to_s)
51
+
52
+ # Ensure these are absolute paths to allow for running vagrant commands outside of the root dir
53
+ config.modulepath = VagrantBolt::Util::Config.full_path(config.modulepath, @env.root_path)
54
+ config.boltdir = VagrantBolt::Util::Config.full_path(config.boltdir, @env.root_path)
55
+
56
+ config
57
+ end
58
+
59
+ # Validate the config object for configuration issues
60
+ # Print and raise an exception if errors exist
61
+ def validate
62
+ errors = {}
63
+ errors.merge!(@boltconfig.validate(@machine))
64
+ errors.merge!(validate_config)
65
+
66
+ errors.keys.each do |key|
67
+ errors.delete(key) if errors[key].empty?
68
+ end
69
+
70
+ # rubocop:disable Style/GuardClause
71
+ if errors && !errors.empty?
72
+ raise Vagrant::Errors::ConfigInvalid,
73
+ errors: Vagrant::Util::TemplateRenderer.render(
74
+ "config/validation_failed",
75
+ errors: errors,
76
+ )
77
+ end
78
+ # rubocop:enable Style/GuardClause
79
+ end
80
+
81
+ # Validate a bolt config object for logical errors
82
+ def validate_config
83
+ errors = []
84
+ errors << I18n.t('vagrant-bolt.config.bolt.errors.command_not_specified') if @boltconfig.command.nil?
85
+ errors << I18n.t('vagrant-bolt.config.bolt.errors.no_task_or_plan') if @boltconfig.name.nil?
86
+ { "Bolt" => errors }
87
+ end
88
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'machine'
4
+
5
+ module VagrantBolt::Util
6
+ module Bolt
7
+ # Bolt Centric Utility Functions
8
+
9
+ # Create a bolt command from the config
10
+ # @param config [Object] The config objects
11
+ # @param inventory_path [String] The path of the inventory file
12
+ # @return [String] The bolt command
13
+ def self.generate_bolt_command(config, inventory_path = nil)
14
+ command = []
15
+ command << config.bolt_exe
16
+ command << "#{config.command} run \'#{config.name}\'"
17
+
18
+ config.instance_variables_hash.each do |key, value|
19
+ next if key.to_s.start_with?('__')
20
+ next if config.blacklist.include?(key)
21
+ next if value.nil?
22
+
23
+ key = key.tr('_', '-')
24
+ case value
25
+ when TrueClass, FalseClass
26
+ # Verbose and debug do not have --no flags so exclude them
27
+ next if ['verbose', 'debug', 'noop'].include?(key) && !value
28
+
29
+ arg = value ? "--#{key}" : "--no-#{key}"
30
+ command << arg
31
+ when String
32
+ command << "--#{key} \'#{value}\'"
33
+ when Hash
34
+ command << "--#{key} \'#{value.to_json}\'" unless value.empty?
35
+ end
36
+ end
37
+
38
+ command << "--inventoryfile \'#{inventory_path}\'" unless inventory_path.nil?
39
+ command << "--nodes \'#{config.node_list}\'" unless config.node_list.nil?
40
+ command << config.args unless config.args.nil?
41
+ command.flatten.join(" ")
42
+ end
43
+
44
+ # Generate a bolt inventory hash for the environment
45
+ # @param env [Object] The env object
46
+ # @return [Hash] The hash of config options for the inventory.yaml
47
+ def self.generate_inventory_hash(env)
48
+ inventory = { 'nodes' => [] }
49
+ inventory.merge!(env.vagrantfile.config.bolt.inventory_config.compact)
50
+ VagrantBolt::Util::Machine.nodes_in_environment(env).each do |vm|
51
+ next unless VagrantBolt::Util::Machine.running?(vm)
52
+
53
+ inventory['nodes'] << generate_node_hash(vm)
54
+ end
55
+ inventory.compact
56
+ end
57
+
58
+ # Generate a bolt inventory node hash from the VM config
59
+ # @param machine [Object] The machine object
60
+ # @return [Hash] The hash of config options for the VM
61
+ def self.generate_node_hash(machine)
62
+ # Only call ssh_info once
63
+ node_hash = {}
64
+ ssh_info = machine.ssh_info
65
+ return node_hash if ssh_info.nil?
66
+
67
+ node_hash['alias'] = machine.name.to_s
68
+ machine_config = machine.config.bolt.inventory_config
69
+ node_hash['config'] = {}
70
+ transport = VagrantBolt::Util::Machine.windows?(machine) ? 'winrm' : 'ssh'
71
+ node_hash['config'][transport] = machine_transport_hash(machine, machine_config, ssh_info).compact
72
+ node_hash['config']['transport'] = transport
73
+ node_hash['name'] = "#{transport}://#{ssh_info[:host]}:#{node_hash['config'][transport]['port']}"
74
+ machine_config.each do |key, value|
75
+ next if key == 'config' || value.nil? || value.empty?
76
+
77
+ node_hash[key] = value
78
+ end
79
+ node_hash.compact
80
+ end
81
+
82
+ # Return a transport config hash for a node
83
+ # @param machine [Object] The machine
84
+ # @param machine_config [Hash] A hash of the machine config options
85
+ # @param ssh_info [Hash] The ssh hash for the machine
86
+ def self.machine_transport_hash(machine, machine_config = {}, ssh_info = nil)
87
+ config = {}
88
+ if VagrantBolt::Util::Machine.windows?(machine)
89
+ transport = 'winrm'
90
+ config['ssl'] = (machine.config.winrm.transport == :ssl)
91
+ config['ssl_verify'] = machine.config.winrm.ssl_peer_verification
92
+ config['port'] = machine.config.winrm.port
93
+ config['user'] = machine.config.winrm.username
94
+ config['password'] = machine.config.winrm.password
95
+ else
96
+ transport = 'ssh'
97
+ config['private-key'] = ssh_info[:private_key_path][0] unless ssh_info[:private_key_path].nil?
98
+ config['host-key-check'] = (ssh_info[:verify_host_key] == true)
99
+ config['port'] = ssh_info[:port]
100
+ config['user'] = ssh_info[:username]
101
+ config['password'] = ssh_info[:password]
102
+ end
103
+ config.merge!(machine_config['config'][transport]) if machine_config.dig('config', transport)
104
+ config
105
+ end
106
+
107
+ # Return the path to the inventory file
108
+ # @param env [Object] The environment
109
+ # @return [String] The path to the inventory file
110
+ def self.inventory_file(env)
111
+ File.join(env.local_data_path, 'bolt_inventory.yaml')
112
+ end
113
+
114
+ # Update and write the inventory file for the current running machines
115
+ # @param env [Object] The envionment object
116
+ # @param inventory_file [String] The path where the inventory_file should be written.
117
+ # @return path to the inventory file
118
+ def self.update_inventory_file(env, inventory_file = nil)
119
+ inventory = generate_inventory_hash(env).to_yaml
120
+ inventory_file ||= Pathname.new(inventory_file(env))
121
+ # TODO: This lock should be global
122
+ lock = Mutex.new
123
+ lock.synchronize do
124
+ if !File.exist?(inventory_file) || (inventory != File.read(inventory_file))
125
+ begin
126
+ inventory_tmpfile = Tempfile.new('.vagrant_bolt_inventory', env.local_data_path)
127
+ inventory_tmpfile.write(inventory)
128
+ inventory_tmpfile.close
129
+ File.rename(inventory_tmpfile.path, inventory_file)
130
+ ensure
131
+ inventory_tmpfile.close
132
+ inventory_tmpfile.unlink
133
+ end
134
+ end
135
+ end
136
+ inventory_file
137
+ end
138
+ end
139
+ end