├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── vagrant-hostmanager.rb └── vagrant-hostmanager │ ├── action.rb │ ├── action │ ├── update_all.rb │ ├── update_guest.rb │ └── update_host.rb │ ├── command.rb │ ├── config.rb │ ├── errors.rb │ ├── hosts_file │ └── updater.rb │ ├── plugin.rb │ ├── provisioner.rb │ ├── util.rb │ └── version.rb ├── locales └── en.yml ├── test ├── Vagrantfile └── test.sh └── vagrant-hostmanager.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | pkg 3 | Gemfile.lock 4 | test/.vagrant 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.6.1 4 | ### Bug fixes 5 | * Retain tmp hosts file and fix a bug where powershell mv does not move folder contents, in that context it's moving a file. [[#157](https://github.com/smdahlen/vagrant-hostmanager/issues/157)] 6 | 7 | [Full diff](https://github.com/smdahlen/vagrant-hostmanager/compare/v1.6.0...v1.6.1) 8 | 9 | 10 | ## 1.6.0 11 | ### Features 12 | * splits hostnames across many lines [[#67](https://github.com/smdahlen/vagrant-hostmanager/pull/103)] 13 | 14 | ### Bug fixes 15 | * show description for hostmanager when vagrant list-commands is triggered [[#105](https://github.com/smdahlen/vagrant-hostmanager/pull/105)] 16 | 17 | ### Miscelaneous 18 | * extract old vagrant version compatibility code into util method [[#97](https://github.com/smdahlen/vagrant-hostmanager/pull/97)] 19 | * migrate HostsFile code into its own class [[#98](https://github.com/smdahlen/vagrant-hostmanager/pull/97)] 20 | 21 | [Full diff](https://github.com/smdahlen/vagrant-hostmanager/compare/v1.5.0...v1.6.0) 22 | 23 | 24 | ## 1.5.0 25 | ### Features 26 | * hostmanager now runs *before* provisioning takes place, on `up` action [[#73](https://github.com/smdahlen/vagrant-hostmanager/issues/73)] 27 | 28 | ### Bug fixes 29 | * properly detect hosts file location on Windows guests [[#67](https://github.com/smdahlen/vagrant-hostmanager/pull/67)] 30 | * do not add host if IP cannot be determined [[#85](https://github.com/smdahlen/vagrant-hostmanager/pull/85)] 31 | * force moving of hosts file on Linux guests [[#93](https://github.com/smdahlen/vagrant-hostmanager/pull/93)] 32 | * allow top-level config options (eg. `ip_resolver`) to propagate to machine configs [[#91](https://github.com/smdahlen/vagrant-hostmanager/issues/91)] 33 | 34 | ### Miscelaneous 35 | * add passwordless sudo instructions to README [[#95](https://github.com/smdahlen/vagrant-hostmanager/pull/95)] 36 | 37 | [Full diff](https://github.com/smdahlen/vagrant-hostmanager/compare/v1.4.0...v1.5.0) 38 | 39 | 40 | ## 1.4.0 41 | ### Features 42 | * supports vagrant 1.5 [[#80](https://github.com/smdahlen/vagrant-hostmanager/issues/80), [#81](https://github.com/smdahlen/vagrant-hostmanager/pull/81)] 43 | * only updates hosts file if contents have changed [[#78](https://github.com/smdahlen/vagrant-hostmanager/pull/78)] 44 | * custom ip resolver now has access to the machine whose hosts file is being updated [[#62](https://github.com/smdahlen/vagrant-hostmanager/pull/62)] 45 | 46 | ### Bug fixes 47 | * custom IP resolver result no longer ignored [[#57](https://github.com/smdahlen/vagrant-hostmanager/pull/57)] 48 | * when multiple private_networks are configured, the first one is used [[#64](https://github.com/smdahlen/vagrant-hostmanager/pull/64)] 49 | * destroyed machines are now removed from hosts file [[#52](https://github.com/smdahlen/vagrant-hostmanager/pull/52)] 50 | 51 | [Full diff](https://github.com/smdahlen/vagrant-hostmanager/compare/v1.3.0...v1.4.0) 52 | 53 | 54 | ## 1.3.0 55 | ### Features 56 | * allow defining a custom IP resolver block [[#15](https://github.com/smdahlen/vagrant-hostmanager/pull/15)] 57 | * handle removing destroyed machines from hosts file (currently only works with `include_offline = true`) [[#45](https://github.com/smdahlen/vagrant-hostmanager/pull/45)] 58 | * attempt to elevate privileges when needed in Windows hosts [[#48](https://github.com/smdahlen/vagrant-hostmanager/pull/48)] 59 | 60 | ### Bug fixes 61 | * `--provider` command-line option now finds machines as expected [[#46](https://github.com/smdahlen/vagrant-hostmanager/pull/46)] 62 | * uses proper `hosts` file location in Windows under cygwin [[#49](https://github.com/smdahlen/vagrant-hostmanager/pull/49)] 63 | 64 | ### Miscelaneous 65 | * MIT license added to gemspec 66 | 67 | [Full diff](https://github.com/smdahlen/vagrant-hostmanager/compare/v1.2.3...v1.3.0) 68 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :development do 4 | gem 'vagrant', :git => 'https://github.com/hashicorp/vagrant.git', :tag => 'v2.3.7' 5 | end 6 | 7 | group :plugins do 8 | gemspec 9 | end 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Vagrant Host Manager 2 | ==================== 3 | 4 | [![Gem](https://img.shields.io/gem/v/vagrant-hostmanager.svg)](https://rubygems.org/gems/vagrant-hostmanager) 5 | [![Gem](https://img.shields.io/gem/dt/vagrant-hostmanager.svg)](https://rubygems.org/gems/vagrant-hostmanager) 6 | [![Gem](https://img.shields.io/gem/dtv/vagrant-hostmanager.svg)](https://rubygems.org/gems/vagrant-hostmanager) 7 | [![Twitter](https://img.shields.io/twitter/url/https/github.com/devopsgroup-io/vagrant-hostmanager.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20this%20awesome%20Vagrant%20plugin%21&url=https%3A%2F%2Fgithub.com%devopsgroup-io%2Fvagrant-hostmanager&hashtags=vagrant%hostmanager&original_referer=) 8 | 9 | `vagrant-hostmanager` is a Vagrant plugin that manages the `hosts` file on guest machines (and optionally the host). Its goal is to enable resolution of multi-machine environments deployed with a cloud provider where IP addresses are not known in advance. 10 | 11 | Do you like what we do? Consider supporting us through Patreon. All of the money goes directly back into growing our collection of open source and free software. 12 | [![Patreon](https://img.shields.io/badge/patreon-donate-red.svg)](https://www.patreon.com/devopsgroup) 13 | 14 | Installation 15 | ------------ 16 | 17 | $ vagrant plugin install vagrant-hostmanager 18 | 19 | Usage 20 | ----- 21 | To update the `hosts` file on each active machine, run the following 22 | command: 23 | 24 | $ vagrant hostmanager 25 | 26 | The plugin hooks into the `vagrant up` and `vagrant destroy` commands 27 | automatically. 28 | When a machine enters or exits the running state , all active 29 | machines with the same provider will have their `hosts` file updated 30 | accordingly. Set the `hostmanager.enabled` attribute to `true` in the 31 | Vagrantfile to activate this behavior. 32 | 33 | To update the host's `hosts` file, set the `hostmanager.manage_host` 34 | attribute to `true`. 35 | 36 | To update the guests' `hosts` file, set the `hostmanager.manage_guest` 37 | attribute to `true`. 38 | 39 | A machine's IP address is defined by either the static IP for a private 40 | network configuration or by the SSH host configuration. To disable 41 | using the private network IP address, set `config.hostmanager.ignore_private_ip` 42 | to true. 43 | 44 | A machine's host name is defined by `config.vm.hostname`. If this is not 45 | set, it falls back to the symbol defining the machine in the Vagrantfile. 46 | 47 | If the `hostmanager.include_offline` attribute is set to `true`, boxes that are 48 | up or have a private ip configured will be added to the hosts file. 49 | 50 | In addition, the `hostmanager.aliases` configuration attribute can be used 51 | to provide aliases for your host names. 52 | 53 | Example configuration: 54 | 55 | ```ruby 56 | Vagrant.configure("2") do |config| 57 | config.hostmanager.enabled = true 58 | config.hostmanager.manage_host = true 59 | config.hostmanager.manage_guest = true 60 | config.hostmanager.ignore_private_ip = false 61 | config.hostmanager.include_offline = true 62 | config.vm.define 'example-box' do |node| 63 | node.vm.hostname = 'example-box-hostname' 64 | node.vm.network :private_network, ip: '192.168.42.42' 65 | node.hostmanager.aliases = %w(example-box.localdomain example-box-alias) 66 | end 67 | end 68 | ``` 69 | 70 | ### Provisioner 71 | 72 | Starting at version 1.5.0, `vagrant up` runs hostmanager before any provisioning occurs. 73 | If you would like hostmanager to run after or during your provisioning stage, 74 | you can use hostmanager as a provisioner. This allows you to use the provisioning 75 | order to ensure that hostmanager runs when desired. The provisioner will collect 76 | hosts from boxes with the same provider as the running box. 77 | 78 | Example: 79 | 80 | ```ruby 81 | # Disable the default hostmanager behavior 82 | config.hostmanager.enabled = false 83 | 84 | # ... possible provisioner config before hostmanager ... 85 | 86 | # hostmanager provisioner 87 | config.vm.provision :hostmanager 88 | 89 | # ... possible provisioning config after hostmanager ... 90 | ``` 91 | 92 | Custom IP resolver 93 | ------------------ 94 | 95 | You can customize way, how host manager resolves IP address 96 | for each machine. This might be handy in case of aws provider, 97 | where host name is stored in ssh_info hash of each machine. 98 | This causes generation of invalid /etc/hosts file. 99 | 100 | Custom IP resolver gives you oportunity to calculate IP address 101 | for each machine by yourself, giving You also access to the machine that is 102 | updating /etc/hosts. For example: 103 | 104 | ```ruby 105 | config.hostmanager.ip_resolver = proc do |vm, resolving_vm| 106 | if hostname = (vm.ssh_info && vm.ssh_info[:host]) 107 | `host #{hostname}`.split("\n").last[/(\d+\.\d+\.\d+\.\d+)/, 1] 108 | end 109 | end 110 | ``` 111 | 112 | Passwordless sudo 113 | ----------------- 114 | 115 | To avoid being asked for the password every time the hosts file is updated, 116 | enable passwordless sudo for the specific command that hostmanager uses to 117 | update the hosts file. 118 | 119 | - Add the following snippet to the sudoers file (e.g. 120 | `/etc/sudoers.d/vagrant_hostmanager`): 121 | 122 | ``` 123 | Cmnd_Alias VAGRANT_HOSTMANAGER_UPDATE = /bin/cp /.vagrant.d/tmp/hosts.local /etc/hosts 124 | % ALL=(root) NOPASSWD: VAGRANT_HOSTMANAGER_UPDATE 125 | ``` 126 | 127 | Replace `` with your actual home directory (e.g. 128 | `/home/joe`) and `` with the group that is used by the system 129 | for sudo access (usually `sudo` on Debian/Ubuntu systems and `wheel` 130 | on Fedora/Red Hat systems). 131 | 132 | - If necessary, add yourself to the ``: 133 | 134 | ``` 135 | usermod -aG 136 | ``` 137 | 138 | Replace `` with the group that is used by the system for sudo 139 | access (see above) and `` with you user name. 140 | 141 | Windows support 142 | --------------- 143 | 144 | Hostmanager will detect Windows guests and hosts and use the appropriate 145 | path for the ```hosts``` file: ```%WINDIR%\System32\drivers\etc\hosts``` 146 | 147 | By default on a Windows host, the ```hosts``` file is not writable without 148 | elevated privileges. If hostmanager detects that it cannot overwrite the file, 149 | it will attempt to do so with elevated privileges, causing the 150 | [UAC](http://en.wikipedia.org/wiki/User_Account_Control) prompt to appear. 151 | 152 | To avoid the UAC prompt, open ```%WINDIR%\System32\drivers\etc\``` in 153 | Explorer, right-click the hosts file, go to Properties > Security > Edit 154 | and give your user Modify permission. 155 | 156 | ### UAC limitations 157 | 158 | Due to limitations caused by UAC, cancelling out of the UAC prompt will not cause any 159 | visible errors, however the ```hosts``` file will not be updated. 160 | 161 | 162 | Compatibility 163 | ------------- 164 | This Vagrant plugin has been tested with the following host and guest operating system combinations. 165 | 166 | Date Tested | Vagrant Version | vagrant-hostmanager Version | Host (Workstation) Operating System | Guest (VirtualBox) Operating System 167 | ------------|-----------------|-----------------------------|-------------------------------------|-------------------------------------- 168 | 03/23/2016 | 1.8.1 | 1.8.1 | Ubuntu 14.04 LTS | CentOS 7.2 169 | 03/22/2016 | 1.8.1 | 1.8.1 | OS X 10.11.4 | CentOS 7.2 170 | 05/03/2017 | 1.9.4 | 1.8.6 | macOS 10.12.4 | Windows Server 2012 R2 171 | 172 | 173 | Troubleshooting 174 | ------------- 175 | * Version 1.1 of the plugin prematurely introduced a feature to hook into 176 | commands other than `vagrant up` and `vagrant destroy`. Version 1.1 broke support 177 | for some providers. Version 1.2 reverts this feature until a suitable implementation 178 | supporting all providers is available. 179 | 180 | * Potentially breaking change in v1.5.0: the running order on `vagrant up` has changed 181 | so that hostmanager runs before provisioning takes place. This ensures all hostnames are 182 | available to the guest when it is being provisioned 183 | (see [#73](https://github.com/devopsgroup-io/vagrant-hostmanager/issues/73)). 184 | Previously, hostmanager would run as the very last action. If you depend on the old behavior, 185 | see the [provisioner](#provisioner) section. 186 | 187 | 188 | Contribute 189 | ---------- 190 | To contribute, fork then clone the repository, and then the following: 191 | 192 | **Developing** 193 | 194 | 1. Install [RVM](https://rvm.io/rvm/install) 195 | 2. If using MacOS, follow these [OpenSSL instructions](https://github.com/rvm/rvm/issues/5252#issuecomment-1298835941) 196 | 3. Use Ruby v3.0.0 `rvm use 3.0.0` 197 | 4. Run `bundle install` 198 | 199 | **Testing** 200 | 201 | 1. Build and package your newly developed code: 202 | * `rake gem:build` 203 | 2. Then install the packaged plugin: 204 | * `vagrant plugin install pkg/vagrant-hostmanager-*.gem` 205 | 3. Once you're done testing, roll-back to the latest released version: 206 | * `vagrant plugin uninstall vagrant-hostmanager` 207 | * `vagrant plugin install vagrant-hostmanager` 208 | 4. Once you're satisfied developing and testing your new code, please submit a pull request for review. 209 | 210 | **Releasing** 211 | 212 | To release a new version of vagrant-hostmanager you will need to do the following: 213 | 214 | *(only contributors of the GitHub repo and owners of the project at RubyGems will have rights to do this)* 215 | 216 | 1. First, bump, commit, and push the version in ~/lib/vagrant-hostmanager/version.rb: 217 | * Follow [Semantic Versioning](http://semver.org/). 218 | 2. Then, create a matching GitHub Release (this will also create a tag): 219 | * Preface the version number with a `v`. 220 | * https://github.com/devopsgroup-io/vagrant-hostmanager/releases 221 | 3. You will then need to build and push the new gem to RubyGems: 222 | * `rake gem:build` 223 | * `gem push pkg/vagrant-hostmanager-1.6.1.gem` 224 | 4. Then, when John Doe runs the following, they will receive the updated vagrant-hostmanager plugin: 225 | * `vagrant plugin update` 226 | * `vagrant plugin update vagrant-hostmanager` 227 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_helper' 2 | 3 | # Change to the directory of this file. 4 | Dir.chdir(File.expand_path("../", __FILE__)) 5 | 6 | namespace :gem do 7 | Bundler::GemHelper.install_tasks 8 | end 9 | 10 | task :test do 11 | sh 'bash test/test.sh' 12 | end 13 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-hostmanager/plugin' 2 | require 'vagrant-hostmanager/version' 3 | require 'vagrant-hostmanager/errors' 4 | 5 | module VagrantPlugins 6 | module HostManager 7 | def self.source_root 8 | @source_root ||= Pathname.new(File.expand_path('../../', __FILE__)) 9 | end 10 | 11 | I18n.load_path << File.expand_path('locales/en.yml', source_root) 12 | I18n.reload! 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/action.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-hostmanager/action/update_all' 2 | require 'vagrant-hostmanager/action/update_guest' 3 | require 'vagrant-hostmanager/action/update_host' 4 | 5 | module VagrantPlugins 6 | module HostManager 7 | module Action 8 | include Vagrant::Action::Builtin 9 | 10 | def self.update_all 11 | Vagrant::Action::Builder.new.tap do |builder| 12 | builder.use ConfigValidate 13 | builder.use UpdateAll 14 | end 15 | end 16 | 17 | def self.update_guest 18 | Vagrant::Action::Builder.new.tap do |builder| 19 | builder.use ConfigValidate 20 | builder.use UpdateGuest 21 | end 22 | end 23 | 24 | def self.update_host 25 | Vagrant::Action::Builder.new.tap do |builder| 26 | builder.use UpdateHost 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/action/update_all.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-hostmanager/hosts_file/updater' 2 | require 'vagrant-hostmanager/util' 3 | 4 | module VagrantPlugins 5 | module HostManager 6 | module Action 7 | class UpdateAll 8 | 9 | def initialize(app, env) 10 | @app = app 11 | @machine = env[:machine] 12 | @global_env = @machine.env 13 | @provider = @machine.provider_name 14 | @config = Util.get_config(@global_env) 15 | @updater = HostsFile::Updater.new(@global_env, @provider) 16 | @logger = Log4r::Logger.new('vagrant::hostmanager::update_all') 17 | end 18 | 19 | def call(env) 20 | # skip if machine is not active on destroy action 21 | return @app.call(env) if !@machine.id && env[:machine_action] == :destroy 22 | 23 | # check config to see if the hosts file should be update automatically 24 | return @app.call(env) unless @config.hostmanager.enabled? 25 | @logger.info 'Updating /etc/hosts file automatically' 26 | 27 | @app.call(env) 28 | 29 | # update /etc/hosts file on active machines 30 | if @machine.config.hostmanager.manage_guest? 31 | env[:ui].info I18n.t('vagrant_hostmanager.action.update_guests') 32 | @global_env.active_machines.each do |name, p| 33 | if p == @provider 34 | machine = @global_env.machine(name, p) 35 | state = machine.state 36 | if ['active','running'].include?(state.short_description) 37 | @updater.update_guest(machine) 38 | end 39 | end 40 | end 41 | end 42 | 43 | # update /etc/hosts files on host if enabled 44 | if @machine.config.hostmanager.manage_host? 45 | env[:ui].info I18n.t('vagrant_hostmanager.action.update_host') 46 | @updater.update_host 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/action/update_guest.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-hostmanager/hosts_file/updater' 2 | require 'vagrant-hostmanager/util' 3 | 4 | module VagrantPlugins 5 | module HostManager 6 | module Action 7 | class UpdateGuest 8 | 9 | def initialize(app, env) 10 | @app = app 11 | global_env = env[:global_env] 12 | @config = Util.get_config(global_env) 13 | @machine = env[:machine] 14 | @updater = HostsFile::Updater.new(@machine.env, env[:provider]) 15 | @logger = Log4r::Logger.new('vagrant::hostmanager::update_guest') 16 | end 17 | 18 | def call(env) 19 | if @config.hostmanager.manage_guest? 20 | env[:ui].info I18n.t('vagrant_hostmanager.action.update_guest', name: @machine.name) 21 | @updater.update_guest(@machine) 22 | 23 | @app.call(env) 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/action/update_host.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-hostmanager/hosts_file/updater' 2 | require 'vagrant-hostmanager/util' 3 | 4 | module VagrantPlugins 5 | module HostManager 6 | module Action 7 | class UpdateHost 8 | 9 | def initialize(app, env) 10 | @app = app 11 | 12 | global_env = env[:global_env] 13 | @config = Util.get_config(global_env) 14 | @updater = HostsFile::Updater.new(global_env, env[:provider]) 15 | 16 | @logger = Log4r::Logger.new('vagrant::hostmanager::update_host') 17 | end 18 | 19 | def call(env) 20 | if @config.hostmanager.manage_host? 21 | env[:ui].info I18n.t('vagrant_hostmanager.action.update_host') 22 | @updater.update_host 23 | end 24 | 25 | @app.call(env) 26 | end 27 | end 28 | end 29 | end 30 | end -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/command.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module HostManager 3 | class Command < Vagrant.plugin('2', :command) 4 | 5 | # Show description when `vagrant list-commands` is triggered 6 | def self.synopsis 7 | "plugin: vagrant-hostmanager: manages the /etc/hosts file within a multi-machine environment" 8 | end 9 | 10 | def execute 11 | options = {} 12 | opts = OptionParser.new do |o| 13 | o.banner = 'Usage: vagrant hostmanager [vm-name]' 14 | o.separator '' 15 | o.version = VagrantPlugins::HostManager::VERSION 16 | o.program_name = 'vagrant hostmanager' 17 | 18 | o.on('--provider provider', String, 19 | 'Update machines with the specific provider.') do |provider| 20 | options[:provider] = provider.to_sym 21 | end 22 | end 23 | 24 | argv = parse_options(opts) 25 | options[:provider] ||= @env.default_provider 26 | 27 | # update /etc/hosts file for specified guest machines 28 | with_target_vms(argv, options) do |machine| 29 | @env.action_runner.run(Action.update_guest, { 30 | :global_env => @env, 31 | :machine => machine, 32 | :provider => options[:provider] 33 | }) 34 | end 35 | 36 | # update /etc/hosts file for host 37 | @env.action_runner.run(Action.update_host, { 38 | :global_env => @env, 39 | :provider => options[:provider] 40 | }) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/config.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module HostManager 3 | class Config < Vagrant.plugin('2', :config) 4 | attr_accessor :enabled 5 | attr_accessor :manage_host 6 | attr_accessor :manage_guest 7 | attr_accessor :ignore_private_ip 8 | attr_accessor :aliases 9 | attr_accessor :include_offline 10 | attr_accessor :ip_resolver 11 | 12 | alias_method :enabled?, :enabled 13 | alias_method :include_offline?, :include_offline 14 | alias_method :manage_host?, :manage_host 15 | alias_method :manage_guest?, :manage_guest 16 | 17 | def initialize 18 | @enabled = UNSET_VALUE 19 | @manage_host = UNSET_VALUE 20 | @manage_guest = UNSET_VALUE 21 | @ignore_private_ip = UNSET_VALUE 22 | @include_offline = UNSET_VALUE 23 | @aliases = UNSET_VALUE 24 | @ip_resolver = UNSET_VALUE 25 | end 26 | 27 | def finalize! 28 | @enabled = false if @enabled == UNSET_VALUE 29 | @manage_host = false if @manage_host == UNSET_VALUE 30 | @manage_guest = true if @manage_guest == UNSET_VALUE 31 | @ignore_private_ip = false if @ignore_private_ip == UNSET_VALUE 32 | @include_offline = false if @include_offline == UNSET_VALUE 33 | @aliases = [] if @aliases == UNSET_VALUE 34 | @ip_resolver = nil if @ip_resolver == UNSET_VALUE 35 | 36 | @aliases = [ @aliases ].flatten 37 | end 38 | 39 | def validate(machine) 40 | errors = [] 41 | 42 | errors << validate_bool('hostmanager.enabled', @enabled) 43 | errors << validate_bool('hostmanager.manage_host', @manage_host) 44 | errors << validate_bool('hostmanager.manage_guest', @manage_guest) 45 | errors << validate_bool('hostmanager.ignore_private_ip', @ignore_private_ip) 46 | errors << validate_bool('hostmanager.include_offline', @include_offline) 47 | errors.compact! 48 | 49 | # check if aliases option is an Array 50 | if !machine.config.hostmanager.aliases.kind_of?(Array) && 51 | !machine.config.hostmanager.aliases.kind_of?(String) 52 | errors << I18n.t('vagrant_hostmanager.config.not_an_array_or_string', **{ 53 | :config_key => 'hostmanager.aliases', 54 | :is_class => aliases.class.to_s, 55 | }) 56 | end 57 | 58 | if !machine.config.hostmanager.ip_resolver.nil? && 59 | !machine.config.hostmanager.ip_resolver.kind_of?(Proc) 60 | errors << I18n.t('vagrant_hostmanager.config.not_a_proc', **{ 61 | :config_key => 'hostmanager.ip_resolver', 62 | :is_class => ip_resolver.class.to_s, 63 | }) 64 | end 65 | 66 | errors.compact! 67 | { "HostManager configuration" => errors } 68 | end 69 | 70 | private 71 | 72 | def validate_bool(key, value) 73 | if ![TrueClass, FalseClass].include?(value.class) && 74 | value != UNSET_VALUE 75 | I18n.t('vagrant_hostmanager.config.not_a_bool', **{ 76 | :config_key => key, 77 | :value => value.class.to_s 78 | }) 79 | else 80 | nil 81 | end 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/errors.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module HostManager 3 | module Errors 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/hosts_file/updater.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | 3 | module VagrantPlugins 4 | module HostManager 5 | module HostsFile 6 | 7 | class Updater 8 | 9 | def initialize(global_env, provider) 10 | @global_env = global_env 11 | @config = Util.get_config(@global_env) 12 | @provider = provider 13 | @logger = Log4r::Logger.new('vagrant::hostmanager::updater') 14 | @logger.debug("init updater") 15 | end 16 | 17 | def update_guest(machine) 18 | return unless machine.communicate.ready? 19 | 20 | if (machine.communicate.test("uname -s | grep SunOS")) 21 | realhostfile = "/etc/inet/hosts" 22 | line_endings = "lf" 23 | elsif (machine.communicate.test("test -d $Env:SystemRoot")) 24 | windir = "" 25 | machine.communicate.execute("echo %SYSTEMROOT%", {:shell => :cmd}) do |type, contents| 26 | windir << contents.gsub("\r\n", '') if type == :stdout 27 | end 28 | realhostfile = "#{windir}\\System32\\drivers\\etc\\hosts" 29 | line_endings = "crlf" 30 | else 31 | realhostfile = "/etc/hosts" 32 | line_endings = "lf" 33 | end 34 | 35 | # download and modify file with Vagrant-managed entries 36 | file = @global_env.tmp_path.join("hosts.#{machine.name}") 37 | machine.communicate.download(realhostfile, file) 38 | 39 | @logger.debug("file is: #{file.to_s}") 40 | @logger.debug("class of file is: #{file.class}") 41 | 42 | if update_file(file, machine, false, line_endings) 43 | # upload modified file and remove temporary file 44 | machine.communicate.upload(file.to_s, "/tmp/hosts.#{machine.name}") 45 | if windir 46 | machine.communicate.sudo("mv -force /tmp/hosts.#{machine.name} #{realhostfile}") 47 | else 48 | machine.communicate.sudo("cat /tmp/hosts.#{machine.name} > #{realhostfile} && rm -f /tmp/hosts.#{machine.name}") 49 | end 50 | end 51 | 52 | end 53 | 54 | def update_host 55 | # copy and modify hosts file on host with Vagrant-managed entries 56 | file = @global_env.tmp_path.join('hosts.local') 57 | 58 | if WindowsSupport.windows? 59 | # lazily include windows Module 60 | class << self 61 | include WindowsSupport unless include? WindowsSupport 62 | end 63 | hosts_location = "#{ENV['WINDIR']}\\System32\\drivers\\etc\\hosts" 64 | copy_proc = Proc.new { windows_copy_file(file, hosts_location) } 65 | line_endings = "crlf" 66 | else 67 | hosts_location = '/etc/hosts' 68 | copy_proc = Proc.new { `[ -w "#{hosts_location}" ] && cat "#{file}" > "#{hosts_location}" || sudo cp "#{file}" "#{hosts_location}"` } 69 | line_endings = "lf" 70 | end 71 | 72 | FileUtils.cp(hosts_location, file) 73 | 74 | if update_file(file, nil, true, line_endings) 75 | copy_proc.call 76 | end 77 | end 78 | 79 | private 80 | 81 | def update_file(file, resolving_machine = nil, include_id = true, line_endings) 82 | file = Pathname.new(file) 83 | old_file_content = file.read 84 | new_file_content = update_content(old_file_content, resolving_machine, include_id, line_endings) 85 | file.open('wb') { |io| io.write(new_file_content) } 86 | old_file_content != new_file_content 87 | end 88 | 89 | def update_content(file_content, resolving_machine, include_id, line_endings) 90 | id = include_id ? " id: #{read_or_create_id}" : "" 91 | header = "## vagrant-hostmanager-start#{id}" 92 | footer = "## vagrant-hostmanager-end" 93 | body = get_machines 94 | .map { |machine| get_hosts_file_entry(machine, resolving_machine) } 95 | .join 96 | get_new_content(header, footer, body, file_content, line_endings) 97 | end 98 | 99 | def get_hosts_file_entry(machine, resolving_machine) 100 | ip = get_ip_address(machine, resolving_machine) 101 | host = machine.config.vm.hostname || machine.name 102 | aliases = machine.config.hostmanager.aliases 103 | if ip != nil 104 | "#{ip}\t#{host}\n" + aliases.map{|a| "#{ip}\t#{a}"}.join("\n") + "\n" 105 | end 106 | end 107 | 108 | def get_ip_address(machine, resolving_machine) 109 | custom_ip_resolver = machine.config.hostmanager.ip_resolver 110 | if custom_ip_resolver 111 | custom_ip_resolver.call(machine, resolving_machine) 112 | else 113 | ip = nil 114 | if machine.config.hostmanager.ignore_private_ip != true 115 | machine.config.vm.networks.each do |network| 116 | key, options = network[0], network[1] 117 | ip = options[:ip] if key == :private_network 118 | break if ip 119 | end 120 | end 121 | ip || (machine.ssh_info ? machine.ssh_info[:host] : nil) 122 | end 123 | end 124 | 125 | def get_machines 126 | if @config.hostmanager.include_offline? 127 | machines = @global_env.machine_names 128 | else 129 | machines = @global_env.active_machines 130 | .select { |name, provider| provider == @provider } 131 | .collect { |name, provider| name } 132 | end 133 | # Collect only machines that exist for the current provider 134 | machines.collect do |name| 135 | begin 136 | machine = @global_env.machine(name, @provider) 137 | rescue Vagrant::Errors::MachineNotFound 138 | # ignore 139 | end 140 | machine 141 | end 142 | .reject(&:nil?) 143 | end 144 | 145 | def get_new_content(header, footer, body, old_content, line_endings) 146 | if body.empty? 147 | block = "\n" 148 | else 149 | block = "\n\n" + header + "\n" + body + footer + "\n\n" 150 | end 151 | # Pattern for finding existing block 152 | header_pattern = Regexp.quote(header) 153 | footer_pattern = Regexp.quote(footer) 154 | pattern = Regexp.new("[\r\n]*#{header_pattern}.*?#{footer_pattern}[\r\n]*", Regexp::MULTILINE) 155 | # Replace existing block or append 156 | content = old_content.match(pattern) ? old_content.sub(pattern, block) : old_content.rstrip + block 157 | if line_endings == "crlf" 158 | content.encode(content.encoding, :universal_encoding => true).encode(content.encoding, :crlf_newline => true) 159 | elsif line_endings == "lf" 160 | content.encode(content.encoding, :universal_encoding => true) 161 | else 162 | content.encode(content.encoding, :universal_encoding => true) 163 | end 164 | end 165 | 166 | def read_or_create_id 167 | file = Pathname.new("#{@global_env.local_data_path}/hostmanager/id") 168 | if (file.file?) 169 | id = file.read.strip 170 | else 171 | id = SecureRandom.uuid 172 | file.dirname.mkpath 173 | file.open('w') { |io| io.write(id) } 174 | end 175 | id 176 | end 177 | 178 | ## Windows support for copying files, requesting elevated privileges if necessary 179 | module WindowsSupport 180 | require 'rbconfig' 181 | 182 | def self.windows? 183 | RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ 184 | end 185 | 186 | require 'win32ole' if windows? 187 | 188 | def windows_copy_file(source, dest) 189 | begin 190 | # First, try Ruby copy 191 | FileUtils.cp(source, dest) 192 | rescue Errno::EACCES 193 | # Access denied, try with elevated privileges 194 | windows_copy_file_elevated(source, dest) 195 | end 196 | end 197 | 198 | private 199 | 200 | def windows_copy_file_elevated(source, dest) 201 | # copy command only supports backslashes as separators 202 | source, dest = [source, dest].map { |s| s.to_s.gsub(/\//, '\\') } 203 | 204 | # run 'cmd /C copy ...' with elevated privilege, minimized 205 | copy_cmd = "copy \"#{source}\" \"#{dest}\"" 206 | WIN32OLE.new('Shell.Application').ShellExecute('cmd', "/C #{copy_cmd}", nil, 'runas', 7) 207 | 208 | # Unfortunately, ShellExecute does not give us a status code, 209 | # and it is non-blocking so we can't reliably compare the file contents 210 | # to see if they were copied. 211 | # 212 | # If the user rejects the UAC prompt, vagrant will silently continue 213 | # without updating the hostsfile. 214 | end 215 | end 216 | end 217 | end 218 | end 219 | end 220 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/plugin.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-hostmanager/action' 2 | 3 | module VagrantPlugins 4 | module HostManager 5 | class Plugin < Vagrant.plugin('2') 6 | name 'HostManager' 7 | description <<-DESC 8 | This plugin manages the /etc/hosts file for the host and guest machines. 9 | An entry is created for each running machine using the hostname attribute. 10 | 11 | You can also use the hostmanager provisioner to update the hosts file. 12 | DESC 13 | 14 | config(:hostmanager) do 15 | require_relative 'config' 16 | Config 17 | end 18 | 19 | action_hook(:hostmanager, :machine_action_up) do |hook| 20 | hook.after(Vagrant::Action::Builtin::Provision, Action.update_all) 21 | end 22 | 23 | action_hook(:hostmanager, :machine_action_destroy) do |hook| 24 | hook.prepend(Action.update_all) 25 | end 26 | 27 | provisioner(:hostmanager) do 28 | require_relative 'provisioner' 29 | Provisioner 30 | end 31 | 32 | # Work-around for vagrant >= 1.5 33 | # It breaks without a provisioner config, so we provide a dummy one 34 | config(:hostmanager, :provisioner) do 35 | ::Vagrant::Config::V2::DummyConfig.new 36 | end 37 | 38 | command(:hostmanager) do 39 | require_relative 'command' 40 | Command 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/provisioner.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-hostmanager/hosts_file/updater' 2 | 3 | module VagrantPlugins 4 | module HostManager 5 | class Provisioner < Vagrant.plugin('2', :provisioner) 6 | 7 | def initialize(machine, config) 8 | super(machine, config) 9 | global_env = machine.env 10 | @config = Util.get_config(global_env) 11 | @updater = HostsFile::Updater.new(global_env, machine.provider_name) 12 | end 13 | 14 | def provision 15 | if @config.hostmanager.manage_guest? 16 | @updater.update_guest(@machine) 17 | end 18 | if @config.hostmanager.manage_host? 19 | @updater.update_host 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/util.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module HostManager 3 | module Util 4 | def self.get_config(env) 5 | # config_global has been removed from v1.5 6 | if Gem::Version.new(::Vagrant::VERSION) >= Gem::Version.new('1.5') 7 | env.vagrantfile.config 8 | else 9 | env.config_global 10 | end 11 | end 12 | end 13 | end 14 | end -------------------------------------------------------------------------------- /lib/vagrant-hostmanager/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module HostManager 3 | VERSION = '1.8.10' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_hostmanager: 3 | action: 4 | update_guests: "[vagrant-hostmanager:guests] Updating hosts file on active guest virtual machines..." 5 | update_guest: "[vagrant-hostmanager:guest] Updating hosts file on the virtual machine %{name}..." 6 | update_host: "[vagrant-hostmanager:host] Updating hosts file on your workstation (password may be required)..." 7 | config: 8 | not_a_bool: "[vagrant-hostmanager:config:error] A value for %{config_key} can only be true or false, not type '%{value}'" 9 | not_an_array_or_string: "[vagrant-hostmanager:config:error] A value for %{config_key} must be an Array or String, not type '%{is_class}'" 10 | not_a_proc: "[vagrant-hostmanager:config:error] A value for %{config_key} must be a Proc, not type '%{is_class}'" 11 | -------------------------------------------------------------------------------- /test/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | if Gem::Version.new(::Vagrant::VERSION) < Gem::Version.new('1.5') 5 | Vagrant.require_plugin('vagrant-hostmanager') 6 | end 7 | 8 | Vagrant.configure('2') do |config| 9 | 10 | if ENV.key? 'VAGRANT_BOX' 11 | config.vm.box = ENV['VAGRANT_BOX'] 12 | else 13 | config.vm.box = 'precise64' 14 | config.vm.box_url = 'http://cloud-images.ubuntu.com/precise/current/precise-server-cloudimg-vagrant-amd64-disk1.box' 15 | end 16 | 17 | config.hostmanager.enabled = true 18 | config.hostmanager.manage_host = true 19 | config.hostmanager.manage_guest = true 20 | 21 | config.vm.define :server1 do |server| 22 | server.vm.hostname = 'fry' 23 | server.vm.network :private_network, :ip => '10.0.5.2' 24 | server.hostmanager.aliases = %w(test-alias) 25 | end 26 | 27 | config.vm.define :server2 do |server| 28 | server.vm.hostname = 'bender' 29 | server.vm.network :private_network, :ip => '10.0.5.3' 30 | end 31 | 32 | config.vm.define :server3 do |server| 33 | server.vm.hostname = 'leena' 34 | server.vm.network :private_network, :ip => '10.0.5.4' 35 | server.vm.provision :hostmanager 36 | end 37 | 38 | config.vm.define :server4 do |server| 39 | server.vm.hostname = 'scruffy' 40 | server.vm.provision :hostmanager 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | cd test 2 | 3 | vagrant up 4 | 5 | vagrant hostmanager 6 | 7 | echo "[server1] /etc/hosts file:" 8 | vagrant ssh server1 -c 'cat /etc/hosts' 9 | echo "[server2] /etc/hosts file:" 10 | vagrant ssh server2 -c 'cat /etc/hosts' 11 | 12 | vagrant destroy -f 13 | 14 | cd .. 15 | -------------------------------------------------------------------------------- /vagrant-hostmanager.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | lib = File.expand_path('../lib', __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'vagrant-hostmanager/version' 6 | 7 | Gem::Specification.new do |gem| 8 | gem.name = 'vagrant-hostmanager' 9 | gem.version = VagrantPlugins::HostManager::VERSION 10 | gem.authors = ['Shawn Dahlen','Seth Reeser'] 11 | gem.email = ['shawn@dahlen.me','info@devopsgroup.io'] 12 | gem.description = %q{A Vagrant plugin that manages the /etc/hosts file within a multi-machine environment} 13 | gem.summary = gem.description 14 | gem.license = 'MIT' 15 | 16 | gem.files = `git ls-files`.split($/) 17 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 18 | gem.require_paths = ['lib'] 19 | 20 | gem.add_development_dependency 'bundler' 21 | gem.add_development_dependency 'rake' 22 | end 23 | --------------------------------------------------------------------------------