├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── example_boxes ├── README.md ├── gce-test │ ├── Vagrantfile │ └── metadata.json └── gce │ └── metadata.json ├── google-test.box ├── google.box ├── lib ├── vagrant-google.rb └── vagrant-google │ ├── action.rb │ ├── action │ ├── assign_instance_groups.rb │ ├── connect_google.rb │ ├── is_created.rb │ ├── is_terminated.rb │ ├── message_already_created.rb │ ├── message_not_created.rb │ ├── message_will_not_destroy.rb │ ├── read_ssh_info.rb │ ├── read_state.rb │ ├── run_instance.rb │ ├── setup_winrm_password.rb │ ├── start_instance.rb │ ├── stop_instance.rb │ ├── terminate_instance.rb │ ├── timed_provision.rb │ ├── warn_networks.rb │ └── warn_ssh_keys.rb │ ├── config.rb │ ├── errors.rb │ ├── plugin.rb │ ├── provider.rb │ ├── util │ └── timer.rb │ └── version.rb ├── locales └── en.yml ├── tasks ├── acceptance.rake ├── boxes.rake ├── bundler.rake ├── changelog.rake ├── lint.rake └── test.rake ├── test ├── acceptance │ ├── base.rb │ ├── provider │ │ ├── halt_spec.rb │ │ ├── image_family_spec.rb │ │ ├── instance_groups_spec.rb │ │ ├── multi_instance_spec.rb │ │ ├── preemptible_spec.rb │ │ ├── reload_spec.rb │ │ └── scopes_spec.rb │ ├── shared │ │ └── context_google.rb │ └── skeletons │ │ ├── generic │ │ └── Vagrantfile │ │ ├── image_family │ │ └── Vagrantfile │ │ ├── instance_groups │ │ └── Vagrantfile │ │ ├── multi_instance │ │ └── Vagrantfile │ │ ├── preemptible │ │ └── Vagrantfile │ │ └── scopes │ │ └── Vagrantfile └── unit │ ├── base.rb │ └── common │ └── config_test.rb ├── vagrant-google.gemspec ├── vagrant-spec.config.rb └── vagrantfile_examples ├── Vagrantfile.multiple_machines ├── Vagrantfile.provision_single ├── Vagrantfile.simple └── Vagrantfile.zone_config /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Run unit tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | 13 | - name: Set up Ruby 2.6 14 | uses: ruby/setup-ruby@v1 15 | with: 16 | ruby-version: 2.7.4 17 | 18 | - name: Unit tests 19 | run: | 20 | gem install bundler 21 | bundle install --jobs 4 --retry 3 22 | bundle exec rake 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editor/OS-specific 2 | .DS_Store 3 | .swp 4 | 5 | # Bundler/Rubygems 6 | *.gem 7 | .bundle 8 | pkg/* 9 | tags 10 | Gemfile.lock 11 | /vagrant-google/* 12 | vendor/* 13 | 14 | # Vagrant 15 | .vagrant 16 | /Vagrantfile* 17 | 18 | # Intellij projects folder 19 | .idea 20 | 21 | # Env 22 | setenv_*.sh 23 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: rubocop-performance 2 | 3 | # Custom project config 4 | AllCops: 5 | Exclude: 6 | - './Vagrantfile' 7 | - './vagrant-google.gemspec' 8 | 9 | Layout/EmptyLinesAroundBlockBody: 10 | Enabled: false 11 | 12 | Style/StringLiterals: 13 | Enabled: false 14 | 15 | Style/IfUnlessModifier: 16 | Enabled: false 17 | 18 | Metrics/LineLength: 19 | Max: 120 20 | 21 | Layout/HashAlignment: 22 | EnforcedHashRocketStyle: table 23 | 24 | Style/HashSyntax: 25 | EnforcedStyle: no_mixed_keys 26 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.7.4 2 | # Tracking Vagrant 2.2.19 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 4 | 5 | ## Next 6 | 7 | 8 | ## 2.7.0 (November 2021) 9 | 10 | ### User-facing 11 | 12 | #### Added 13 | 14 | - \#255 Adds enable_display option [dcode] 15 | - \#248 Use google_application_default credentials by default [pecigonzalo] 16 | 17 | ## 2.6.0 (December 2020) 18 | 19 | ### User-facing 20 | 21 | #### Added 22 | 23 | - \#248 Add support for modifying the configuration of Shielded VM [lcy0321] 24 | - \#244 Add accelerator (GPU) configuration [johanvdhaegen] 25 | - \#216 Add support for Windows hosts through WinRM [dvanbrug] 26 | 27 | #### Fixed 28 | 29 | - \#246 Fix config override during metadata merge [mavin] 30 | - \#249 Fix shielded API failures on non-shielded VM's [temikus] 31 | - \#227 Fix additional disks being auto-deleted unless a disk type is specified [chrisgavin] 32 | 33 | ### Development 34 | 35 | #### Fixed 36 | 37 | - \#247 chore: Update Gemfile with new `vagrant-spec` branch name [mavin] 38 | - \#241 Fix unfortunate CI `on:` trigger typo [temikus] 39 | - \#240 Update development environment, add CI functionality [Temikus] 40 | - \#239 Remove old fixup in Gemfile [Temikus] 41 | - \#237 Bump image size for test skeleton [Temikus] 42 | 43 | 44 | ## 2.5.0 (September 2019) 45 | 46 | ### User-facing 47 | 48 | #### Added 49 | - \#222 Added internal IP support [andrewh1978] 50 | 51 | #### Deprecated 52 | 53 | - \#220 Deprecated google_client_email parameter, as it's no longer needed or 54 | supported by fog-google [temikus] 55 | 56 | ### Development 57 | 58 | #### Added 59 | 60 | - \#223 Set up unit test GitHub Action worflow [temikus] 61 | 62 | #### Fixed 63 | 64 | - \#225 Locked the vagrant dep to 2.2.4 due to bundler issues on 2.2.5 [temikus] 65 | 66 | ## 2.4.0 (April 2019) 67 | 68 | ### User-facing 69 | 70 | #### Added 71 | - \#213 Implemented Application Default Credentials authentication [mavin] 72 | 73 | #### Fixed 74 | - \#214 Set a default zone only if `default` network is used [mavin] 75 | - \#215 Allow tags,labels and additional_disks to be merged with multiple 76 | configs [mavin] 77 | 78 | ### Development 79 | 80 | - \#213 Bumped dependencies [mavin] 81 | - fog-google version to 1.9.0 82 | 83 | ## 2.3.0 (February 2019) 84 | 85 | ### User-facing 86 | 87 | - \#210 Allow adding additional disks to the instances. [whynick1] 88 | 89 | ### Development 90 | 91 | - \#211 Rspec-its is now explicitly required for unit tests. [temikus] 92 | 93 | ## 2.2.1 (October 2018) 94 | 95 | ### User-facing 96 | 97 | - \#206 Fix image selection logic - Plugin no longer traces back with 98 | `image_family` config option. [temikus] 99 | 100 | ### Development 101 | 102 | - \#206 Bumped dependencies. [temikus] 103 | - fog-google version to 1.8.1 104 | - vagrant & vagrant-spec are now pointing to new upstream Hashicorp org repos 105 | 106 | ## 2.2.0 (June 2018) 107 | 108 | #### Fixed 109 | * Bumped fog-google to v1.4. 110 | This is a necessary upstream update to work properly with Ruby 2.4+ on some 111 | platforms. 112 | 113 | ## 2.1.0 (May 2018) 114 | 115 | * Add new configuration option `image_project_id` to allow using GCE images from other projects. [seanmalloy] 116 | * Add new configuration option `network_project_id` to allow using GCP Shared VPC networks. [seanmalloy] 117 | * Add new configuration option `service_account` to allow setting the IAM service account on instances. [seanmalloy] 118 | * Deprecate configuration option `service_accounts`. Use `scopes` configuration option instead. [seanmalloy] 119 | 120 | ## 2.0.0 (March 2018) 121 | 122 | * Update to use fog-google gem v1 123 | * Add new configuration option `labels` for setting [labels](https://cloud.google.com/compute/docs/labeling-resources) 124 | on GCE instances 125 | * Fix disk cleanup issue causing the disk to be marked as created before insertion 126 | * Test environment fixups to avoid 'Encoded files can't be read outside of the Vagrant installer.' 127 | * Breaking changes: 128 | * Drop support for configuration option `google_key_location`(GCP P12 key) 129 | * `image` parameter no longer defaults to an arbitrary image and must be 130 | specified at runtime 131 | * Rsync behavior now consistent with Vagrant's default, removed old rsync code 132 | 133 | ## 1.0.0 (July 2017) 134 | ## 0.2.5 (October 2016) 135 | ## 0.2.4 (April 2016) 136 | ## 0.2.3 (January 2016) 137 | 138 | ## 0.2.2 (October 2015) 139 | 140 | * Cleanup instance and disks on backend failures [p0deje] 141 | * Refactoring ssh warnings into separate action [temikus] 142 | * Refactoring disk type detection logic [temikus] 143 | * Miscellaneous doc updates and minor fixes [mbrukman, temikus] 144 | 145 | ## 0.2.1 (July 2015) 146 | 147 | * Temporarily reverted the old SyncedFolders behaviour. (See #94) 148 | 149 | ## 0.2.0 (July 2015) 150 | 151 | * Added support for service account definitions [tcr] 152 | * Added support for preemptible instances [jcdang] 153 | * Implemented auto_restart and on_host_maintenance options [jcdang] 154 | * Implemented vagrant halt and reload actions [temikus] 155 | * Added support for IP address specification by name [temikus] 156 | * Instance name now defaults to time + uuid [temikus] 157 | * Removed legacy rsync code, switched to Vagrant built-in SyncedFolders [temikus] 158 | * Switched to fog-google metagem [temikus] 159 | * Added a linter and custom acceptance tests [temikus] 160 | * Updated documentation and examples [mbrukman, temikus] 161 | * Miscellaneous UI/UX updates and bugfixes [temikus] 162 | 163 | ## 0.1.5 (May 2015) 164 | 165 | * Added support for JSON private keys [temikus] 166 | * Added disk_type parameter support [temikus] 167 | * Added acceptance tests [temikus] 168 | * Added can_ip_forward, external_ip, autodelete_disk and disk_name parameters support [phueper] 169 | * Added support for user specified rsync excludes [patkar] 170 | * Miscellaneous bugfixes [mbrukman, beauzeaux, iceydee, mklbtz, temikus] 171 | 172 | ## 0.1.4 (October 2014) 173 | 174 | * Add option for disk size [franzs] 175 | * Add tags [ptone] 176 | * Updated default for latest Debian image 177 | 178 | ## 0.1.3 (July 2014) 179 | 180 | * Updated all image references 181 | * Fixed fog deprecation warning 182 | * Updated example box `google.box` 183 | * Got spec tests passing again 184 | 185 | ## 0.1.1 (October 11, 2013) 186 | 187 | * Fixed bug with instance ready/SSH 188 | 189 | ## 0.1.0 (August 14, 2013) 190 | 191 | * Initial release. 192 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | source "https://rubygems.org" 16 | 17 | group :plugins do 18 | # Dependencies need to be specified in vagrant-google.gemspec 19 | gemspec 20 | end 21 | 22 | group :development do 23 | # We depend on Vagrant for development, but we don't add it as a 24 | # gem dependency because we expect to be installed within the 25 | # Vagrant environment itself using `vagrant plugin`. 26 | 27 | gem 'vagrant', git: "https://github.com/hashicorp/vagrant.git" 28 | gem 'vagrant-spec', git: "https://github.com/hashicorp/vagrant-spec.git", branch: "main" 29 | end 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vagrant Google Compute Engine (GCE) Provider 2 | 3 | [![Gem Version](https://badge.fury.io/rb/vagrant-google.svg)](https://badge.fury.io/rb/vagrant-google) 4 | 5 | [gem]: https://rubygems.org/gems/vagrant-google 6 | [gemnasium]: https://gemnasium.com/mitchellh/vagrant-google 7 | 8 | This is a [Vagrant](https://www.vagrantup.com) plugin that adds an 9 | [Google Compute Engine](https://cloud.google.com/compute/) (GCE) provider to 10 | Vagrant, allowing Vagrant to control and provision instances in GCE. 11 | 12 | **NOTE:** The plugin is currently looking for maintainers, please contact @temikus. 13 | 14 | # Features 15 | 16 | * Boot Google Compute Engine instances. 17 | * SSH into the instances. 18 | * Provision the instances with any built-in Vagrant provisioner. 19 | * Synced folder support via Vagrant's 20 | [rsync action](https://www.vagrantup.com/docs/synced-folders/rsync.html). 21 | * Define zone-specific configurations so Vagrant can manage machines in 22 | multiple zones. 23 | 24 | # Requirements 25 | 26 | * Google Cloud Platform (GCP) account, 27 | * a GCP project with: 28 | * Google Compute Engine API enabled 29 | * Your public SSH key added as GCE metadata. 30 | * Vagrant 2.0.3+ 31 | 32 | ## Google Cloud Platform Setup 33 | 34 | Do the following: 35 | 36 | 1. Log in with your Google Account and go to 37 | [Google Cloud Platform](https://cloud.google.com) and click on the 38 | `Try it free` button. 39 | 2. Create a new project and remember to record the `Project ID` 40 | 3. Enable the 41 | [Google Compute Engine API](https://console.cloud.google.com/apis/library/compute.googleapis.com) 42 | for your project in the API console. If prompted, review and agree to the 43 | terms of service. 44 | 4. Install the [Cloud SDK](https://cloud.google.com/sdk/docs/install) 45 | 5. Run `[gcloud auth application-default login](https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login) 46 | to create your credentials. (Alternatively, you may use a service account, see **Using a Service Account** section). 47 | 6. Add the public SSH key you're going to use to GCE Metadata in `Compute` -> 48 | `Compute Engine` -> `Metadata` section of the console, `SSH Keys` tab. (Read 49 | the [SSH Support](https://github.com/mitchellh/vagrant-google#ssh-support) 50 | readme section for more information.) 51 | 52 | ### Using a Service Account 53 | 54 | The `[appplication-default login](https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login) 55 | method is intended to be used for developing code on a local environment - this is typically Vagrant's use-case 56 | as well. However, if this is not your use-case, you will want to use a credential not tied to your local environment: 57 | a service account. 58 | 59 | To use a service account: 60 | 61 | 1. While still in the API & Services, go to 62 | [Credentials subsection](https://console.cloud.google.com/apis/api/compute.googleapis.com/credentials), 63 | and click `Create credentials` -> `Service account`. 64 | 2. Create a Service Account with any name (f.e. `vagrant`) and grant it 65 | a `Compute Admin` role. 66 | 3. Open the new service account page and click on the `Keys` tab. 67 | Click `Add key` -> `Create new key`, choose JSON. Download the JSON private key 68 | and save this file in a secure and reliable location. 69 | 70 | Then include the private key in your Vagrantfile's `provider` block as a `google_json_key_location` 71 | attribute: 72 | 73 | ```ruby 74 | Vagrant.configure("2") do |config| 75 | # ... other stuff 76 | 77 | config.vm.provider :google do |google| 78 | google.google_project_id = "YOUR_GOOGLE_CLOUD_PROJECT_ID" 79 | google.google_json_key_location = "/path/to/your/private-key.json" 80 | end 81 | end 82 | ``` 83 | 84 | ## Vagrant Setup 85 | 86 | Install as a Vagrant plugin: 87 | 88 | ```sh 89 | vagrant plugin install vagrant-google 90 | ``` 91 | 92 | # Usage 93 | 94 | Make a `Vagrantfile` that looks like the following, filling in 95 | your information where necessary: 96 | 97 | ```ruby 98 | Vagrant.configure("2") do |config| 99 | config.vm.box = "google/gce" 100 | 101 | config.vm.provider :google do |google, override| 102 | google.google_project_id = "YOUR_GOOGLE_CLOUD_PROJECT_ID" 103 | google.image_family = 'ubuntu-2004-lts' 104 | 105 | override.ssh.username = "USERNAME" 106 | override.ssh.private_key_path = "~/.ssh/id_rsa" 107 | end 108 | 109 | end 110 | ``` 111 | 112 | Run: 113 | ```sh 114 | vagrant up --provider=google 115 | ``` 116 | 117 | This will start the latest version of Ubuntu 20.04 LTS instance in the 118 | `us-central1-f` zone, with an `n1-standard-1` machine, and the `"default"` 119 | network within your project. And assuming your SSH information (see below) was 120 | filled in properly within your Vagrantfile, SSH and provisioning will work as 121 | well. 122 | 123 | Note that normally a lot of this boilerplate is encoded within the box file, 124 | but the box file used for the quick start, the "google" box, has no 125 | preconfigured defaults. 126 | 127 | ## SSH Support 128 | 129 | In order for SSH to work properly to the GCE VM, you will first need to add 130 | your public key to the GCE metadata service for the desired VM user account. 131 | When a VM first boots, a Google-provided daemon is responsible for talking to 132 | the internal GCE metadata service and creates local user accounts and their 133 | respective `~/.ssh/authorized_keys` entries. Most new GCE users will use the 134 | [Cloud SDK](https://cloud.google.com/sdk/) `gcloud compute` utility when first 135 | getting started with GCE. This utility has built in support for creating SSH 136 | key pairs, and uploading the public key to the GCE metadata service. By 137 | default, `gcloud compute` creates a key pair named 138 | `~/.ssh/google_compute_engine[.pub]`. 139 | 140 | Note that you can use the more standard `~/.ssh/id_rsa[.pub]` files, but you 141 | will need to manually add your public key to the GCE metadata service so your 142 | VMs will pick up the key. Note that the public key is typically 143 | prefixed with the username, so that the daemon on the VM adds the public key 144 | to the correct user account. 145 | 146 | Additionally, you will probably need to add the key and username to override 147 | settings in your Vagrantfile like so: 148 | 149 | ```ruby 150 | config.vm.provider :google do |google, override| 151 | 152 | #...google provider settings are skipped... 153 | 154 | override.ssh.username = "testuser" 155 | override.ssh.private_key_path = "~/.ssh/id_rsa" 156 | 157 | #...google provider settings are skipped... 158 | 159 | end 160 | ``` 161 | 162 | See the links below for more help with SSH and GCE VMs. 163 | 164 | * https://cloud.google.com/compute/docs/instances#sshing 165 | * https://cloud.google.com/compute/docs/console#sshkeys 166 | 167 | ## Box Format 168 | 169 | Every provider in Vagrant must introduce a custom box format. This provider 170 | introduces `google` boxes. You can view an example box in 171 | [example_boxes/](https://github.com/mitchellh/vagrant-google/tree/master/example_boxes). 172 | That directory also contains instructions on how to build a box. 173 | 174 | The box format is basically just the required `metadata.json` file along with 175 | a `Vagrantfile` that does default settings for the provider-specific 176 | configuration for this provider. 177 | 178 | ## Configuration 179 | 180 | This provider exposes quite a few provider-specific configuration options: 181 | 182 | * `google_json_key_location` - The location of the JSON private key file matching your 183 | Service Account. 184 | (Can also be configured with `GOOGLE_JSON_KEY_LOCATION` environment variable.) 185 | * `google_project_id` - The Project ID for your Google Cloud Platform account. 186 | (Can also be configured with `GOOGLE_PROJECT_ID` environment variable.) 187 | * `image` - The image name to use when booting your instance. 188 | * `image_family` - Specify an "image family" to pull the latest image from. For example: `centos-7` 189 | will pull the most recent CentOS 7 image. For more info, refer to 190 | [Google Image documentation](https://cloud.google.com/compute/docs/images#image_families). 191 | * `image_project_id` - The ID of the GCP project to search for the `image` or `image_family`. 192 | For example: `centos-cloud` for Centos 7/8/Stream image families. 193 | * `instance_group` - Unmanaged instance group to add the machine to. If one 194 | doesn't exist it will be created. 195 | * `instance_ready_timeout` - The number of seconds to wait for the instance 196 | to become "ready" in GCE. Defaults to 20 seconds. 197 | * `machine_type` - The machine type to use. The default is "n1-standard-1". 198 | * `disk_size` - The disk size in GB. The default is 10. 199 | * `disk_name` - The disk name to use. If the disk exists, it will be reused, otherwise created. 200 | * `disk_type` - Whether to use Standard disk or SSD disk. Use either `pd-ssd` or `pd-standard`. 201 | * `autodelete_disk` - Boolean whether to delete the disk when the instance is deleted or not. Default is true. 202 | * `metadata` - Custom key/value pairs of metadata to add to the instance. 203 | * `name` - The name of your instance. The default is "i-yyyymmddhh-randomsd", 204 | e.g. 10/08/2015 13:15:15 is "i-2015081013-15637fda". 205 | * `network` - The name of the network to use for the instance. Default is 206 | "default". 207 | * `network_project_id` - The ID of the GCP project for the network and subnetwork to use for the instance. Default is `google_project_id`. 208 | * `subnetwork` - The name of the subnetwork to use for the instance. 209 | * `tags` - An array of tags to apply to this instance. 210 | * `labels` - Custom key/value pairs of labels to add to the instance. 211 | * `zone` - The zone name where the instance will be created. 212 | * `can_ip_forward` - Boolean whether to enable IP Forwarding. 213 | * `external_ip` - The external IP address to use (supports names). Set to `false` to not assign an external address. 214 | * `network_ip` - The internal IP address to use. Default is to use next available address. 215 | * `use_private_ip` - Boolean whether to use private IP for SSH/provisioning. Default is false. 216 | * `preemptible` - Boolean whether to enable preemptibility. Default is false. 217 | * `auto_restart` - Boolean whether to enable auto_restart. Default is true. 218 | * `on_host_maintenance` - What to do on host maintenance. Can be set to `MIGRATE` or `TERMINATE` Default is `MIGRATE`. 219 | * `scopes` or `service_accounts` - An array of OAuth2 account scopes for 220 | services that the instance will have access to. Those can be both full API 221 | scopes, just endpoint aliases (the part after `...auth/`), and `gcloud` 222 | utility aliases, for example: 223 | `['storage-full', 'bigquery', 'https://www.googleapis.com/auth/compute']`. 224 | * `service_account` - The IAM service account email to use for the instance. 225 | * `additional_disks` - An array of additional disk configurations. `disk_size` is default to `10`GB; 226 | `disk_name` is default to `name` + "-additional-disk-#{index}"; `disk_type` is default to `pd-standard`; 227 | `autodelete_disk` is default to `true`. Here is an example of configuration. 228 | ```ruby 229 | [{ 230 | :image_family => "google-image-family", 231 | :image => nil, 232 | :image_project_id => "google-project-id", 233 | :disk_size => 20, 234 | :disk_name => "google-additional-disk-0", 235 | :disk_type => "pd-standard", 236 | :autodelete_disk => true 237 | }] 238 | ``` 239 | * `accelerators` - An array of accelerator configurations. `type` is the 240 | accelerator type (e.g. `nvidia-tesla-k80`); `count` is the number of 241 | accelerators and defaults to 1. Note that only `TERMINATE` is supported for 242 | `on_host_maintenance`; this should be set explicitly, since the default is 243 | `MIGRATE`. 244 | ```ruby 245 | google.accelerators = [{ 246 | :type => "nvidia-tesla-k80", 247 | :count => 2 248 | }] 249 | 250 | google.on_host_maintenance = "TERMINATE" 251 | ``` 252 | * `enable_secure_boot` - For [Shielded VM](https://cloud.google.com/security/shielded-cloud/shielded-vm), whether to enable Secure Boot. 253 | * `enable_vtpm` - For [Shielded VM](https://cloud.google.com/security/shielded-cloud/shielded-vm), whether to enable vTPM. 254 | * `enable_integrity_monitoring` - For [Shielded VM](https://cloud.google.com/security/shielded-cloud/shielded-vm), whether to enable Integrity monitoring. 255 | * `resource_policies` - Adds [Resource Policies](https://cloud.google.com/compute/docs/reference/rest/v1/resourcePolicies) to given instance. 256 | These can be set like typical provider-specific configuration: 257 | 258 | ```ruby 259 | Vagrant.configure("2") do |config| 260 | # ... other stuff 261 | 262 | config.vm.provider :google do |google| 263 | google.google_project_id = "YOUR_GOOGLE_CLOUD_PROJECT_ID" 264 | google.google_json_key_location = "/path/to/your/private-key.json" 265 | end 266 | end 267 | ``` 268 | 269 | In addition to the above top-level configs, you can use the `zone_config` 270 | method to specify zone-specific overrides within your Vagrantfile. Note 271 | that the top-level `zone` config must always be specified to choose which 272 | zone you want to actually use, however. This looks like this: 273 | 274 | ```ruby 275 | Vagrant.configure("2") do |config| 276 | 277 | config.vm.box = "google/gce" 278 | 279 | config.vm.provider :google do |google| 280 | google.google_project_id = "YOUR_GOOGLE_CLOUD_PROJECT_ID" 281 | google.google_json_key_location = "/path/to/your/private-key.json" 282 | 283 | # Make sure to set this to trigger the zone_config 284 | google.zone = "us-central1-f" 285 | 286 | google.zone_config "us-central1-f" do |zone1f| 287 | zone1f.name = "testing-vagrant" 288 | zone1f.image = "debian-9-stretch-v20211105" 289 | zone1f.machine_type = "n1-standard-4" 290 | zone1f.zone = "us-central1-f" 291 | zone1f.metadata = {'custom' => 'metadata', 'testing' => 'foobarbaz'} 292 | zone1f.scopes = ['bigquery', 'monitoring', 'https://www.googleapis.com/auth/compute'] 293 | zone1f.tags = ['web', 'app1'] 294 | end 295 | end 296 | end 297 | ``` 298 | 299 | The zone-specific configurations will override the top-level configurations 300 | when that zone is used. They otherwise inherit the top-level configurations, 301 | as you would expect. 302 | 303 | There are a few example Vagrantfiles located in the 304 | [vagrantfile_examples/ directory](https://github.com/mitchellh/vagrant-google/tree/master/vagrantfile_examples/). 305 | 306 | ## Networks 307 | 308 | Networking features in the form of `config.vm.network` are not supported 309 | with `vagrant-google`, currently. If any of these are specified, Vagrant will 310 | emit a warning, but will otherwise boot the GCE machine. 311 | 312 | ## Synced Folders 313 | 314 | Since plugin version 2.0, this is implemented via built-in `SyncedFolders` action. 315 | See Vagrant's [rsync action](https://www.vagrantup.com/docs/synced-folders/rsync.html) 316 | documentation for more info. 317 | 318 | ## Automatic shutdown 319 | 320 | To save money you may want to ensure you don't forget to shut down your instances 321 | when you stop using them. 322 | 323 | A very basic solution for this is to use Vagrant's provisioning feature to plan 324 | automatic shutdown of the vm after given time after each `vagrant up`: 325 | 326 | ```ruby 327 | # Plan automatic shutdown of machine to prevent unwanted costs 328 | config.vm.provision "auto-shutdown", type: "shell", run: "always", 329 | inline: "shutdown -P +480" # = 60 minutes * 8 hours 330 | ``` 331 | 332 | ## Print external IP 333 | 334 | You may want to know your machine's external IP f.e. to put it in your Ansible inventory 335 | or open the app you deploy in it in your browser. 336 | 337 | To automate printing it IP you can also use the Vagrant's provisioning feature: 338 | 339 | ```ruby 340 | # Print the external IP 341 | config.vm.provision "print-ip", type: "shell", run: "always", 342 | inline: "echo External IP: $(curl -s icanhazip.com)" 343 | ``` 344 | 345 | # Development 346 | 347 | To work on the `vagrant-google` plugin, clone this repository, and use 348 | [Bundler](https://gembundler.com) to get the dependencies: 349 | 350 | ```sh 351 | $ bundle 352 | ``` 353 | 354 | Once you have the dependencies, verify the unit tests pass with `rake`: 355 | 356 | ```sh 357 | $ bundle exec rake 358 | ``` 359 | 360 | If those pass, you're ready to start developing the plugin. You can test 361 | the plugin without installing it into your Vagrant environment by just 362 | creating a `Vagrantfile` in the top level of this directory (it is ignored by 363 | git), and use bundler to execute Vagrant: 364 | 365 | ```sh 366 | $ bundle exec vagrant up --provider=google 367 | ``` 368 | 369 | ## Acceptance testing 370 | 371 | **Work-in-progress:** Acceptance tests are based on vagrant-spec library which 372 | is currently under active development so they may occasionally break. 373 | 374 | Before you start acceptance tests, you'll need to set the authentication 375 | shell variables accordingly: 376 | 377 | ```sh 378 | export GOOGLE_PROJECT_ID="your-google-cloud-project-id" 379 | export GOOGLE_JSON_KEY_LOCATION="/full/path/to/your/private-key.json" 380 | 381 | export GOOGLE_SSH_USER="testuser" 382 | export GOOGLE_SSH_KEY_LOCATION="/home/testuser/.ssh/id_rsa" 383 | ``` 384 | 385 | After, you can run acceptance tests by running the `full` task in `acceptance` 386 | namespace: 387 | ```sh 388 | $ bundle exec rake acceptance:full 389 | ``` 390 | 391 | **IMPORTANT NOTES**: 392 | 393 | - Since acceptance tests spin up instances on GCE, the whole suite may take 394 | 20+ minutes to run. 395 | - Since those are live instances, **you will be billed** for running them. 396 | 397 | # Changelog 398 | See [CHANGELOG.md](CHANGELOG.md) 399 | 400 | # License 401 | Apache 2.0; see [LICENSE](LICENSE) for details. 402 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require 'rubygems' 16 | require 'bundler/setup' 17 | 18 | # Immediately sync all stdout so that tools like buildbot can 19 | # immediately load in the output. 20 | $stdout.sync = true 21 | $stderr.sync = true 22 | 23 | # Load all the rake tasks from the "tasks" folder. This folder 24 | # allows us to nicely separate rake tasks into individual files 25 | # based on their role, which makes development and debugging easier 26 | # than one monolithic file. 27 | task_dir = File.expand_path("../tasks", __FILE__) 28 | Dir["#{task_dir}/**/*.rake"].each do |task_file| 29 | load task_file 30 | end 31 | 32 | task default: "test:unit" 33 | -------------------------------------------------------------------------------- /example_boxes/README.md: -------------------------------------------------------------------------------- 1 | # Vagrant Google Example Box 2 | 3 | Vagrant providers each require a custom provider-specific box format. 4 | This folder shows the example contents of a box for the `google` provider. 5 | To turn this into a box: 6 | 7 | ``` 8 | $ tar cvzf ../../google.box ./metadata.json ./Vagrantfile 9 | ``` 10 | 11 | This box works by using Vagrant's built-in Vagrantfile merging to setup 12 | defaults for Google. These defaults can easily be overwritten by higher-level 13 | Vagrantfiles (such as project root Vagrantfiles). 14 | -------------------------------------------------------------------------------- /example_boxes/gce-test/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | Vagrant.configure("2") do |config| 18 | config.vm.provider :google do |_google, override| 19 | if ENV['GOOGLE_SSH_USER'] and ENV['GOOGLE_SSH_KEY_LOCATION'] 20 | override.ssh.username = ENV['GOOGLE_SSH_USER'] 21 | override.ssh.private_key_path = ENV['GOOGLE_SSH_KEY_LOCATION'] 22 | end 23 | _google.image_family = "debian-9" 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /example_boxes/gce-test/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google" 3 | } 4 | -------------------------------------------------------------------------------- /example_boxes/gce/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "google" 3 | } 4 | -------------------------------------------------------------------------------- /google-test.box: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchellh/vagrant-google/9fa82b186b8b6981e6210ad1e2599ab7b73423f9/google-test.box -------------------------------------------------------------------------------- /google.box: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mitchellh/vagrant-google/9fa82b186b8b6981e6210ad1e2599ab7b73423f9/google.box -------------------------------------------------------------------------------- /lib/vagrant-google.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | require "pathname" 16 | require "vagrant-google/plugin" 17 | 18 | module VagrantPlugins 19 | module Google 20 | lib_path = Pathname.new(File.expand_path("../vagrant-google", __FILE__)) 21 | autoload :Action, lib_path.join("action") 22 | autoload :Errors, lib_path.join("errors") 23 | 24 | # This returns the path to the source of this plugin. 25 | # 26 | # @return [Pathname] 27 | def self.source_root 28 | @source_root ||= Pathname.new(File.expand_path("../../", __FILE__)) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/vagrant-google/action.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require "vagrant/action/builder" 15 | 16 | module VagrantPlugins 17 | module Google 18 | module Action # rubocop:disable Metrics/ModuleLength 19 | # Include the built-in modules so we can use them as top-level things. 20 | include Vagrant::Action::Builtin 21 | 22 | # This action is called to halt the remote machine. 23 | def self.action_halt 24 | Vagrant::Action::Builder.new.tap do |b| 25 | b.use ConfigValidate 26 | b.use Call, IsCreated do |env, b2| 27 | unless env[:result] 28 | b2.use MessageNotCreated 29 | next 30 | end 31 | b2.use ConnectGoogle 32 | b2.use StopInstance 33 | end 34 | end 35 | end 36 | 37 | # This action is called to terminate the remote machine. 38 | def self.action_destroy 39 | Vagrant::Action::Builder.new.tap do |b| 40 | b.use Call, DestroyConfirm do |env, b2| 41 | if env[:result] 42 | b2.use ConfigValidate 43 | b2.use Call, IsCreated do |env2, b3| 44 | unless env2[:result] 45 | b3.use MessageNotCreated 46 | next 47 | end 48 | b3.use ConnectGoogle 49 | b3.use TerminateInstance 50 | end 51 | else 52 | b2.use MessageWillNotDestroy 53 | end 54 | end 55 | end 56 | end 57 | 58 | # This action is called when `vagrant provision` is called. 59 | def self.action_provision 60 | Vagrant::Action::Builder.new.tap do |b| 61 | b.use ConfigValidate 62 | b.use Call, IsCreated do |env, b2| 63 | unless env[:result] 64 | b2.use MessageNotCreated 65 | next 66 | end 67 | 68 | b2.use Provision 69 | b2.use SyncedFolders 70 | end 71 | end 72 | end 73 | 74 | # This action is called to read the SSH info of the machine. The 75 | # resulting state is expected to be put into the `:machine_ssh_info` 76 | # key. 77 | def self.action_read_ssh_info 78 | Vagrant::Action::Builder.new.tap do |b| 79 | b.use ConfigValidate 80 | b.use ConnectGoogle 81 | b.use ReadSSHInfo 82 | end 83 | end 84 | 85 | # This action is called to setup the Windows user/password on the machine. 86 | def self.action_setup_winrm_password 87 | Vagrant::Action::Builder.new.tap do |b| 88 | b.use ConfigValidate 89 | b.use ConnectGoogle 90 | b.use SetupWinrmPassword 91 | end 92 | end 93 | 94 | # This action is called to read the state of the machine. The 95 | # resulting state is expected to be put into the `:machine_state_id` 96 | # key. 97 | def self.action_read_state 98 | Vagrant::Action::Builder.new.tap do |b| 99 | b.use ConfigValidate 100 | b.use ConnectGoogle 101 | b.use ReadState 102 | end 103 | end 104 | 105 | # This action is called to SSH into the machine. 106 | def self.action_ssh 107 | Vagrant::Action::Builder.new.tap do |b| 108 | b.use ConfigValidate 109 | b.use Call, IsCreated do |env, b2| 110 | unless env[:result] 111 | b2.use MessageNotCreated 112 | next 113 | end 114 | 115 | b2.use SSHExec 116 | end 117 | end 118 | end 119 | 120 | def self.action_ssh_run 121 | Vagrant::Action::Builder.new.tap do |b| 122 | b.use ConfigValidate 123 | b.use Call, IsCreated do |env, b2| 124 | unless env[:result] 125 | b2.use MessageNotCreated 126 | next 127 | end 128 | 129 | b2.use SSHRun 130 | end 131 | end 132 | end 133 | 134 | # This action is called to bring the box up from nothing. 135 | def self.action_up 136 | Vagrant::Action::Builder.new.tap do |b| 137 | b.use HandleBox 138 | b.use ConfigValidate 139 | b.use BoxCheckOutdated 140 | b.use ConnectGoogle 141 | b.use Call, IsCreated do |env1, b1| 142 | if env1[:result] 143 | b1.use Call, IsTerminated do |env2, b2| 144 | if env2[:result] 145 | b2.use Provision 146 | b2.use SyncedFolders 147 | b2.use WarnNetworks 148 | b2.use WarnSshKeys 149 | b2.use StartInstance 150 | else 151 | # TODO: Impement better messages for different states 152 | b2.use MessageAlreadyCreated 153 | end 154 | end 155 | else 156 | b1.use Provision 157 | b1.use SyncedFolders 158 | b1.use WarnNetworks 159 | b1.use WarnSshKeys 160 | b1.use RunInstance 161 | b1.use AssignInstanceGroups 162 | end 163 | end 164 | end 165 | end 166 | 167 | def self.action_reload 168 | Vagrant::Action::Builder.new.tap do |b| 169 | b.use ConfigValidate 170 | b.use ConnectGoogle 171 | b.use Call, IsCreated do |env1, b1| 172 | unless env1[:result] 173 | b1.use MessageNotCreated 174 | next 175 | end 176 | 177 | # TODO: Think about implementing through server.reboot 178 | b1.use action_halt 179 | b1.use action_up 180 | end 181 | end 182 | end 183 | 184 | # The autoload farm 185 | action_root = Pathname.new(File.expand_path("../action", __FILE__)) 186 | autoload :AssignInstanceGroups, action_root.join("assign_instance_groups") 187 | autoload :ConnectGoogle, action_root.join("connect_google") 188 | autoload :IsCreated, action_root.join("is_created") 189 | autoload :IsTerminated, action_root.join("is_terminated") 190 | autoload :MessageAlreadyCreated, action_root.join("message_already_created") 191 | autoload :MessageNotCreated, action_root.join("message_not_created") 192 | autoload :MessageWillNotDestroy, action_root.join("message_will_not_destroy") 193 | autoload :ReadSSHInfo, action_root.join("read_ssh_info") 194 | autoload :SetupWinrmPassword, action_root.join('setup_winrm_password') 195 | autoload :ReadState, action_root.join("read_state") 196 | autoload :RunInstance, action_root.join("run_instance") 197 | autoload :StartInstance, action_root.join("start_instance") 198 | autoload :StopInstance, action_root.join("stop_instance") 199 | autoload :TerminateInstance, action_root.join("terminate_instance") 200 | autoload :TimedProvision, action_root.join("timed_provision") 201 | autoload :WarnNetworks, action_root.join("warn_networks") 202 | autoload :WarnSshKeys, action_root.join("warn_ssh_keys") 203 | end 204 | end 205 | end 206 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/assign_instance_groups.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | module VagrantPlugins 15 | module Google 16 | module Action 17 | # Action to assign instance groups. Looks for the 'instance_group' 18 | # parameter in the zone config and adds the instance to it. 19 | # If the instance group does not exist in the specified zone, it tries to 20 | # create one first. 21 | # 22 | # This action manipulates unmanaged instance groups 23 | # https://cloud.google.com/compute/docs/instance-groups/unmanaged-groups 24 | class AssignInstanceGroups 25 | def initialize(app, env) 26 | @app = app 27 | @logger = Log4r::Logger.new( 28 | "vagrant_google::action::assign_instance_groups" 29 | ) 30 | end 31 | 32 | def call(env) 33 | zone = env[:machine].provider_config.zone 34 | zone_config = env[:machine].provider_config.get_zone_config(zone) 35 | instance_name = zone_config.name 36 | instance_group_name = zone_config.instance_group 37 | network = zone_config.network 38 | subnetwork = zone_config.subnetwork 39 | 40 | if instance_group_name 41 | group = env[:google_compute].instance_groups.get(instance_group_name, 42 | zone) 43 | if group.nil? 44 | # If instance group doesn't exist, attempt to create it 45 | env[:ui].info(I18n.t("vagrant_google.instance_group_create")) 46 | instance_group_config = { 47 | name: instance_group_name, 48 | zone: zone, 49 | description: "Created by Vagrant", 50 | network: network, 51 | subnetwork: subnetwork, 52 | } 53 | env[:google_compute].instance_groups.create(instance_group_config) 54 | end 55 | 56 | # Add the machine to instance group 57 | env[:ui].info(I18n.t("vagrant_google.instance_group_add")) 58 | 59 | # Fixup with add_instance_group_instance after adding to fog 60 | # See https://github.com/fog/fog-google/issues/308 61 | response = env[:google_compute].add_instance_group_instances( 62 | instance_group_name, 63 | zone, 64 | [instance_name] 65 | ) 66 | unless response.status == "DONE" 67 | operation = env[:google_compute].operations.get(response.name, zone) 68 | env[:ui].info(I18n.t("vagrant_google.waiting_for_operation", 69 | name: operation.name)) 70 | operation.wait_for { ready? } 71 | end 72 | end 73 | 74 | @app.call(env) 75 | end 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/connect_google.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require "fog/google" 15 | require "log4r" 16 | 17 | module VagrantPlugins 18 | module Google 19 | module Action 20 | # This action connects to Google, verifies credentials work, and 21 | # puts the Google connection object into the `:google_compute` key 22 | # in the environment. 23 | class ConnectGoogle 24 | def initialize(app, env) 25 | @app = app 26 | @logger = Log4r::Logger.new("vagrant_google::action::connect_google") 27 | end 28 | 29 | # Initialize Fog::Compute and add it to the environment 30 | def call(env) 31 | provider_config = env[:machine].provider_config 32 | 33 | # Build fog config 34 | fog_config = { 35 | :provider => :google, 36 | :google_project => provider_config.google_project_id, 37 | } 38 | 39 | unless provider_config.google_json_key_location.nil? 40 | fog_config[:google_json_key_location] = find_key(provider_config.google_json_key_location, env) 41 | else 42 | fog_config[:google_application_default] = true 43 | end 44 | 45 | @logger.info("Creating Google API client and adding to Vagrant environment") 46 | env[:google_compute] = Fog::Compute.new(fog_config) 47 | @app.call(env) 48 | end 49 | 50 | # If the key is not found, try expanding from root location (see #159) 51 | def find_key(location, env) 52 | if File.file?(File.expand_path(location)) 53 | return File.expand_path(location) 54 | else 55 | return File.expand_path(location, env[:root_path]) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/is_created.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | module VagrantPlugins 15 | module Google 16 | module Action 17 | # This can be used with "Call" built-in to check if the machine 18 | # is created and branch in the middleware. 19 | class IsCreated 20 | def initialize(app, env) 21 | @app = app 22 | end 23 | 24 | def call(env) 25 | env[:result] = env[:machine].state.id != :not_created 26 | @app.call(env) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/is_terminated.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | module VagrantPlugins 15 | module Google 16 | module Action 17 | # This can be used with "Call" built-in to check if the machine 18 | # is stopped and branch in the middleware. 19 | class IsTerminated 20 | def initialize(app, env) 21 | @app = app 22 | end 23 | 24 | def call(env) 25 | env[:result] = env[:machine].state.id == :TERMINATED 26 | @app.call(env) 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/message_already_created.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | module VagrantPlugins 15 | module Google 16 | module Action 17 | class MessageAlreadyCreated 18 | def initialize(app, env) 19 | @app = app 20 | end 21 | 22 | def call(env) 23 | env[:ui].info(I18n.t("vagrant_google.already_created")) 24 | @app.call(env) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/message_not_created.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | module VagrantPlugins 15 | module Google 16 | module Action 17 | class MessageNotCreated 18 | def initialize(app, env) 19 | @app = app 20 | end 21 | 22 | def call(env) 23 | env[:ui].info(I18n.t("vagrant_google.not_created")) 24 | @app.call(env) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/message_will_not_destroy.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | module VagrantPlugins 15 | module Google 16 | module Action 17 | class MessageWillNotDestroy 18 | def initialize(app, env) 19 | @app = app 20 | end 21 | 22 | def call(env) 23 | env[:ui].info(I18n.t("vagrant_google.will_not_destroy", name: env[:machine].name)) 24 | @app.call(env) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/read_ssh_info.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require "log4r" 15 | 16 | module VagrantPlugins 17 | module Google 18 | module Action 19 | # This action reads the SSH info for the machine and puts it into the 20 | # `:machine_ssh_info` key in the environment. 21 | class ReadSSHInfo 22 | def initialize(app, env) 23 | @app = app 24 | @logger = Log4r::Logger.new("vagrant_google::action::read_ssh_info") 25 | end 26 | 27 | def call(env) 28 | env[:machine_ssh_info] = read_ssh_info(env[:google_compute], env[:machine]) 29 | 30 | @app.call(env) 31 | end 32 | 33 | def read_ssh_info(google, machine) 34 | return nil if machine.id.nil? 35 | # Find the machine 36 | zone = machine.provider_config.zone 37 | server = google.servers.get(machine.id, zone) 38 | if server.nil? 39 | # The machine can't be found 40 | @logger.info("Machine '#{zone}:#{machine.id}'couldn't be found, assuming it got destroyed.") 41 | machine.id = nil 42 | return nil 43 | end 44 | 45 | # Get private_ip setting 46 | use_private_ip = machine.provider_config.get_zone_config(zone).use_private_ip 47 | 48 | # Default to use public ip address 49 | ssh_info = { 50 | :host => server.public_ip_addresses[0], 51 | :port => 22 52 | } 53 | 54 | if use_private_ip then 55 | ssh_info = { 56 | :host => server.private_ip_addresses[0], 57 | :port => 22 58 | } 59 | end 60 | 61 | # Return SSH network info 62 | return ssh_info 63 | end 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/read_state.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require "log4r" 15 | 16 | module VagrantPlugins 17 | module Google 18 | module Action 19 | # This action reads the state of the machine and puts it in the 20 | # `:machine_state_id` key in the environment. 21 | class ReadState 22 | def initialize(app, env) 23 | @app = app 24 | @logger = Log4r::Logger.new("vagrant_google::action::read_state") 25 | end 26 | 27 | def call(env) 28 | env[:machine_state_id] = read_state(env[:google_compute], env[:machine]) 29 | 30 | @app.call(env) 31 | end 32 | 33 | def read_state(google, machine) 34 | return :not_created if machine.id.nil? 35 | 36 | # Find the machine 37 | zone = machine.provider_config.zone 38 | # TODO(erjohnso): not sure why this is necessary, 'server' should be nil 39 | begin 40 | server = google.servers.get(machine.id, zone) 41 | rescue Exception => e 42 | @logger.info("TODO: this shouldn't be happening. Call should return nil") 43 | @logger.info(e.message) 44 | server = nil 45 | end 46 | if server.nil? || [:"shutting-down", :terminated].include?(server.status.to_sym) 47 | # The machine can't be found 48 | @logger.info("Machine '#{zone}:#{machine.id}' not found or terminated, assuming it got destroyed.") 49 | machine.id = nil 50 | return :not_created 51 | end 52 | 53 | # Return the state 54 | return server.status.to_sym 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/run_instance.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require "log4r" 15 | require 'vagrant/util/retryable' 16 | require 'vagrant-google/util/timer' 17 | require 'vagrant-google/action/setup_winrm_password' 18 | 19 | module VagrantPlugins 20 | module Google 21 | module Action 22 | # This runs the configured instance. 23 | class RunInstance # rubocop:disable Metrics/ClassLength 24 | include Vagrant::Util::Retryable 25 | 26 | FOG_ERRORS = [ 27 | Fog::Compute::Google::NotFound, 28 | Fog::Compute::Google::Error, 29 | Fog::Errors::Error 30 | ].freeze 31 | 32 | def initialize(app, env) 33 | @app = app 34 | @logger = Log4r::Logger.new("vagrant_google::action::run_instance") 35 | end 36 | 37 | def call(env) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize 38 | # Initialize metrics if they haven't been 39 | env[:metrics] ||= {} 40 | 41 | # Get the zone we're going to booting up in 42 | zone = env[:machine].provider_config.zone 43 | region = zone.split('-')[0..1].join('-') 44 | 45 | # Get the configs 46 | zone_config = env[:machine].provider_config.get_zone_config(zone) 47 | image = zone_config.image 48 | image_family = zone_config.image_family 49 | image_project_id = zone_config.image_project_id 50 | instance_group = zone_config.instance_group 51 | name = zone_config.name 52 | machine_type = zone_config.machine_type 53 | disk_size = zone_config.disk_size 54 | disk_name = zone_config.disk_name 55 | disk_type = zone_config.disk_type 56 | network = zone_config.network 57 | network_project_id = zone_config.network_project_id 58 | subnetwork = zone_config.subnetwork 59 | metadata = zone_config.metadata 60 | labels = zone_config.labels 61 | tags = zone_config.tags 62 | can_ip_forward = zone_config.can_ip_forward 63 | use_private_ip = zone_config.use_private_ip 64 | external_ip = zone_config.external_ip 65 | network_ip = zone_config.network_ip 66 | preemptible = zone_config.preemptible 67 | auto_restart = zone_config.auto_restart 68 | on_host_maintenance = zone_config.on_host_maintenance 69 | autodelete_disk = zone_config.autodelete_disk 70 | service_account_scopes = zone_config.scopes 71 | service_account = zone_config.service_account 72 | project_id = zone_config.google_project_id 73 | additional_disks = zone_config.additional_disks 74 | accelerators = zone_config.accelerators 75 | enable_secure_boot = zone_config.enable_secure_boot 76 | enable_display = zone_config.enable_display 77 | enable_vtpm = zone_config.enable_vtpm 78 | enable_integrity_monitoring = zone_config.enable_integrity_monitoring 79 | resource_policies = zone_config.resource_policies 80 | 81 | # Launch! 82 | env[:ui].info(I18n.t("vagrant_google.launching_instance")) 83 | env[:ui].info(" -- Name: #{name}") 84 | env[:ui].info(" -- Project: #{project_id}") 85 | env[:ui].info(" -- Type: #{machine_type}") 86 | env[:ui].info(" -- Disk type: #{disk_type}") 87 | env[:ui].info(" -- Disk size: #{disk_size} GB") 88 | env[:ui].info(" -- Disk name: #{disk_name}") 89 | env[:ui].info(" -- Image: #{image}") 90 | env[:ui].info(" -- Image family: #{image_family}") 91 | env[:ui].info(" -- Image Project: #{image_project_id}") if image_project_id 92 | env[:ui].info(" -- Instance Group: #{instance_group}") 93 | env[:ui].info(" -- Zone: #{zone}") if zone 94 | env[:ui].info(" -- Network: #{network}") if network 95 | env[:ui].info(" -- Network Project: #{network_project_id}") if network_project_id 96 | env[:ui].info(" -- Subnetwork: #{subnetwork}") if subnetwork 97 | env[:ui].info(" -- Metadata: '#{metadata}'") 98 | env[:ui].info(" -- Labels: '#{labels}'") 99 | env[:ui].info(" -- Network tags: '#{tags}'") 100 | env[:ui].info(" -- IP Forward: #{can_ip_forward}") 101 | env[:ui].info(" -- Use private IP: #{use_private_ip}") 102 | env[:ui].info(" -- External IP: #{external_ip}") 103 | env[:ui].info(" -- Network IP: #{network_ip}") 104 | env[:ui].info(" -- Preemptible: #{preemptible}") 105 | env[:ui].info(" -- Auto Restart: #{auto_restart}") 106 | env[:ui].info(" -- On Maintenance: #{on_host_maintenance}") 107 | env[:ui].info(" -- Autodelete Disk: #{autodelete_disk}") 108 | env[:ui].info(" -- Scopes: #{service_account_scopes}") if service_account_scopes 109 | env[:ui].info(" -- Service Account: #{service_account}") if service_account 110 | env[:ui].info(" -- Additional Disks: #{additional_disks}") 111 | env[:ui].info(" -- Accelerators: #{accelerators}") 112 | env[:ui].info(" -- Secure Boot: #{enable_secure_boot}") if enable_secure_boot 113 | env[:ui].info(" -- Display Device: #{enable_display}") if enable_display 114 | env[:ui].info(" -- vTPM: #{enable_vtpm}") if enable_vtpm 115 | env[:ui].info(" -- Integrity Monitoring: #{enable_integrity_monitoring}") if enable_integrity_monitoring 116 | env[:ui].info(" -- Resource policies: #{resource_policies}") if resource_policies != [] 117 | 118 | 119 | # Munge image config 120 | if image_family 121 | image_source = "image_family: #{image_family}, image_project_id: #{image_project_id}" 122 | image = env[:google_compute].images.get_from_family(image_family, image_project_id) 123 | else 124 | image_source = "image: #{image}, image_project_id: #{image_project_id}" 125 | image = env[:google_compute].images.get(image, image_project_id) 126 | end 127 | unless image 128 | raise Errors::ImageNotFound, :image => image_source 129 | end 130 | image = image.self_link 131 | 132 | # Munge network configs 133 | if network != 'default' 134 | network = "projects/#{network_project_id}/global/networks/#{network}" 135 | subnetwork = "projects/#{network_project_id}/regions/#{region}/subnetworks/#{subnetwork}" 136 | else 137 | network = "global/networks/default" 138 | end 139 | 140 | if external_ip == false 141 | # No external IP 142 | network_interfaces = [ { :network => network, :subnetwork => subnetwork } ] 143 | else 144 | network_interfaces = [ { :network => network, :subnetwork => subnetwork, :access_configs => [{:name => 'External NAT', :type => 'ONE_TO_ONE_NAT'}]} ] 145 | end 146 | 147 | # Munge scheduling configs 148 | scheduling = { :automatic_restart => auto_restart, :on_host_maintenance => on_host_maintenance, :preemptible => preemptible} 149 | 150 | # Munge service_accounts / scopes config 151 | service_accounts = [ { :email => service_account, :scopes => service_account_scopes } ] 152 | 153 | # Construct accelerator URLs 154 | accelerators_url = [] 155 | accelerators.each do |accelerator| 156 | unless accelerator.key?(:type) 157 | next 158 | end 159 | accelerator_type = "https://compute.googleapis.com/compute/v1/projects/#{project_id}/zones/#{zone}/acceleratorTypes/#{accelerator[:type]}" 160 | accelerator_count = accelerator.fetch(:count, 1) 161 | accelerators_url.push({ :accelerator_type => accelerator_type, 162 | :accelerator_count => accelerator_count }) 163 | end 164 | 165 | # Munge shieldedInstance config 166 | shielded_instance_config = { :enable_secure_boot => enable_secure_boot, :enable_vtpm => enable_vtpm, :enable_integrity_monitoring => enable_integrity_monitoring } 167 | 168 | # Munge displayDevice config 169 | display_device = { :enable_display => enable_display } 170 | 171 | resource_policies_urls = [] 172 | resource_policies.each do |policy| 173 | resource_policies_url = "https://compute.googleapis.com/compute/v1/projects/#{project_id}/regions/#{region}/resourcePolicies/#{policy}" 174 | resource_policies_urls.push(resource_policies_url) 175 | end 176 | 177 | begin 178 | request_start_time = Time.now.to_i 179 | disk = nil 180 | # Check if specified external ip is available 181 | external_ip = get_external_ip(env, external_ip) if external_ip 182 | # Check if disk type is available in the zone and set the proper resource link 183 | disk_type = get_disk_type(env, disk_type, zone) 184 | 185 | disk_created_by_vagrant = false 186 | if disk_name.nil? 187 | # no disk_name... disk_name defaults to instance name 188 | disk = env[:google_compute].disks.create( 189 | name: name, 190 | size_gb: disk_size, 191 | type: disk_type, 192 | zone_name: zone, 193 | source_image: image 194 | ) 195 | disk.wait_for { disk.ready? } 196 | disk_created_by_vagrant = true 197 | else 198 | disk = env[:google_compute].disks.get(disk_name, zone) 199 | if disk.nil? 200 | # disk not found... create it with name 201 | disk = env[:google_compute].disks.create( 202 | name: disk_name, 203 | size_gb: disk_size, 204 | type: disk_type, 205 | zone_name: zone, 206 | source_image: image 207 | ) 208 | disk.wait_for { disk.ready? } 209 | disk_created_by_vagrant = true 210 | end 211 | end 212 | 213 | # Add boot disk to the instance 214 | disks = [disk.get_as_boot_disk(true, autodelete_disk)] 215 | 216 | # Configure additional disks 217 | additional_disks.each_with_index do |disk_config, index| 218 | additional_disk = nil 219 | 220 | # Get additional disk image 221 | # Create a blank disk if neither image nor additional_disk_image is provided 222 | additional_disk_image = nil 223 | if disk_config[:image_family] 224 | additional_disk_image = env[:google_compute].images.get_from_family(disk_config[:image_family], disk_config[:image_project_id]).self_link 225 | elsif disk_config[:image] 226 | additional_disk_image = env[:google_compute].images.get(disk_config[:image], disk_config[:image_project_id]).self_link 227 | end 228 | 229 | # Get additional disk size 230 | additional_disk_size = nil 231 | if disk_config[:disk_size].nil? 232 | # Default disk size is 10 GB 233 | additional_disk_size = 10 234 | else 235 | additional_disk_size = disk_config[:disk_size] 236 | end 237 | 238 | # Get additional disk type 239 | additional_disk_type = nil 240 | if disk_config[:disk_type].nil? 241 | # Default disk type is pd-standard 242 | additional_disk_type = get_disk_type(env, "pd-standard", zone) 243 | else 244 | additional_disk_type = get_disk_type(env, disk_config[:disk_type], zone) 245 | end 246 | 247 | # Get additional disk auto delete 248 | additional_disk_auto_delete = nil 249 | if disk_config[:autodelete_disk].nil? 250 | # Default auto delete to true 251 | additional_disk_auto_delete = true 252 | else 253 | additional_disk_auto_delete = disk_config[:autodelete_disk] 254 | end 255 | 256 | # Get additional disk name 257 | additional_disk_name = nil 258 | if disk_config[:disk_name].nil? 259 | # no disk_name... disk_name defaults to instance (name + "-additional-disk-#{index}" 260 | additional_disk_name = name + "-additional-disk-#{index}" 261 | additional_disk = env[:google_compute].disks.create( 262 | name: additional_disk_name, 263 | size_gb: additional_disk_size, 264 | type: additional_disk_type, 265 | zone_name: zone, 266 | source_image: additional_disk_image 267 | ) 268 | else 269 | # additional_disk_name set in disk_config 270 | additional_disk_name = disk_config[:disk_name] 271 | 272 | additional_disk = env[:google_compute].disks.get(additional_disk_name, zone) 273 | if additional_disk.nil? 274 | # disk not found... create it with name 275 | additional_disk = env[:google_compute].disks.create( 276 | name: additional_disk_name, 277 | size_gb: additional_disk_size, 278 | type: additional_disk_type, 279 | zone_name: zone, 280 | source_image: additional_disk_image 281 | ) 282 | additional_disk.wait_for { additional_disk.ready? } 283 | end 284 | end 285 | 286 | # Add additional disk to the instance 287 | disks.push(additional_disk.attached_disk_obj(boot:false, writable:true, auto_delete:additional_disk_auto_delete)) 288 | end 289 | 290 | defaults = { 291 | :name => name, 292 | :zone => zone, 293 | :machine_type => machine_type, 294 | :disk_size => disk_size, 295 | :disk_type => disk_type, 296 | :image => image, 297 | :network_interfaces => network_interfaces, 298 | :metadata => { :items => metadata.each.map { |k, v| { :key => k.to_s, :value => v.to_s } } }, 299 | :labels => labels, 300 | :tags => { :items => tags }, 301 | :can_ip_forward => can_ip_forward, 302 | :use_private_ip => use_private_ip, 303 | :external_ip => external_ip, 304 | :network_ip => network_ip, 305 | :disks => disks, 306 | :scheduling => scheduling, 307 | :service_accounts => service_accounts, 308 | :guest_accelerators => accelerators_url, 309 | :resource_policies => resource_policies_urls 310 | } 311 | 312 | # XXX HACK - only add of the parameters are set in :shielded_instance_config we need to drop the field from 313 | # the API call otherwise we'll error out with Google::Apis::ClientError 314 | # TODO(temikus): Remove if the API changes, see internal GOOG ref: b/175063371 315 | if shielded_instance_config.has_value?(true) 316 | defaults[:shielded_instance_config] = shielded_instance_config 317 | end 318 | 319 | if display_device.has_value?(true) 320 | defaults[:display_device] = display_device 321 | end 322 | 323 | server = env[:google_compute].servers.create(defaults) 324 | @logger.info("Machine '#{zone}:#{name}' created.") 325 | rescue *FOG_ERRORS => e 326 | # TODO: Cleanup the Fog catch-all once Fog implements better exceptions 327 | # There is a chance Google has failed to create an instance, so we need 328 | # to clean up the created disk. 329 | disks.each do |disk| 330 | cleanup_disk(disk.name, env) if disk && disk_created_by_vagrant 331 | end 332 | raise Errors::FogError, :message => e.message 333 | end 334 | 335 | # Immediately save the name since the instance has been created 336 | env[:machine].id = server.name 337 | server.reload 338 | 339 | env[:ui].info(I18n.t("vagrant_google.waiting_for_ready")) 340 | begin 341 | server.wait_for { ready? } 342 | env[:metrics]["instance_ready_time"] = Time.now.to_i - request_start_time 343 | @logger.info("Time for instance ready: #{env[:metrics]["instance_ready_time"]}") 344 | env[:ui].info(I18n.t("vagrant_google.ready")) 345 | rescue 346 | env[:interrupted] = true 347 | end 348 | 349 | # Parse out the image project in case it was not set 350 | # and check if it is part of a public windows project 351 | img_project = image.split("/")[6] 352 | is_windows_image = img_project.eql?("windows-cloud") || img_project.eql?("windows-sql-cloud") 353 | 354 | # Reset the password if a windows image unless flag overrides 355 | setup_winrm_password = zone_config.setup_winrm_password 356 | if setup_winrm_password.nil? && is_windows_image 357 | setup_winrm_password = true 358 | end 359 | 360 | if setup_winrm_password 361 | env[:ui].info("Setting up WinRM Password") 362 | env[:action_runner].run(Action.action_setup_winrm_password, env) 363 | end 364 | 365 | unless env[:terminated] 366 | env[:metrics]["instance_comm_time"] = Util::Timer.time do 367 | # Wait for Comms to be ready. 368 | env[:ui].info(I18n.t("vagrant_google.waiting_for_comm")) 369 | while true 370 | # If we're interrupted just back out 371 | break if env[:interrupted] 372 | break if env[:machine].communicate.ready? 373 | sleep 2 374 | end 375 | end 376 | @logger.info("Time for Comms ready: #{env[:metrics]["instance_comm_time"]}") 377 | env[:ui].info(I18n.t("vagrant_google.ready_comm")) unless env[:interrupted] 378 | end 379 | 380 | # Terminate the instance if we were interrupted 381 | terminate(env) if env[:interrupted] 382 | 383 | @app.call(env) 384 | end 385 | 386 | def recover(env) 387 | return if env["vagrant.error"].is_a?(Vagrant::Errors::VagrantError) 388 | 389 | if env[:machine].provider.state.id != :not_created 390 | # Undo the import 391 | terminate(env) 392 | end 393 | end 394 | 395 | def terminate(env) 396 | destroy_env = env.dup 397 | destroy_env.delete(:interrupted) 398 | destroy_env[:config_validate] = false 399 | destroy_env[:force_confirm_destroy] = true 400 | env[:action_runner].run(Action.action_destroy, destroy_env) 401 | end 402 | 403 | def get_disk_type(env, disk_type, zone) 404 | begin 405 | # TODO(temikus): Outsource parsing logic to fog-google 406 | disk_type = env[:google_compute].get_disk_type(disk_type, zone).self_link 407 | rescue Fog::Errors::NotFound 408 | raise Errors::DiskTypeError, 409 | :disktype => disk_type 410 | end 411 | disk_type 412 | end 413 | 414 | def get_external_ip(env, external_ip) 415 | address = env[:google_compute].addresses.get_by_ip_address_or_name(external_ip) 416 | if address.nil? 417 | raise Errors::ExternalIpDoesNotExistError, 418 | :externalip => external_ip 419 | end 420 | if address.in_use? 421 | raise Errors::ExternalIpInUseError, 422 | :externalip => external_ip 423 | end 424 | # Resolve the name to IP address 425 | address.address 426 | end 427 | 428 | def cleanup_disk(disk_name, env) 429 | zone = env[:machine].provider_config.zone 430 | autodelete_disk = env[:machine].provider_config.get_zone_config(zone).autodelete_disk 431 | if autodelete_disk 432 | disk = env[:google_compute].disks.get(disk_name, zone) 433 | disk.destroy(false) if disk 434 | end 435 | end 436 | end 437 | end 438 | end 439 | end 440 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/setup_winrm_password.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | # Changes: 16 | # April 2019: Modified example found here: 17 | # https://github.com/GoogleCloudPlatform/compute-image-windows/blob/master/examples/windows_auth_python_sample.py 18 | # to enable WinRM with vagrant. 19 | 20 | module VagrantPlugins 21 | module Google 22 | module Action 23 | # Sets up a temporary WinRM password using Google's method for 24 | # establishing a new password over encrypted channels. 25 | class SetupWinrmPassword 26 | def initialize(app, env) 27 | @app = app 28 | @logger = Log4r::Logger.new("vagrant_google::action::setup_winrm_password") 29 | end 30 | 31 | def setup_password(env, instance, zone, user) 32 | # Setup 33 | compute = env[:google_compute] 34 | server = compute.servers.get(instance, zone) 35 | password = server.reset_windows_password(user) 36 | 37 | env[:ui].info("Temp Password: #{password}") 38 | 39 | password 40 | end 41 | 42 | def call(env) 43 | # Get the configs 44 | zone = env[:machine].provider_config.zone 45 | zone_config = env[:machine].provider_config.get_zone_config(zone) 46 | 47 | instance = zone_config.name 48 | user = env[:machine].config.winrm.username 49 | pass = env[:machine].config.winrm.password 50 | 51 | # Get Temporary Password, set WinRM password 52 | temp_pass = setup_password(env, instance, zone, user) 53 | env[:machine].config.winrm.password = temp_pass 54 | 55 | # Wait for WinRM To be Ready 56 | env[:ui].info("Waiting for WinRM To be ready") 57 | env[:machine].communicate.wait_for_ready(60) 58 | 59 | # Use WinRM to Change Password to one in Vagrantfile 60 | env[:ui].info("Changing password from temporary to winrm password") 61 | winrmcomm = VagrantPlugins::CommunicatorWinRM::Communicator.new(env[:machine]) 62 | cmd = "net user #{user} #{pass}" 63 | opts = { elevated: true } 64 | winrmcomm.test(cmd, opts) 65 | 66 | # Update WinRM password to reflect updated one 67 | env[:machine].config.winrm.password = pass 68 | end 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/start_instance.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require 'log4r' 15 | require 'vagrant/util/retryable' 16 | require 'vagrant-google/util/timer' 17 | 18 | module VagrantPlugins 19 | module Google 20 | module Action 21 | # This starts a stopped instance. 22 | class StartInstance 23 | include Vagrant::Util::Retryable 24 | 25 | def initialize(app, env) 26 | @app = app 27 | @logger = Log4r::Logger.new("vagrant_google::action::start_instance") 28 | end 29 | 30 | def call(env) 31 | # Initialize metrics if they haven't been 32 | env[:metrics] ||= {} 33 | 34 | server = env[:google_compute].servers.get(env[:machine].id, env[:machine].provider_config.zone) 35 | 36 | env[:ui].info(I18n.t("vagrant_google.starting")) 37 | 38 | begin 39 | server.start 40 | 41 | # Wait for the instance to be ready first 42 | env[:metrics]["instance_ready_time"] = Util::Timer.time do 43 | 44 | tries = env[:machine].provider_config.instance_ready_timeout / 2 45 | 46 | env[:ui].info(I18n.t("vagrant_google.waiting_for_ready")) 47 | begin 48 | retryable(:on => Fog::Errors::TimeoutError, :tries => tries) do 49 | # If we're interrupted don't worry about waiting 50 | next if env[:interrupted] 51 | 52 | # Wait for the server to be ready 53 | server.wait_for(2) { ready? } 54 | end 55 | rescue Fog::Errors::TimeoutError 56 | # Notify the user 57 | raise Errors::InstanceReadyTimeout, 58 | timeout: env[:machine].provider_config.instance_ready_timeout 59 | end 60 | end 61 | rescue Fog::Compute::Google::Error => e 62 | raise Errors::FogError, :message => e.message 63 | end 64 | 65 | @logger.info("Time to instance ready: #{env[:metrics]["instance_ready_time"]}") 66 | 67 | unless env[:interrupted] 68 | env[:metrics]["instance_comm_time"] = Util::Timer.time do 69 | # Wait for Comms to be ready. 70 | env[:ui].info(I18n.t("vagrant_google.waiting_for_comm")) 71 | while true 72 | # If we're interrupted then just back out 73 | break if env[:interrupted] 74 | break if env[:machine].communicate.ready? 75 | sleep 2 76 | end 77 | end 78 | 79 | @logger.info("Time for Comms ready: #{env[:metrics]["instance_comm_time"]}") 80 | 81 | # Ready and booted! 82 | env[:ui].info(I18n.t("vagrant_google.ready")) 83 | end 84 | 85 | @app.call(env) 86 | end 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/stop_instance.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require 'log4r' 15 | require 'vagrant/util/retryable' 16 | require 'vagrant-google/util/timer' 17 | 18 | module VagrantPlugins 19 | module Google 20 | module Action 21 | # This stops the running instance. 22 | class StopInstance 23 | include Vagrant::Util::Retryable 24 | 25 | def initialize(app, env) 26 | @app = app 27 | @logger = Log4r::Logger.new("vagrant_google::action::stop_instance") 28 | end 29 | 30 | def call(env) 31 | server = env[:google_compute].servers.get(env[:machine].id, env[:machine].provider_config.zone) 32 | 33 | if env[:machine].state.id == :TERMINATED 34 | env[:ui].info(I18n.t("vagrant_google.already_status", :status => env[:machine].state.id)) 35 | else 36 | env[:ui].info(I18n.t("vagrant_google.stopping")) 37 | operation = server.stop 38 | operation.wait_for { ready? } 39 | end 40 | 41 | @app.call(env) 42 | end 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/terminate_instance.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require "log4r" 15 | 16 | module VagrantPlugins 17 | module Google 18 | module Action 19 | # This terminates the running instance. 20 | class TerminateInstance 21 | def initialize(app, env) 22 | @app = app 23 | @logger = Log4r::Logger.new("vagrant_google::action::terminate_instance") 24 | end 25 | 26 | def call(env) 27 | server = env[:google_compute].servers.get(env[:machine].id, env[:machine].provider_config.zone) 28 | 29 | # Destroy the server and remove the tracking ID 30 | # destroy() is called with 'false' to disable asynchronous execution. 31 | # TODO: Add "override_async" option for faster test 32 | # TODO: Look at fog logic for possibly making sync faster 33 | env[:ui].info(I18n.t("vagrant_google.terminating")) 34 | server.destroy(false) unless server.nil? 35 | env[:machine].id = nil 36 | 37 | @app.call(env) 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/timed_provision.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require "vagrant-google/util/timer" 15 | 16 | module VagrantPlugins 17 | module Google 18 | module Action 19 | # This is the same as the builtin provision except it times the 20 | # provisioner runs. 21 | class TimedProvision < Vagrant::Action::Builtin::Provision 22 | def run_provisioner(env, name, p) 23 | timer = Util::Timer.time do 24 | super 25 | end 26 | 27 | env[:metrics] ||= {} 28 | env[:metrics]["provisioner_times"] ||= [] 29 | env[:metrics]["provisioner_times"] << [name, timer] 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/warn_networks.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | module VagrantPlugins 15 | module Google 16 | module Action 17 | class WarnNetworks 18 | def initialize(app, env) 19 | @app = app 20 | end 21 | 22 | def call(env) 23 | # Default SSH forward always exists so "> 1" 24 | if env[:machine].config.vm.networks.length > 1 25 | env[:ui].warn(I18n.t("vagrant_google.warn_networks")) 26 | end 27 | 28 | @app.call(env) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/vagrant-google/action/warn_ssh_keys.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | module VagrantPlugins 15 | module Google 16 | module Action 17 | class WarnSshKeys 18 | def initialize(app, env) 19 | @app = app 20 | end 21 | 22 | def call(env) 23 | # Warn on ssh-key overrides 24 | if env[:machine].config.ssh.username.nil? 25 | env[:ui].warn(I18n.t("vagrant_google.warn_ssh_vagrant_user")) 26 | end 27 | 28 | @app.call(env) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/vagrant-google/config.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require "vagrant" 15 | require "securerandom" 16 | 17 | module VagrantPlugins 18 | module Google 19 | class Config < Vagrant.plugin("2", :config) # rubocop:disable Metrics/ClassLength 20 | # The path to the Service Account json-formatted private key 21 | # 22 | # @return [String] 23 | attr_accessor :google_json_key_location 24 | 25 | # The Google Cloud Project ID (not name or number) 26 | # 27 | # @return [String] 28 | attr_accessor :google_project_id 29 | 30 | # The image name of the instance to use. 31 | # 32 | # @return [String] 33 | attr_accessor :image 34 | 35 | # The image family of the instance to use. 36 | # 37 | # @return [String] 38 | attr_accessor :image_family 39 | 40 | # The name of the image_project_id 41 | # 42 | # @return [String] 43 | attr_accessor :image_project_id 44 | 45 | # The instance group name to put the instance in. 46 | # 47 | # @return [String] 48 | attr_accessor :instance_group 49 | 50 | # The type of machine to launch, such as "n1-standard-1" 51 | # 52 | # @return [String] 53 | attr_accessor :machine_type 54 | 55 | # The size of disk in GB 56 | # 57 | # @return [Int] 58 | attr_accessor :disk_size 59 | 60 | # The name of the disk to be used, it it exists, it will be reused, otherwise created. 61 | # 62 | # @return [String] 63 | attr_accessor :disk_name 64 | 65 | # The type of the disk to be used, such as "pd-standard" 66 | # 67 | # @return [String] 68 | attr_accessor :disk_type 69 | 70 | # The user metadata string 71 | # 72 | # @return [Hash] 73 | attr_accessor :metadata 74 | 75 | # The name of the instance 76 | # 77 | # @return [String] 78 | attr_accessor :name 79 | 80 | # The name of the network 81 | # 82 | # @return [String] 83 | attr_accessor :network 84 | 85 | # The name of the network_project_id 86 | # 87 | # @return [String] 88 | attr_accessor :network_project_id 89 | 90 | # The name of the subnetwork 91 | # 92 | # @return [String] 93 | attr_accessor :subnetwork 94 | 95 | # Tags to apply to the instance 96 | # 97 | # @return [Array] 98 | attr_accessor :tags 99 | 100 | # Labels to apply to the instance 101 | # 102 | # @return [Hash] 103 | attr_accessor :labels 104 | 105 | # whether to enable ip forwarding 106 | # 107 | # @return Boolean 108 | attr_accessor :can_ip_forward 109 | 110 | # The external IP Address to use 111 | # 112 | # @return String 113 | attr_accessor :external_ip 114 | 115 | # The network IP Address to use 116 | # 117 | # @return String 118 | attr_accessor :network_ip 119 | 120 | # Use private ip address 121 | # 122 | # @return Boolean 123 | attr_accessor :use_private_ip 124 | 125 | # whether to autodelete disk on instance delete 126 | # 127 | # @return Boolean 128 | attr_accessor :autodelete_disk 129 | 130 | # Availability policy 131 | # whether to run instance as preemptible 132 | # 133 | # @return Boolean 134 | attr_accessor :preemptible 135 | 136 | # Availability policy 137 | # whether to have instance restart on failures 138 | # 139 | # @return Boolean 140 | attr_accessor :auto_restart 141 | 142 | # Availability policy 143 | # specify what to do when infrastructure maintenance events occur 144 | # Options: MIGRATE, TERMINATE 145 | # The default is MIGRATE. 146 | # 147 | # @return String 148 | attr_accessor :on_host_maintenance 149 | 150 | # The timeout value waiting for instance ready 151 | # 152 | # @return [Int] 153 | attr_accessor :instance_ready_timeout 154 | 155 | # The zone to launch the instance into. 156 | # If nil and the "default" network is set use the default us-central1-f. 157 | # 158 | # @return [String] 159 | attr_accessor :zone 160 | 161 | # The list of access scopes for instance. 162 | # 163 | # @return [Array] 164 | attr_accessor :scopes 165 | 166 | # Deprecated: the list of access scopes for instance. 167 | # 168 | # @return [Array] 169 | attr_accessor :service_accounts 170 | 171 | # IAM service account for instance. 172 | # 173 | # @return [String] 174 | attr_accessor :service_account 175 | 176 | # The configuration for additional disks. 177 | # 178 | # @return [Array] 179 | attr_accessor :additional_disks 180 | 181 | # (Optional - Override default WinRM setup before for Public Windows images) 182 | # 183 | # @return [Boolean] 184 | attr_accessor :setup_winrm_password 185 | 186 | # Accelerators 187 | # 188 | # @return [Array] 189 | attr_accessor :accelerators 190 | 191 | # whether the instance has Secure Boot enabled 192 | # 193 | # @return Boolean 194 | attr_accessor :enable_secure_boot 195 | 196 | # whether the instance has a display enabled 197 | # 198 | # @return Boolean 199 | attr_accessor :enable_display 200 | 201 | # whether the instance has the vTPM enabled 202 | # 203 | # @return Boolean 204 | attr_accessor :enable_vtpm 205 | 206 | # whether the instance has integrity monitoring enabled 207 | # 208 | # @return Boolean 209 | attr_accessor :enable_integrity_monitoring 210 | 211 | # The list of resource policies for instance. 212 | # 213 | # @return [Array] 214 | attr_accessor :resource_policies 215 | 216 | def initialize(zone_specific=false) 217 | @google_json_key_location = UNSET_VALUE 218 | @google_project_id = UNSET_VALUE 219 | @image = UNSET_VALUE 220 | @image_family = UNSET_VALUE 221 | @image_project_id = UNSET_VALUE 222 | @instance_group = UNSET_VALUE 223 | @machine_type = UNSET_VALUE 224 | @disk_size = UNSET_VALUE 225 | @disk_name = UNSET_VALUE 226 | @disk_type = UNSET_VALUE 227 | @metadata = {} 228 | @name = UNSET_VALUE 229 | @network = UNSET_VALUE 230 | @network_project_id = UNSET_VALUE 231 | @subnetwork = UNSET_VALUE 232 | @tags = [] 233 | @labels = {} 234 | @can_ip_forward = UNSET_VALUE 235 | @external_ip = UNSET_VALUE 236 | @network_ip = UNSET_VALUE 237 | @use_private_ip = UNSET_VALUE 238 | @autodelete_disk = UNSET_VALUE 239 | @preemptible = UNSET_VALUE 240 | @auto_restart = UNSET_VALUE 241 | @on_host_maintenance = UNSET_VALUE 242 | @instance_ready_timeout = UNSET_VALUE 243 | @zone = UNSET_VALUE 244 | @scopes = UNSET_VALUE 245 | @service_accounts = UNSET_VALUE 246 | @service_account = UNSET_VALUE 247 | @additional_disks = [] 248 | @setup_winrm_password = UNSET_VALUE 249 | @accelerators = [] 250 | @enable_secure_boot = UNSET_VALUE 251 | @enable_display = UNSET_VALUE 252 | @enable_vtpm = UNSET_VALUE 253 | @enable_integrity_monitoring = UNSET_VALUE 254 | @resource_policies = [] 255 | 256 | # Internal state (prefix with __ so they aren't automatically 257 | # merged) 258 | @__compiled_zone_configs = {} 259 | @__finalized = false 260 | @__zone_config = {} 261 | @__zone_specific = zone_specific 262 | end 263 | 264 | # Allows zone-specific overrides of any of the settings on this 265 | # configuration object. This allows the user to override things like 266 | # image and machine type name for zones. Example: 267 | # 268 | # google.zone_config "us-central1-f" do |zone| 269 | # zone.image = "ubuntu-1604-xenial-v20180306" 270 | # zone.machine_type = "n1-standard-4" 271 | # end 272 | # 273 | # @param [String] zone The zone name to configure. 274 | # @param [Hash] attributes Direct attributes to set on the configuration 275 | # as a shortcut instead of specifying a full block. 276 | # @yield [config] Yields a new Google configuration. 277 | def zone_config(zone, attributes=nil, &block) 278 | # Append the block to the list of zone configs for that zone. 279 | # We'll evaluate these upon finalization. 280 | @__zone_config[zone] ||= [] 281 | 282 | # Append a block that sets attributes if we got one 283 | if attributes 284 | attr_block = lambda do |config| 285 | config.set_options(attributes) 286 | end 287 | 288 | @__zone_config[zone] << attr_block 289 | end 290 | 291 | # Append a block if we got one 292 | @__zone_config[zone] << block if block_given? 293 | end 294 | 295 | #------------------------------------------------------------------- 296 | # Internal methods. 297 | #------------------------------------------------------------------- 298 | 299 | def merge(other) 300 | super.tap do |result| 301 | # Copy over the zone specific flag. "True" is retained if either 302 | # has it. 303 | new_zone_specific = other.instance_variable_get(:@__zone_specific) 304 | result.instance_variable_set( 305 | :@__zone_specific, new_zone_specific || @__zone_specific 306 | ) 307 | 308 | # Go through all the zone configs and prepend ours onto 309 | # theirs. 310 | new_zone_config = other.instance_variable_get(:@__zone_config) 311 | @__zone_config.each do |key, value| 312 | new_zone_config[key] ||= [] 313 | new_zone_config[key] = value + new_zone_config[key] 314 | end 315 | 316 | # Set it 317 | result.instance_variable_set(:@__zone_config, new_zone_config) 318 | 319 | # Merge in the metadata 320 | result.metadata = self.metadata.merge(other.metadata) 321 | 322 | # Merge in the labels 323 | result.labels = self.labels.merge(other.labels) 324 | 325 | # Merge in the tags 326 | result.tags |= self.tags 327 | result.tags |= other.tags 328 | 329 | # Merge in the additional disks 330 | result.additional_disks |= self.additional_disks 331 | result.additional_disks |= other.additional_disks 332 | end 333 | end 334 | 335 | def finalize! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity 336 | # Try to get access keys from standard Google environment variables; they 337 | # will default to nil if the environment variables are not present. 338 | @google_json_key_location = ENV['GOOGLE_JSON_KEY_LOCATION'] if @google_json_key_location == UNSET_VALUE 339 | @google_project_id = ENV['GOOGLE_PROJECT_ID'] if @google_project_id == UNSET_VALUE 340 | 341 | # Default image is nil 342 | @image = nil if @image == UNSET_VALUE 343 | 344 | # Default image family is nil 345 | @image_family = nil if @image_family == UNSET_VALUE 346 | 347 | # Default image project is nil 348 | @image_project_id = nil if @image_project_id == UNSET_VALUE 349 | 350 | # Default instance group name is nil 351 | @instance_group = nil if @instance_group == UNSET_VALUE 352 | 353 | # Default instance type is an n1-standard-1 354 | @machine_type = "n1-standard-1" if @machine_type == UNSET_VALUE 355 | 356 | # Default disk size is 10 GB 357 | @disk_size = 10 if @disk_size == UNSET_VALUE 358 | 359 | # Default disk name is nil 360 | @disk_name = nil if @disk_name == UNSET_VALUE 361 | 362 | # Default disk type is pd-standard 363 | @disk_type = "pd-standard" if @disk_type == UNSET_VALUE 364 | 365 | # Instance name defaults to a new datetime value + random seed 366 | # e.g. i-2015081013-15637fdb 367 | if @name == UNSET_VALUE 368 | t = Time.now 369 | @name = "i-#{t.strftime("%Y%m%d%H")}-" + SecureRandom.hex(4) 370 | end 371 | 372 | # Network defaults to 'default' 373 | @network = "default" if @network == UNSET_VALUE 374 | 375 | # Network project id defaults to project_id 376 | @network_project_id = @google_project_id if @network_project_id == UNSET_VALUE 377 | 378 | # Subnetwork defaults to nil 379 | @subnetwork = nil if @subnetwork == UNSET_VALUE 380 | 381 | # Default zone is us-central1-f if using the default network 382 | if @zone == UNSET_VALUE 383 | @zone = nil 384 | if @network == "default" 385 | @zone = "us-central1-f" 386 | end 387 | end 388 | 389 | # autodelete_disk defaults to true 390 | @autodelete_disk = true if @autodelete_disk == UNSET_VALUE 391 | 392 | # can_ip_forward defaults to nil 393 | @can_ip_forward = nil if @can_ip_forward == UNSET_VALUE 394 | 395 | # external_ip defaults to nil 396 | @external_ip = nil if @external_ip == UNSET_VALUE 397 | 398 | # network_ip defaults to nil 399 | @network_ip = nil if @network_ip == UNSET_VALUE 400 | 401 | # use_private_ip defaults to false 402 | @use_private_ip = false if @use_private_ip == UNSET_VALUE 403 | 404 | # preemptible defaults to false 405 | @preemptible = false if @preemptible == UNSET_VALUE 406 | 407 | # auto_restart defaults to true 408 | @auto_restart = true if @auto_restart == UNSET_VALUE 409 | 410 | # on_host_maintenance defaults to MIGRATE 411 | @on_host_maintenance = "MIGRATE" if @on_host_maintenance == UNSET_VALUE 412 | 413 | # Default instance_ready_timeout 414 | @instance_ready_timeout = 20 if @instance_ready_timeout == UNSET_VALUE 415 | 416 | # Default access scopes 417 | @scopes = nil if @scopes == UNSET_VALUE 418 | 419 | # Default access scopes 420 | @service_accounts = nil if @service_accounts == UNSET_VALUE 421 | 422 | # Default IAM service account 423 | @service_account = nil if @service_account == UNSET_VALUE 424 | 425 | # Default Setup WinRM Password 426 | @setup_winrm_password = nil if @setup_winrm_password == UNSET_VALUE 427 | 428 | # Config option service_accounts is deprecated 429 | if @service_accounts 430 | @scopes = @service_accounts 431 | end 432 | 433 | # enable_secure_boot defaults to nil 434 | @enable_secure_boot = false if @enable_secure_boot == UNSET_VALUE 435 | 436 | # enable_display defaults to nil 437 | @enable_display = false if @enable_display == UNSET_VALUE 438 | 439 | # enable_vtpm defaults to nil 440 | @enable_vtpm = false if @enable_vtpm == UNSET_VALUE 441 | 442 | # enable_integrity_monitoring defaults to nil 443 | @enable_integrity_monitoring = false if @enable_integrity_monitoring == UNSET_VALUE 444 | 445 | # Compile our zone specific configurations only within 446 | # NON-zone-SPECIFIC configurations. 447 | unless @__zone_specific 448 | @__zone_config.each do |zone, blocks| 449 | config = self.class.new(true).merge(self) 450 | 451 | # Execute the configuration for each block 452 | blocks.each { |b| b.call(config) } 453 | 454 | # The zone name of the configuration always equals the 455 | # zone config name: 456 | config.zone = zone 457 | 458 | # Finalize the configuration 459 | config.finalize! 460 | 461 | # Store it for retrieval 462 | @__compiled_zone_configs[zone] = config 463 | end 464 | end 465 | 466 | # Mark that we finalized 467 | @__finalized = true 468 | end 469 | 470 | def validate(machine) 471 | errors = _detected_errors 472 | 473 | errors << I18n.t("vagrant_google.config.zone_required") if @zone.nil? 474 | 475 | if @zone 476 | config = get_zone_config(@zone) 477 | 478 | # TODO: Check why provider-level settings are validated in the zone config 479 | errors << I18n.t("vagrant_google.config.google_project_id_required") if \ 480 | config.google_project_id.nil? 481 | 482 | if config.google_json_key_location 483 | errors << I18n.t("vagrant_google.config.private_key_missing") unless \ 484 | File.exist?(File.expand_path(config.google_json_key_location.to_s)) or 485 | File.exist?(File.expand_path(config.google_json_key_location.to_s, machine.env.root_path)) 486 | end 487 | 488 | if config.preemptible 489 | errors << I18n.t("vagrant_google.config.auto_restart_invalid_on_preemptible") if \ 490 | config.auto_restart 491 | errors << I18n.t("vagrant_google.config.on_host_maintenance_invalid_on_preemptible") unless \ 492 | config.on_host_maintenance == "TERMINATE" 493 | end 494 | 495 | if config.image_family 496 | errors << I18n.t("vagrant_google.config.image_and_image_family_set") if \ 497 | config.image 498 | end 499 | 500 | errors << I18n.t("vagrant_google.config.image_required") if config.image.nil? && config.image_family.nil? 501 | errors << I18n.t("vagrant_google.config.name_required") if @name.nil? 502 | 503 | if !config.accelerators.empty? 504 | errors << I18n.t("vagrant_google.config.on_host_maintenance_invalid_with_accelerators") unless \ 505 | config.on_host_maintenance == "TERMINATE" 506 | end 507 | end 508 | 509 | if @service_accounts 510 | machine.env.ui.warn(I18n.t("vagrant_google.config.service_accounts_deprecaated")) 511 | end 512 | 513 | { "Google Provider" => errors } 514 | end 515 | 516 | # This gets the configuration for a specific zone. It shouldn't 517 | # be called by the general public and is only used internally. 518 | def get_zone_config(name) 519 | unless @__finalized 520 | raise "Configuration must be finalized before calling this method." 521 | end 522 | 523 | # Return the compiled zone config 524 | @__compiled_zone_configs[name] || self 525 | end 526 | end 527 | end 528 | end 529 | -------------------------------------------------------------------------------- /lib/vagrant-google/errors.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require "vagrant" 15 | 16 | module VagrantPlugins 17 | module Google 18 | module Errors 19 | class VagrantGoogleError < Vagrant::Errors::VagrantError 20 | error_namespace("vagrant_google.errors") 21 | end 22 | 23 | class DiskTypeError < VagrantGoogleError 24 | error_key(:disk_type_error) 25 | end 26 | 27 | class FogError < VagrantGoogleError 28 | error_key(:fog_error) 29 | end 30 | 31 | class ExternalIpInUseError < VagrantGoogleError 32 | error_key(:external_ip_error) 33 | end 34 | 35 | class ExternalIpDoesNotExistError < VagrantGoogleError 36 | error_key(:external_ip_does_not_exist_error) 37 | end 38 | 39 | class InstanceReadyTimeout < VagrantGoogleError 40 | error_key(:instance_ready_timeout) 41 | end 42 | 43 | class RsyncError < VagrantGoogleError 44 | error_key(:rsync_error) 45 | end 46 | 47 | class ImageNotFound < VagrantGoogleError 48 | error_key(:image_not_found) 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/vagrant-google/plugin.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | begin 15 | require "vagrant" 16 | rescue LoadError 17 | raise "The Vagrant Google plugin must be run within Vagrant." 18 | end 19 | 20 | # This is a sanity check to make sure no one is attempting to install 21 | # this into an early Vagrant version. 22 | if Vagrant::VERSION < "1.2.0" 23 | raise "The Vagrant Google plugin is only compatible with Vagrant 1.2+" 24 | end 25 | 26 | module VagrantPlugins 27 | module Google 28 | class Plugin < Vagrant.plugin("2") 29 | name "Google" 30 | description <<-DESC 31 | This plugin installs a provider that allows Vagrant to manage 32 | Google Compute Engine instances. 33 | DESC 34 | 35 | config(:google, :provider) do 36 | require_relative "config" 37 | Config 38 | end 39 | 40 | provider(:google, parallel: true) do 41 | # Setup logging and i18n 42 | setup_logging 43 | setup_i18n 44 | 45 | # Return the provider 46 | require_relative "provider" 47 | Provider 48 | end 49 | 50 | # This initializes the internationalization strings. 51 | def self.setup_i18n 52 | I18n.load_path << File.expand_path("locales/en.yml", Google.source_root) 53 | I18n.reload! 54 | end 55 | 56 | # This sets up our log level to be whatever VAGRANT_LOG is. 57 | def self.setup_logging 58 | require "log4r" 59 | 60 | level = nil 61 | begin 62 | level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase) 63 | rescue NameError 64 | # This means that the logging constant wasn't found, 65 | # which is fine. We just keep `level` as `nil`. But 66 | # we tell the user. 67 | level = nil 68 | end 69 | 70 | # Some constants, such as "true" resolve to booleans, so the 71 | # above error checking doesn't catch it. This will check to make 72 | # sure that the log level is an integer, as Log4r requires. 73 | level = nil unless level.is_a?(Integer) 74 | 75 | # Set the logging level on all "vagrant" namespaced 76 | # logs as long as we have a valid level. 77 | if level 78 | logger = Log4r::Logger.new("vagrant_google") 79 | logger.outputters = Log4r::Outputter.stderr 80 | logger.level = level 81 | logger = nil 82 | end 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/vagrant-google/provider.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require "log4r" 15 | require "vagrant" 16 | 17 | module VagrantPlugins 18 | module Google 19 | class Provider < Vagrant.plugin("2", :provider) 20 | def initialize(machine) 21 | @machine = machine 22 | 23 | # Turn off NFS/SMB functionality by default, so machine always uses 24 | # rsync, see https://github.com/mitchellh/vagrant-google/issues/94 25 | @machine.config.nfs.functional = false unless ENV.has_key?('VAGRANT_GOOGLE_ENABLE_NFS') 26 | @machine.config.smb.functional = false unless ENV.has_key?('VAGRANT_GOOGLE_ENABLE_SMB') 27 | end 28 | 29 | def action(name) 30 | # Attempt to get the action method from the Action class if it 31 | # exists, otherwise return nil to show that we don't support the 32 | # given action. 33 | action_method = "action_#{name}" 34 | return Action.send(action_method) if Action.respond_to?(action_method) 35 | nil 36 | end 37 | 38 | def ssh_info 39 | # Run a custom action called "read_ssh_info" which does what it 40 | # says and puts the resulting SSH info into the `:machine_ssh_info` 41 | # key in the environment. 42 | env = @machine.action("read_ssh_info") 43 | env[:machine_ssh_info] 44 | end 45 | 46 | def state 47 | # Run a custom action we define called "read_state" which does 48 | # what it says. It puts the state in the `:machine_state_id` 49 | # key in the environment. 50 | env = @machine.action("read_state") 51 | 52 | state_id = env[:machine_state_id] 53 | 54 | # Get the short and long description 55 | short = I18n.t("vagrant_google.states.short_#{state_id}") 56 | long = I18n.t("vagrant_google.states.long_#{state_id}") 57 | 58 | # Return the MachineState object 59 | Vagrant::MachineState.new(state_id, short, long) 60 | end 61 | 62 | def to_s 63 | id = @machine.id.nil? ? "new" : @machine.id 64 | "Google (#{id})" 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/vagrant-google/util/timer.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | module VagrantPlugins 15 | module Google 16 | module Util 17 | class Timer 18 | # A basic utility method that times the execution of the given 19 | # block and returns it. 20 | def self.time 21 | start_time = Time.now.to_f 22 | yield 23 | end_time = Time.now.to_f 24 | 25 | end_time - start_time 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/vagrant-google/version.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | module VagrantPlugins 15 | module Google 16 | VERSION = "2.7.0".freeze 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_google: 3 | already_created: |- 4 | The machine is already created. 5 | already_status: |- 6 | The machine is already in %{status} status. 7 | instance_group_create: |- 8 | Instance group doesn't exist, trying to create... 9 | instance_group_add: |- 10 | Adding machine to the instance group... 11 | launching_instance: |- 12 | Launching an instance with the following settings... 13 | not_created: |- 14 | Instance is not created. Please run `vagrant up` first. 15 | ready: |- 16 | Machine is booted and ready for use! 17 | ready_comm: |- 18 | Machine is ready for Communicator access! 19 | rsync_not_found_warning: |- 20 | Warning! Folder sync disabled because the rsync binary is missing. 21 | Make sure rsync is installed and the binary can be found in the PATH. 22 | rsync_folder: |- 23 | Rsyncing folder: %{hostpath} => %{guestpath} 24 | starting: |- 25 | Starting the instance... 26 | stopping: |- 27 | Stopping the instance... 28 | terminating: |- 29 | Terminating the instance... 30 | waiting_for_operation: |- 31 | Waiting for GCP operation '%{name}' to finish... 32 | waiting_for_ready: |- 33 | Waiting for instance to become "ready"... 34 | waiting_for_comm: |- 35 | Waiting for Communicator to become available... 36 | warn_networks: |- 37 | Warning! The Google provider doesn't support any of the Vagrant 38 | high-level network configurations (`config.vm.network`). They 39 | will be silently ignored. 40 | warn_ssh_vagrant_user: |- 41 | Warning! SSH credentials are not set (`ssh.username`, `ssh.private_key`). 42 | Unless Vagrant's default insecure private key is added to GCE metadata, 43 | "vagrant up" will timeout while trying to estabilish an ssh connection 44 | to the instance. 45 | NOTE! Adding Vagrant private key to GCE metadata is heavily discouraged, 46 | since it potentially allows anyone to connect to your instances, 47 | including those not managed by Vagrant. 48 | will_not_destroy: |- 49 | The instance '%{name}' will not be destroyed, since the confirmation 50 | was declined. 51 | #------------------------------------------------------------------------------- 52 | # Translations for config validation errors 53 | #------------------------------------------------------------------------------- 54 | config: 55 | private_key_missing: |- 56 | Private key for Google could not be found in the specified location. 57 | zone_required: |- 58 | A zone must be specified via "zone" option. 59 | name_required: |- 60 | An instance name must be specified via "name" option. 61 | image_required: |- 62 | An image must be specified via "image" or "image_family" option. 63 | google_project_id_required: |- 64 | A Google Cloud Project ID is required via "google_project_id". 65 | auto_restart_invalid_on_preemptible: |- 66 | "auto_restart" option must be set to "false" for preemptible instances. 67 | on_host_maintenance_invalid_on_preemptible: |- 68 | "on_host_maintenance" option must be set to "TERMINATE" for preemptible 69 | instances. 70 | image_and_image_family_set: |- 71 | "image" must be unset when setting "image_family" 72 | service_accounts_deprecaated: |- 73 | "service_accounts is deprecated. Please use scopes instead" 74 | on_host_maintenance_invalid_with_accelerators: |- 75 | "on_host_maintenance" option must be set to "TERMINATE" for instances 76 | with accelerators 77 | #------------------------------------------------------------------------------- 78 | # Translations for exception classes 79 | #------------------------------------------------------------------------------- 80 | errors: 81 | fog_error: |- 82 | There was an error talking to Google. The error message is shown 83 | below: 84 | 85 | %{message} 86 | instance_ready_timeout: |- 87 | The instance never became "ready" in Google. The timeout currently 88 | set waiting for the instance to become ready is %{timeout} seconds. 89 | Please verify that the machine properly boots. If you need more time 90 | set the `instance_ready_timeout` configuration on the Google provider. 91 | rsync_error: |- 92 | There was an error when attemping to rsync a share folder. 93 | Please inspect the error message below for more info. 94 | 95 | Host path: %{hostpath} 96 | Guest path: %{guestpath} 97 | Error: %{stderr} 98 | external_ip_error: |- 99 | Specified external IP address is already in use. 100 | IP address requested: %{externalip} 101 | external_ip_does_not_exist_error: |- 102 | Specified external IP address is invalid or does not exist. 103 | IP address requested: %{externalip} 104 | disk_type_error: |- 105 | Specified disk type is not available in the region selected. 106 | Disk type requested: %{disktype} 107 | image_not_found: |- 108 | Specified image family or image not found: %{image} 109 | 110 | #------------------------------------------------------------------------------- 111 | # Translations for machine states 112 | #------------------------------------------------------------------------------- 113 | states: 114 | short_not_created: |- 115 | not created 116 | long_not_created: |- 117 | The Google instance is not created. Run `vagrant up` to create it. 118 | 119 | short_PROVISIONING: |- 120 | provsioning 121 | long_PROVISIONING: |- 122 | Resources are being reserved for this instance. This instance is 123 | not yet running. 124 | 125 | short_STAGING: |- 126 | staging 127 | long_STAGING: |- 128 | Resources have been acquired and the instance is being prepared for 129 | launch and should be in the RUNNING state soon. 130 | 131 | short_STOPPING: |- 132 | stopping 133 | long_STOPPING: |- 134 | The GCE instance is stopping. Wait until is completely stopped to 135 | run `vagrant up` and start it. 136 | 137 | short_RUNNING: |- 138 | running 139 | long_RUNNING: |- 140 | The Google instance is running. To stop this machine, you can run 141 | `vagrant halt`. To destroy the machine, you can run `vagrant destroy`. 142 | 143 | short_STOPPED: |- 144 | stopped 145 | long_STOPPED: |- 146 | The Google instance is not currently running, either due to a failure 147 | , or the instance is being shut down. This is a temporary status; 148 | the instance will move to either PROVISIONING or TERMINATED. 149 | 150 | short_TERMINATED: |- 151 | terminated 152 | long_TERMINATED: |- 153 | The Google instance was halted or failed for some reason. You can 154 | attempt to restart the instance by running `vagrant up`. 155 | -------------------------------------------------------------------------------- /tasks/acceptance.rake: -------------------------------------------------------------------------------- 1 | def colorize(text, color_code) 2 | puts "\033[#{color_code}m#{text}\033[0m" 3 | end 4 | 5 | { 6 | :red => 31, 7 | :green => 32, 8 | :yellow => 33 9 | }.each do |key, color_code| 10 | define_method key do |text| 11 | colorize(text, color_code) 12 | end 13 | end 14 | 15 | namespace :acceptance do 16 | 17 | desc "Run full acceptance suite" 18 | task :full => [:check_env, :run_full] 19 | 20 | desc "Run smoke tests" 21 | task :smoke => [:check_env, :run_smoke] 22 | 23 | desc "shows components that can be tested separately" 24 | task :components do 25 | exec("bundle exec vagrant-spec components") 26 | end 27 | 28 | desc "checks if environment variables are set" 29 | task :check_env do 30 | yellow "NOTE: For acceptance tests to be functional, correct ssh key needs to be added to GCE metadata." 31 | 32 | unless ENV["GOOGLE_JSON_KEY_LOCATION"] 33 | abort "Environment variables GOOGLE_JSON_KEY_LOCATION is not set. Aborting." 34 | end 35 | 36 | unless ENV["GOOGLE_PROJECT_ID"] 37 | abort "Environment variable GOOGLE_PROJECT_ID is not set. Aborting." 38 | end 39 | 40 | unless ENV["GOOGLE_SSH_USER"] 41 | yellow "WARNING: GOOGLE_SSH_USER variable is not set. Will try to start tests using insecure Vagrant private key." 42 | end 43 | 44 | if ENV["GOOGLE_SSH_KEY_LOCATION"] 45 | if File.read(ENV["GOOGLE_SSH_KEY_LOCATION"]).include?('ENCRYPTED') 46 | unless `ssh-add -L`.include?(ENV["GOOGLE_SSH_KEY_LOCATION"]) 47 | yellow "WARNING: It looks like ssh key is encrypted and ssh-agent doesn't contain any identities." 48 | puts "Trying to add identity, executing ssh-add..." 49 | system("ssh-add #{ENV["GOOGLE_SSH_KEY_LOCATION"]}") 50 | end 51 | end 52 | end 53 | 54 | # Set vagrant env to avoid "Encoded files can't be read" error. 55 | ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] = File.expand_path("../../", __FILE__) 56 | end 57 | 58 | task :run_full do 59 | components = %w( 60 | halt 61 | multi_instance 62 | preemptible 63 | reload 64 | scopes 65 | instance_groups 66 | image_family 67 | synced_folder/rsync 68 | provisioner/shell 69 | ).map{ |s| "provider/google/#{s}" } 70 | 71 | command = "vagrant-spec test --components=#{components.join(" ")}" 72 | puts command 73 | puts 74 | exec(command) 75 | end 76 | 77 | task :run_smoke do 78 | components = %w( 79 | provisioner/shell 80 | ).map{ |s| "provider/google/#{s}" } 81 | 82 | command = "vagrant-spec test --components=#{components.join(" ")}" 83 | puts command 84 | puts 85 | exec(command) 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /tasks/boxes.rake: -------------------------------------------------------------------------------- 1 | # Boxes maintenance tasks 2 | namespace :boxes do 3 | task :update => [:repack_main, :repack_testing] 4 | 5 | task :repack_main do 6 | boxes_dir = File.expand_path("../../example_boxes", __FILE__) 7 | Dir.chdir(boxes_dir) 8 | 9 | puts "Repacking main box" 10 | system('tar cvzf ../google.box -C ./gce ./metadata.json') 11 | end 12 | 13 | task :repack_testing do 14 | boxes_dir = File.expand_path("../../example_boxes", __FILE__) 15 | Dir.chdir(boxes_dir) 16 | 17 | puts "Repacking test box" 18 | system('tar cvzf ../google-test.box -C ./gce-test ./metadata.json ./Vagrantfile') 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /tasks/bundler.rake: -------------------------------------------------------------------------------- 1 | # This installs the tasks that help with gem creation and 2 | # publishing. 3 | Bundler::GemHelper.install_tasks 4 | -------------------------------------------------------------------------------- /tasks/changelog.rake: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | 3 | # Helper method to insert text after a line that matches the regex 4 | def insert_after_line(file, insert, regex = /^## Next/) 5 | tempfile = File.open("#{file}.tmp", "w") 6 | f = File.new(file) 7 | f.each do |line| 8 | tempfile << line 9 | next unless line =~ regex 10 | 11 | tempfile << "\n" 12 | tempfile << insert 13 | tempfile << "\n" 14 | end 15 | f.close 16 | tempfile.close 17 | 18 | FileUtils.mv("#{file}.tmp", file) 19 | end 20 | 21 | # Extracts all changes that have been made after the latest pushed tag 22 | def changes_since_last_tag 23 | `git --no-pager log $(git describe --tags --abbrev=0)..HEAD --grep="Merge" --pretty=format:"%t - %s%n%b%n"` 24 | end 25 | 26 | # Extracts all github users contributed since last tag 27 | def users_since_last_tag 28 | `git --no-pager log $(git describe --tags --abbrev=0)..HEAD --grep="Merge" --pretty=format:"%s" | cut -d' ' -f 6 | cut -d/ -f1 | sort | uniq` 29 | end 30 | 31 | namespace :changelog do 32 | task :generate do 33 | insert_after_line("CHANGELOG.md", changes_since_last_tag, /^## Next/) 34 | printf("Users contributed since last release:\n") 35 | contributors = users_since_last_tag.split("\n").map { |name| "@" + name } 36 | printf("Huge thanks to all our contributors 🎆\n") 37 | printf("Special thanks to: " + contributors.join(" ") + "\n") 38 | printf("\nI'll merge this and release the gem once all tests pass.\n") 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /tasks/lint.rake: -------------------------------------------------------------------------------- 1 | require 'rubocop/rake_task' 2 | 3 | RuboCop::RakeTask.new(:lint) 4 | -------------------------------------------------------------------------------- /tasks/test.rake: -------------------------------------------------------------------------------- 1 | require 'rake/testtask' 2 | require 'rspec/core/rake_task' 3 | 4 | namespace :test do 5 | RSpec::Core::RakeTask.new(:unit) do |t| 6 | t.pattern = "test/unit/**/*_test.rb" 7 | t.rspec_opts = "--color" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/acceptance/base.rb: -------------------------------------------------------------------------------- 1 | require "vagrant-spec/acceptance" 2 | require_relative "shared/context_google" 3 | -------------------------------------------------------------------------------- /test/acceptance/provider/halt_spec.rb: -------------------------------------------------------------------------------- 1 | # This tests that an instance can be halted correctly 2 | shared_examples 'provider/halt' do |provider, options| 3 | unless options[:box] 4 | raise ArgumentError, 5 | "box option must be specified for provider: #{provider}" 6 | end 7 | 8 | include_context 'acceptance' 9 | 10 | before do 11 | environment.skeleton('generic') 12 | assert_execute('vagrant', 'box', 'add', 'basic', options[:box]) 13 | assert_execute('vagrant', 'up', "--provider=#{provider}") 14 | end 15 | 16 | after do 17 | assert_execute('vagrant', 'destroy', '--force') 18 | end 19 | 20 | it 'should halt the machine and bring it back up' do 21 | status("Test: machine can be halted") 22 | halt_result = execute("vagrant", "halt") 23 | expect(halt_result).to exit_with(0) 24 | 25 | status("Test: machine can be brought up after halt") 26 | up_result = execute("vagrant", "up") 27 | expect(up_result).to exit_with(0) 28 | 29 | status("Test: machine is running after up") 30 | echo_result = execute("vagrant", "ssh", "-c", "echo foo") 31 | expect(echo_result).to exit_with(0) 32 | expect(echo_result.stdout).to match(/foo\n$/) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/acceptance/provider/image_family_spec.rb: -------------------------------------------------------------------------------- 1 | # This tests that an instance referenced by image family can be started properly 2 | shared_examples 'provider/image_family' do |provider, options| 3 | unless options[:box] 4 | raise ArgumentError, 5 | "box option must be specified for provider: #{provider}" 6 | end 7 | 8 | include_context 'acceptance' 9 | 10 | before do 11 | environment.skeleton('image_family') 12 | assert_execute('vagrant', 'box', 'add', 'basic', options[:box]) 13 | assert_execute('vagrant', 'up', "--provider=#{provider}") 14 | end 15 | 16 | after do 17 | assert_execute('vagrant', 'destroy', '--force') 18 | end 19 | 20 | it 'should bring up machine with image_family option' do 21 | status("Test: machine is running after up") 22 | result = execute("vagrant", "ssh", "-c", "echo foo") 23 | expect(result).to exit_with(0) 24 | expect(result.stdout).to match(/foo\n$/) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/acceptance/provider/instance_groups_spec.rb: -------------------------------------------------------------------------------- 1 | # This test check that the instance groups logic doesn't break the provisioning 2 | # process. 3 | # 4 | # TODO: Reach out using Fog credentials and verify group existence. 5 | # TODO: Clean up the created instance groups automatically 6 | shared_examples 'provider/instance_groups' do |provider, options| 7 | unless options[:box] 8 | raise ArgumentError, 9 | "box option must be specified for provider: #{provider}" 10 | end 11 | 12 | include_context 'acceptance' 13 | 14 | before do 15 | environment.skeleton('instance_groups') 16 | assert_execute('vagrant', 'box', 'add', 'basic', options[:box]) 17 | assert_execute('vagrant', 'up', "--provider=#{provider}") 18 | end 19 | 20 | after do 21 | assert_execute('vagrant', 'destroy', '--force') 22 | end 23 | 24 | it 'should bring up machine with an instance group' do 25 | status("Test: machine is running after up") 26 | result = execute("vagrant", "ssh", "-c", "echo foo") 27 | expect(result).to exit_with(0) 28 | expect(result.stdout).to match(/foo\n$/) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/acceptance/provider/multi_instance_spec.rb: -------------------------------------------------------------------------------- 1 | # This tests that multiple instances can be brought up correctly 2 | shared_examples 'provider/multi_instance' do |provider, options| 3 | unless options[:box] 4 | raise ArgumentError, 5 | "box option must be specified for provider: #{provider}" 6 | end 7 | 8 | include_context 'acceptance' 9 | 10 | before do 11 | environment.skeleton('multi_instance') 12 | assert_execute('vagrant', 'box', 'add', 'basic', options[:box]) 13 | assert_execute('vagrant', 'up', "--provider=#{provider}") 14 | end 15 | 16 | after do 17 | assert_execute('vagrant', 'destroy', '--force') 18 | end 19 | 20 | it 'should bring up 2 machines in different zones' do 21 | status("Test: both machines are running after up") 22 | status("Test: machine1 is running after up") 23 | result1 = execute("vagrant", "ssh", "z1a", "-c", "echo foo") 24 | expect(result1).to exit_with(0) 25 | expect(result1.stdout).to match(/foo\n$/) 26 | status("Test: machine2 is running after up") 27 | result1 = execute("vagrant", "ssh", "z1b", "-c", "echo foo") 28 | expect(result1).to exit_with(0) 29 | expect(result1.stdout).to match(/foo\n$/) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/acceptance/provider/preemptible_spec.rb: -------------------------------------------------------------------------------- 1 | # This tests that a preemptible instance can be brought up correctly 2 | shared_examples 'provider/preemptible' do |provider, options| 3 | unless options[:box] 4 | raise ArgumentError, 5 | "box option must be specified for provider: #{provider}" 6 | end 7 | 8 | include_context 'acceptance' 9 | 10 | before do 11 | environment.skeleton('preemptible') 12 | assert_execute('vagrant', 'box', 'add', 'basic', options[:box]) 13 | assert_execute('vagrant', 'up', "--provider=#{provider}") 14 | end 15 | 16 | after do 17 | assert_execute('vagrant', 'destroy', '--force') 18 | end 19 | 20 | it 'should bring up machine with preemptible option' do 21 | status("Test: machine is running after up") 22 | result = execute("vagrant", "ssh", "-c", "echo foo") 23 | expect(result).to exit_with(0) 24 | expect(result.stdout).to match(/foo\n$/) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/acceptance/provider/reload_spec.rb: -------------------------------------------------------------------------------- 1 | # This tests that an instance can be reloaded correctly 2 | shared_examples 'provider/reload' do |provider, options| 3 | unless options[:box] 4 | raise ArgumentError, 5 | "box option must be specified for provider: #{provider}" 6 | end 7 | 8 | include_context 'acceptance' 9 | 10 | before do 11 | environment.skeleton('generic') 12 | assert_execute('vagrant', 'box', 'add', 'basic', options[:box]) 13 | assert_execute('vagrant', 'up', "--provider=#{provider}") 14 | end 15 | 16 | after do 17 | assert_execute('vagrant', 'destroy', '--force') 18 | end 19 | 20 | it 'should reload the machine correctly' do 21 | status("Test: machine can be reloaded") 22 | reload_result = execute("vagrant", "reload") 23 | expect(reload_result).to exit_with(0) 24 | 25 | echo_result = execute("vagrant", "ssh", "-c", "echo foo") 26 | expect(echo_result).to exit_with(0) 27 | expect(echo_result.stdout).to match(/foo\n$/) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/acceptance/provider/scopes_spec.rb: -------------------------------------------------------------------------------- 1 | # This tests that account scopes can be configured correctly 2 | # and that gcloud GCE aliases can be used. 3 | # (see lib/fog/google/models/compute/server.rb in fog-google) 4 | shared_examples 'provider/scopes' do |provider, options| 5 | unless options[:box] 6 | raise ArgumentError, 7 | "box option must be specified for provider: #{provider}" 8 | end 9 | 10 | include_context 'acceptance' 11 | 12 | before do 13 | environment.skeleton('scopes') 14 | assert_execute('vagrant', 'box', 'add', 'basic', options[:box]) 15 | assert_execute('vagrant', 'up', "--provider=#{provider}") 16 | end 17 | 18 | after do 19 | assert_execute('vagrant', 'destroy', '--force') 20 | end 21 | 22 | it 'should bring up machine with scope definitions' do 23 | status("Test: machine is running after up") 24 | result = execute("vagrant", "ssh", "-c", "echo foo") 25 | expect(result).to exit_with(0) 26 | expect(result.stdout).to match(/foo\n$/) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/acceptance/shared/context_google.rb: -------------------------------------------------------------------------------- 1 | shared_context "provider-context/google" do 2 | 3 | end 4 | -------------------------------------------------------------------------------- /test/acceptance/skeletons/generic/Vagrantfile: -------------------------------------------------------------------------------- 1 | # This is a generic acceptance skeleton with no particular settings, set in 2 | # europe-west1-d zone 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.box = "basic" 6 | 7 | config.vm.provider :google do |google| 8 | 9 | google.zone = "australia-southeast1-b" 10 | 11 | google.zone_config "australia-southeast1-b" do |zone| 12 | zone.name = "vagrant-acceptance-generic-#{('a'..'z').to_a.sample(8).join}" 13 | zone.image_family = "debian-9" 14 | zone.disk_type = "pd-ssd" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/acceptance/skeletons/image_family/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "basic" 3 | 4 | config.vm.provider :google do |google| 5 | 6 | google.zone = "australia-southeast1-b" 7 | 8 | google.zone_config "australia-southeast1-b" do |zone| 9 | zone.name = "vagrant-acceptance-preemptible-#{('a'..'z').to_a.sample(8).join}" 10 | # Some images no longer fit into default 10GB disk size 11 | zone.disk_size = 30 12 | zone.disk_type = "pd-ssd" 13 | zone.image_family = "centos-7" 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/acceptance/skeletons/instance_groups/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "basic" 3 | 4 | config.vm.provider :google do |google| 5 | 6 | google.zone = "australia-southeast1-b" 7 | 8 | google.zone_config "australia-southeast1-b" do |zone| 9 | zone.name = "vagrant-acceptance-instance-groups-#{('a'..'z').to_a.sample(8).join}" 10 | zone.instance_group = "vagrant-acceptance-#{('a'..'z').to_a.sample(8).join}" 11 | zone.disk_type = "pd-ssd" 12 | zone.image_family = "debian-9" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/acceptance/skeletons/multi_instance/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "basic" 3 | 4 | config.vm.define :z1a do |z1a| 5 | z1a.vm.provider :google do |google| 6 | google.zone = "australia-southeast1-a" 7 | 8 | google.zone_config "australia-southeast1-a" do |z1a_zone| 9 | z1a_zone.name = "vagrant-acceptance-multi-z1a-#{('a'..'z').to_a.sample(8).join}" 10 | z1a_zone.machine_type = "n1-standard-1" 11 | z1a_zone.disk_type = "pd-ssd" 12 | z1a_zone.image_family = "debian-9" 13 | end 14 | end 15 | end 16 | 17 | config.vm.define :z1b do |z1b| 18 | z1b.vm.provider :google do |google| 19 | google.zone = "australia-southeast1-b" 20 | 21 | google.zone_config "australia-southeast1-b" do |z1b_zone| 22 | z1b_zone.name = "vagrant-testing-acceptance-multi-z1b-#{('a'..'z').to_a.sample(8).join}" 23 | z1b_zone.machine_type = "n1-standard-2" 24 | z1b_zone.disk_type = "pd-ssd" 25 | z1b_zone.image_family = "debian-9" 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/acceptance/skeletons/preemptible/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "basic" 3 | 4 | config.vm.provider :google do |google| 5 | 6 | google.zone = "australia-southeast1-b" 7 | 8 | google.zone_config "australia-southeast1-b" do |zone| 9 | zone.name = "vagrant-acceptance-preemptible-#{('a'..'z').to_a.sample(8).join}" 10 | zone.disk_type = "pd-ssd" 11 | zone.preemptible = true 12 | zone.auto_restart = false 13 | zone.on_host_maintenance = "TERMINATE" 14 | zone.image_family = "debian-9" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/acceptance/skeletons/scopes/Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "basic" 3 | 4 | config.vm.provider :google do |google| 5 | 6 | google.zone = "australia-southeast1-b" 7 | 8 | google.zone_config "australia-southeast1-b" do |zone| 9 | zone.name = "vagrant-acceptance-scopes-#{('a'..'z').to_a.sample(8).join}" 10 | zone.scopes = ['sql-admin', 11 | 'bigquery', 12 | 'https://www.googleapis.com/auth/compute'] 13 | zone.disk_type = "pd-ssd" 14 | zone.image_family = "debian-9" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/unit/base.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "rspec/its" 3 | 4 | # Set vagrant env to avoid "Encoded files can't be read" error. 5 | ENV["VAGRANT_INSTALLER_EMBEDDED_DIR"] = File.expand_path("../../../", __FILE__) 6 | 7 | # Require Vagrant itself so we can reference the proper 8 | # classes to test. 9 | require "vagrant" 10 | require "vagrant-google" 11 | 12 | # Add the test directory to the load path 13 | $LOAD_PATH.unshift File.expand_path("../../", __FILE__) 14 | 15 | # Do not buffer output 16 | $stdout.sync = true 17 | $stderr.sync = true 18 | 19 | # Configure RSpec 20 | RSpec.configure do |c| 21 | c.formatter = :progress 22 | end 23 | -------------------------------------------------------------------------------- /test/unit/common/config_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | require File.expand_path("../../base", __FILE__) 15 | 16 | require "vagrant-google/config" 17 | 18 | describe VagrantPlugins::Google::Config do 19 | let(:instance) { described_class.new } 20 | 21 | # Ensure tests are not affected by Google credential environment variables 22 | before :each do 23 | allow(ENV).to receive_messages(:[] => nil) 24 | end 25 | 26 | describe "defaults" do 27 | subject do 28 | instance.tap do |o| 29 | o.finalize! 30 | end 31 | end 32 | 33 | its("name") { should match "i-[0-9]{10}-[0-9a-f]{4}" } 34 | its("image") { should be_nil } 35 | its("image_family") { should be_nil } 36 | its("image_project_id") { should be_nil } 37 | its("instance_group") { should be_nil } 38 | its("zone") { should == "us-central1-f" } 39 | its("network") { should == "default" } 40 | its("machine_type") { should == "n1-standard-1" } 41 | its("disk_size") { should == 10 } 42 | its("disk_name") { should be_nil } 43 | its("disk_type") { should == "pd-standard" } 44 | its("instance_ready_timeout") { should == 20 } 45 | its("metadata") { should == {} } 46 | its("tags") { should == [] } 47 | its("labels") { should == {} } 48 | its("scopes") { should == nil } 49 | its("additional_disks") { should == [] } 50 | its("preemptible") { should be_falsey } 51 | its("auto_restart") { should } 52 | its("on_host_maintenance") { should == "MIGRATE" } 53 | its("accelerators") { should == [] } 54 | its("enable_secure_boot") { should be_falsey } 55 | its("enable_vtpm") { should be_falsey } 56 | its("enable_integrity_monitoring") { should be_falsey } 57 | end 58 | 59 | describe "overriding defaults" do 60 | # I typically don't meta-program in tests, but this is a very 61 | # simple boilerplate test, so I cut corners here. It just sets 62 | # each of these attributes to "foo" in isolation, and reads the value 63 | # and asserts the proper result comes back out. 64 | [ 65 | :name, 66 | :image, 67 | :image_family, 68 | :image_project_id, 69 | :zone, 70 | :instance_ready_timeout, 71 | :machine_type, 72 | :disk_size, 73 | :disk_name, 74 | :disk_type, 75 | :network, 76 | :network_project_id, 77 | :metadata, 78 | :labels, 79 | :can_ip_forward, 80 | :external_ip, 81 | :autodelete_disk, 82 | :enable_secure_boot, 83 | :enable_vtpm, 84 | :enable_integrity_monitoring, 85 | ].each do |attribute| 86 | 87 | it "should not default #{attribute} if overridden" do 88 | instance.send("#{attribute}=".to_sym, "foo") 89 | instance.finalize! 90 | expect(instance.send(attribute)).to eq "foo" 91 | end 92 | end 93 | 94 | it "should raise error when network is not default and zone is not overriden" do 95 | instance.network = "not-default" 96 | instance.finalize! 97 | errors = instance.validate("foo")["Google Provider"] 98 | expect(errors).to include(/zone_required/) 99 | end 100 | 101 | it "should raise error when preemptible and auto_restart is true" do 102 | instance.preemptible = true 103 | instance.auto_restart = true 104 | instance.finalize! 105 | errors = instance.validate("foo")["Google Provider"] 106 | expect(errors).to include(/auto_restart_invalid_on_preemptible/) 107 | end 108 | 109 | it "should raise error when preemptible and on_host_maintenance is not TERMINATE" do 110 | instance.preemptible = true 111 | instance.on_host_maintenance = "MIGRATE" 112 | instance.finalize! 113 | errors = instance.validate("foo")["Google Provider"] 114 | expect(errors).to include(/on_host_maintenance_invalid_on_preemptible/) 115 | end 116 | 117 | it "should raise error with accelerators and on_host_maintenance is not TERMINATE" do 118 | instance.accelerators = [{ :type => "nvidia-tesla-k80" }] 119 | instance.on_host_maintenance = "MIGRATE" 120 | instance.finalize! 121 | errors = instance.validate("foo")["Google Provider"] 122 | expect(errors).to include(/on_host_maintenance_invalid_with_accelerators/) 123 | end 124 | end 125 | 126 | describe "getting credentials from environment" do 127 | context "without Google credential environment variables" do 128 | subject do 129 | instance.tap do |o| 130 | o.finalize! 131 | end 132 | end 133 | 134 | its("google_json_key_location") { should be_nil } 135 | end 136 | 137 | context "with Google credential environment variables" do 138 | before :each do 139 | allow(ENV).to receive(:[]).with("GOOGLE_JSON_KEY_LOCATION").and_return("/path/to/json/key") 140 | end 141 | 142 | subject do 143 | instance.tap do |o| 144 | o.finalize! 145 | end 146 | end 147 | 148 | its("google_json_key_location") { should == "/path/to/json/key" } 149 | end 150 | end 151 | 152 | describe "zone config" do 153 | let(:config_image) { "foo" } 154 | let(:config_machine_type) { "foo" } 155 | let(:config_disk_size) { 99 } 156 | let(:config_disk_name) { "foo" } 157 | let(:config_disk_type) { "foo" } 158 | let(:config_name) { "foo" } 159 | let(:config_zone) { "foo" } 160 | let(:config_network) { "foo" } 161 | let(:can_ip_forward) { true } 162 | let(:external_ip) { "foo" } 163 | let(:accelerators) { [{ :type => "foo" }] } 164 | 165 | def set_test_values(instance) 166 | instance.name = config_name 167 | instance.network = config_network 168 | instance.image = config_image 169 | instance.machine_type = config_machine_type 170 | instance.disk_size = config_disk_size 171 | instance.disk_name = config_disk_name 172 | instance.disk_type = config_disk_type 173 | instance.zone = config_zone 174 | instance.can_ip_forward = can_ip_forward 175 | instance.external_ip = external_ip 176 | instance.accelerators = accelerators 177 | end 178 | 179 | it "should raise an exception if not finalized" do 180 | expect { instance.get_zone_config("us-central1-f") } 181 | .to raise_error(RuntimeError,/Configuration must be finalized/) 182 | end 183 | 184 | context "with no specific config set" do 185 | subject do 186 | # Set the values on the top-level object 187 | set_test_values(instance) 188 | 189 | # Finalize so we can get the zone config 190 | instance.finalize! 191 | 192 | # Get a lower level zone 193 | instance.get_zone_config("us-central1-f") 194 | end 195 | 196 | its("name") { should == config_name } 197 | its("image") { should == config_image } 198 | its("machine_type") { should == config_machine_type } 199 | its("disk_size") { should == config_disk_size } 200 | its("disk_name") { should == config_disk_name } 201 | its("disk_type") { should == config_disk_type } 202 | its("network") { should == config_network } 203 | its("zone") { should == config_zone } 204 | its("can_ip_forward") { should == can_ip_forward } 205 | its("external_ip") { should == external_ip } 206 | its("accelerators") { should == accelerators } 207 | end 208 | 209 | context "with a specific config set" do 210 | let(:zone_name) { "hashi-zone" } 211 | 212 | subject do 213 | # Set the values on a specific zone 214 | instance.zone_config zone_name do |config| 215 | set_test_values(config) 216 | end 217 | 218 | # Finalize so we can get the zone config 219 | instance.finalize! 220 | 221 | # Get the zone 222 | instance.get_zone_config(zone_name) 223 | end 224 | 225 | its("name") { should == config_name } 226 | its("image") { should == config_image } 227 | its("machine_type") { should == config_machine_type } 228 | its("disk_size") { should == config_disk_size } 229 | its("disk_name") { should == config_disk_name } 230 | its("disk_type") { should == config_disk_type } 231 | its("network") { should == config_network } 232 | its("zone") { should == zone_name } 233 | its("can_ip_forward") { should == can_ip_forward } 234 | its("external_ip") { should == external_ip } 235 | its("accelerators") { should == accelerators } 236 | end 237 | 238 | describe "inheritance of parent config" do 239 | let(:zone) { "hashi-zone" } 240 | 241 | subject do 242 | # Set the values on a specific zone 243 | instance.zone_config zone do |config| 244 | config.image = "child" 245 | end 246 | 247 | # Set some top-level values 248 | instance.image = "parent" 249 | 250 | # Finalize and get the zone 251 | instance.finalize! 252 | instance.get_zone_config(zone) 253 | end 254 | 255 | its("image") { should == "child" } 256 | end 257 | 258 | describe "shortcut configuration" do 259 | subject do 260 | # Use the shortcut configuration to set some values 261 | instance.zone_config "us-central1-f", :image => "child" 262 | instance.finalize! 263 | instance.get_zone_config("us-central1-f") 264 | end 265 | 266 | its("image") { should == "child" } 267 | end 268 | 269 | describe "merging" do 270 | let(:current) { described_class.new } 271 | let(:other) { described_class.new } 272 | 273 | subject { current.merge(other) } 274 | 275 | it "should merge the metadata" do 276 | current.metadata["one"] = "foo" 277 | other.metadata["two"] = "bar" 278 | 279 | expect(subject.metadata).to eq({ 280 | "one" => "foo", 281 | "two" => "bar" 282 | }) 283 | end 284 | 285 | it "should merge the metadata and overwrite older values" do 286 | current.metadata = { 287 | "one" => "foo", 288 | "name" => "current", 289 | } 290 | 291 | other.metadata = { 292 | "two" => "bar", 293 | "name" => "other", 294 | } 295 | 296 | expect(subject.metadata).to eq({ 297 | "one" => "foo", 298 | "two" => "bar", 299 | "name" => "other", 300 | }) 301 | end 302 | 303 | it "should merge the labels" do 304 | current.labels["one"] = "one" 305 | other.labels["two"] = "two" 306 | 307 | expect(subject.labels).to eq({ 308 | "one" => "one", 309 | "two" => "two" 310 | }) 311 | end 312 | 313 | it "should merge the labels and overwrite older values" do 314 | current.labels["one"] = "one" 315 | current.labels["name"] = "current" 316 | other.labels["two"] = "two" 317 | other.labels["name"] = "other" 318 | 319 | expect(subject.labels).to eq({ 320 | "one" => "one", 321 | "two" => "two", 322 | "name" => "other", 323 | }) 324 | end 325 | 326 | it "should merge the tags" do 327 | current.tags = ["foo", "bar"] 328 | other.tags = ["biz"] 329 | 330 | expect(subject.tags).to include("foo") 331 | expect(subject.tags).to include("bar") 332 | expect(subject.tags).to include("biz") 333 | end 334 | 335 | it "should merge the additional_disks" do 336 | current.additional_disks = [{:one => "one"}] 337 | other.additional_disks = [{:two => "two"}] 338 | 339 | expect(subject.additional_disks).to contain_exactly( 340 | {:one => "one"}, {:two => "two"} 341 | ) 342 | end 343 | end 344 | 345 | describe "zone_preemptible" do 346 | let(:zone) { "hashi-zone" } 347 | subject do 348 | instance.zone = zone 349 | instance.zone_config zone do |config| 350 | config.preemptible = true 351 | config.auto_restart = true 352 | config.on_host_maintenance = "MIGRATE" 353 | end 354 | 355 | instance.tap do |o| 356 | o.finalize! 357 | end 358 | end 359 | 360 | before :each do 361 | # Stub out required env to make sure we produce only errors we're looking for. 362 | allow(ENV).to receive(:[]).with("GOOGLE_PROJECT_ID").and_return("my-awesome-project") 363 | allow(ENV).to receive(:[]).with("GOOGLE_JSON_KEY_LOCATION").and_return("/path/to/json/key") 364 | allow(ENV).to receive(:[]).with("GOOGLE_SSH_KEY_LOCATION").and_return("/path/to/ssh/key") 365 | allow(File).to receive(:exist?).with("/path/to/json/key").and_return(true) 366 | end 367 | 368 | it "should fail auto_restart validation" do 369 | instance.finalize! 370 | errors = subject.validate("foo")["Google Provider"] 371 | expect(errors).to include(/auto_restart_invalid_on_preemptible/) 372 | end 373 | 374 | it "should fail on_host_maintenance validation" do 375 | instance.finalize! 376 | errors = subject.validate("foo")["Google Provider"] 377 | expect(errors).to include(/on_host_maintenance_invalid_on_preemptible/) 378 | end 379 | end 380 | end 381 | end 382 | -------------------------------------------------------------------------------- /vagrant-google.gemspec: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Google Inc. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) 16 | require "vagrant-google/version" 17 | 18 | Gem::Specification.new do |s| 19 | s.name = "vagrant-google" 20 | s.version = VagrantPlugins::Google::VERSION 21 | s.platform = Gem::Platform::RUBY 22 | s.authors = ["Eric Johnson", "Artem Yakimenko"] 23 | s.email = "vagrant-google@google.com" 24 | s.homepage = "http://www.vagrantup.com" 25 | s.summary = "Vagrant provider plugin for Google Compute Engine." 26 | s.description = "Enables Vagrant to manage Google Compute Engine instances." 27 | 28 | s.required_rubygems_version = ">= 1.3.6" 29 | s.rubyforge_project = "vagrant-google" 30 | 31 | s.add_runtime_dependency "fog-google", "~> 1.17.0" 32 | 33 | # This is a restriction to avoid errors on `failure_message_for_should` 34 | # TODO: revise after vagrant_spec goes past >0.0.1 (at master@e623a56) 35 | s.add_development_dependency "rspec-legacy_formatters" 36 | 37 | s.add_development_dependency "rake", ">= 13.0.1" 38 | s.add_development_dependency "rspec", ">= 3.10", "< 4.0" 39 | s.add_development_dependency "rspec-its", "~> 1.2" 40 | s.add_development_dependency "rubocop", "~> 0.83" 41 | s.add_development_dependency "rubocop-performance", "~> 1.5.2" 42 | s.add_development_dependency "highline" 43 | s.add_development_dependency "pry" 44 | s.add_development_dependency "pry-byebug" 45 | 46 | # The following block of code determines the files that should be included 47 | # in the gem. It does this by reading all the files in the directory where 48 | # this gemspec is, and parsing out the ignored files from the gitignore. 49 | # Note that the entire gitignore(5) syntax is not supported, specifically 50 | # the "!" syntax, but it should mostly work correctly. 51 | root_path = File.dirname(__FILE__) 52 | all_files = Dir.chdir(root_path) { Dir.glob("**/{*,.*}") } 53 | all_files.reject! { |file| [".", ".."].include?(File.basename(file)) } 54 | gitignore_path = File.join(root_path, ".gitignore") 55 | gitignore = File.readlines(gitignore_path) 56 | gitignore.map! { |line| line.chomp.strip } 57 | gitignore.reject! { |line| line.empty? || line =~ /^(#|!)/ } 58 | 59 | unignored_files = all_files.reject do |file| 60 | # Ignore any directories, the gemspec only cares about files 61 | next true if File.directory?(file) 62 | # Ignore any paths that match anything in the gitignore. We do 63 | # two tests here: 64 | # 65 | # - First, test to see if the entire path matches the gitignore. 66 | # - Second, match if the basename does, this makes it so that things 67 | # like '.DS_Store' will match sub-directories too (same behavior 68 | # as git). 69 | # 70 | gitignore.any? do |ignore| 71 | File.fnmatch(ignore, file, File::FNM_PATHNAME) || 72 | File.fnmatch(ignore, File.basename(file), File::FNM_PATHNAME) 73 | end 74 | end 75 | 76 | s.files = unignored_files 77 | s.executables = unignored_files.map { |f| f[/^bin\/(.*)/, 1] }.compact 78 | s.require_path = 'lib' 79 | end 80 | -------------------------------------------------------------------------------- /vagrant-spec.config.rb: -------------------------------------------------------------------------------- 1 | require_relative "test/acceptance/base" 2 | 3 | Vagrant::Spec::Acceptance.configure do |c| 4 | c.component_paths << File.expand_path("../test/acceptance", __FILE__) 5 | c.skeleton_paths << File.expand_path("../test/acceptance/skeletons", __FILE__) 6 | c.assert_retries = 1 7 | c.provider "google", 8 | box: File.expand_path("../google-test.box", __FILE__), 9 | contexts: ["provider-context/google"] 10 | end 11 | -------------------------------------------------------------------------------- /vagrantfile_examples/Vagrantfile.multiple_machines: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | # Copyright 2013 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Usage: 18 | # 1) Launch both instances and then destroy both 19 | # $ vagrant up --provider=google 20 | # $ vagrant destroy 21 | # 2) Launch one instance 'z1c' and then destory the same 22 | # $ vagrant up z1c --provider=google 23 | # $ vagrant destroy z1c 24 | 25 | # Customize these global variables 26 | $GOOGLE_PROJECT_ID = "YOUR_GOOGLE_CLOUD_PROJECT_ID" 27 | $GOOGLE_JSON_KEY_LOCATION = "/path/to/your/private-key.json" 28 | $LOCAL_USER = "mitchellh" 29 | $LOCAL_SSH_KEY = "~/.ssh/id_rsa" 30 | 31 | # Example Debian provision script 32 | $PROVISION_DEBIAN = <