diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..f1030ad6fc82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +*.swp +coverage/* + +# Keep project coverage statistics under source control +!coverage/.last_run.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000000..4c795e61bc72 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,54 @@ +[submodule "build/puppet/dns"] + path = build/puppet/dns + url = https://github.com/GoogleCloudPlatform/puppet-google-dns +[submodule "build/puppet/auth"] + path = build/puppet/auth + url = https://github.com/GoogleCloudPlatform/puppet-google-auth +[submodule "build/puppet/compute"] + path = build/puppet/compute + url = https://github.com/GoogleCloudPlatform/puppet-google-compute +[submodule "build/chef/dns"] + path = build/chef/dns + url = https://github.com/GoogleCloudPlatform/chef-google-dns +[submodule "build/chef/compute"] + path = build/chef/compute + url = https://github.com/GoogleCloudPlatform/chef-google-compute +[submodule "build/puppet/storage"] + path = build/puppet/storage + url = https://github.com/GoogleCloudPlatform/puppet-google-storage +[submodule "build/puppet/sql"] + path = build/puppet/sql + url = https://github.com/GoogleCloudPlatform/puppet-google-sql +[submodule "build/puppet/container"] + path = build/puppet/container + url = https://github.com/GoogleCloudPlatform/puppet-google-container +[submodule "build/puppet/_bundle"] + path = build/puppet/_bundle + url = https://github.com/GoogleCloudPlatform/puppet-google +[submodule "build/chef/auth"] + path = build/chef/auth + url = https://github.com/GoogleCloudPlatform/chef-google-auth +[submodule "build/chef/storage"] + path = build/chef/storage + url = https://github.com/GoogleCloudPlatform/chef-google-storage +[submodule "build/chef/container"] + path = build/chef/container + url = https://github.com/GoogleCloudPlatform/chef-google-container +[submodule "build/chef/sql"] + path = build/chef/sql + url = https://github.com/GoogleCloudPlatform/chef-google-sql +[submodule "build/puppet/logging"] + path = build/puppet/logging + url = https://github.com/GoogleCloudPlatform/puppet-google-logging +[submodule "build/chef/_bundle"] + path = build/chef/_bundle + url = https://github.com/GoogleCloudPlatform/chef-google +[submodule "build/puppet/pubsub"] + path = build/puppet/pubsub + url = https://github.com/GoogleCloudPlatform/puppet-google-pubsub +[submodule "build/puppet/spanner"] + path = build/puppet/spanner + url = https://github.com/GoogleCloudPlatform/puppet-google-spanner +[submodule "build/ansible"] + path = build/ansible + url = https://github.com/ansible/ansible.git diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000000..6509254b7db5 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,28 @@ +AllCops: + Exclude: + - 'build/**/*' + # We do not validate the templates as they will have code that will only be + # properly formatted when compiled for the target module. These files should + # be checked in their final generated form. + - 'products/**/files/*' + - 'templates/**/*' + +Metrics/AbcSize: + Max: 20 + +# TODO(nelsonjr): Refactor this class and remove Rubocop exemptions below +Metrics/ClassLength: + Exclude: + - 'provider/core.rb' + - 'provider/puppet.rb' + - 'provider/legacy_test_data_formatter.rb' + # TODO(alexstephen): Remove this when generate_object is removed + - 'provider/chef.rb' + - 'tests/end2end/tester_base.rb' + +Metrics/MethodLength: + Max: 15 + +Security/Eval: + Exclude: + - 'provider/legacy_test_data_generator.rb' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000000..448f78ab4519 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# How to become a contributor and submit your own code + +## Contributor License Agreements + +We'd love to accept your sample apps and patches! Before we can take them, we +have to jump a couple of legal hurdles. + +Please fill out either the individual or corporate Contributor License Agreement +(CLA). + + * If you are an individual writing original source code and you're sure you + own the intellectual property, then you'll need to sign an [individual CLA] + (http://code.google.com/legal/individual-cla-v1.0.html). + * If you work for a company that wants to allow you to contribute your work, + then you'll need to sign a [corporate CLA] + (http://code.google.com/legal/corporate-cla-v1.0.html). + +Follow either of the two links above to access the appropriate CLA and +instructions for how to sign and return it. Once we receive it, we'll be able to +accept your pull requests. + +## Contributing A Patch + +1. Submit an issue describing your proposed change to the repo in question. +1. The repo owner will respond to your issue promptly. +1. If your proposed change is accepted, and you haven't already done so, sign a + Contributor License Agreement (see details above). +1. Fork the desired repo, develop and test your code changes. +1. Ensure that your code is clear and comprehensible. +1. Ensure that your code has an appropriate set of unit tests which all pass. +1. Submit a pull request. diff --git a/DEVELOPER.md b/DEVELOPER.md new file mode 100644 index 000000000000..d73bb2ca41ac --- /dev/null +++ b/DEVELOPER.md @@ -0,0 +1,197 @@ +# Developer Guide + +All the information required for Magic Modules (MM) to compile a specific +product is usually contained within its folder under the [`products`](products/) +folder. For example all the sources for the Google Compute Engine are inside the +[`products/compute`](products/compute) folder. + +When compiling a product you specify the path to the products folder with the +`-p` parameter. + + +## Anatomy of a Product + +A product definition for a specific provider contains a few basic components: + + 1. Product definition: `api.yaml` + 2. Provider dependent product definitions: `.yaml` + 3. Examples + 4. Tests + +### Product definition: api.yaml + +The `api.yaml` contains all the object definitions for the GCP product. It also +contains the relationships between objects. + +It also includes other product specific information, such as product version and +API endpoint. + +#### Resource + +A resource is an object defined and supported by the product. For example a +virtual machine, a disk, a network, a container, a container cluster are all +resources from MM's point of view. + +A resource defines some basic properties about the resource such as name, +endpoint name, as well as its properties and parameters. + + - !ruby/object:Api::Resource + name: 'Address' + kind: 'compute#address' + base_url: projects/{{project}}/regions/{{region}}/addresses + exports: + ... + description: | + ... + parameters: + ... + properties: + ... + +##### Resource / [ parameters | properties ] + +Specifies fields that the user can specify to define the object and its desired +state. + +> The main difference between a parameter and a property is that a parameter is +> not part of the object, and will be used by the user to convey data to the +> generated code, usually to help locate the object. Every property will be +> eventually persisted as fields of the remote object in GCP. + +Required fields: + +- `type`: Specifies the type of the parameter / property +- `name`: The user facing name of the parameter / property +- `description`: The description for the parameter / property + +Optional fields: + +- `required`: true|false indicating if the field if required to be specified by + the user +- `input`: true|false indicating if the field will be used as "input", which + means that the field will be used only during the _creation_ of the object +- `output`: true|false indicating that the field is produced and controlled by + the server. The user can use an output field to ensure the value matches + what's expected. +- `field`: In case we want the user facing name to be different from the + corresponding API property we can use `field` to map the user facing name + (specified by the `name` parameter) to the backend API (specified by the + `field` parameter) + +Example: + + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'name' + description: | + URL of the region where the regional address resides. + This field is not applicable to global addresses. + required: true + +> Please describe these fields from the user's perspective and not from the API +> perspective. That will allow the generated documentation be useful for the +> provider user that should not care about how the internals work. This is a +> core strength of the MM paradigm: the user should neither need to know nor +> care how the backend works. All he cares is how to describe in a high-level, +> uniform and elegant way his dependencies. +> +> For example a in a virtual machine disk, do not say "disk: array or full URI +> (self link) to the disks". Instead you should say "disks: reference to a list +> disks to attach to the machine". +> +> Also avoid language or provider specific lingo in the product definitions. +> +> For example instead of saying "a hash array of name to datetime" say "a map +> from name to timestamps". While the former may be correct for a provider in +> Java it will not be in a Ruby or Python based output. + + +### Provider dependent product definitions: .yaml + +Each provider has their own product specific definitions that are relevant and +necessary to properly build the product for such provider. + +For example a Puppet provider requires to specify the Puppet Forge URL for the +module being created. That goes into the product `puppet.yaml`. Similarly a Chef +provider requires metadata about dependencies of other cookbooks. That goes into +the product `chef.yaml`. + +Detailed provider documentation: + +- [`puppet.yaml`][puppet-yaml] on how to create Puppet product definitions +- [`chef.yaml`][chef-yaml] on how to create Chef product definitions + +### Examples + +It is strongly encouraged to provide live, working examples to end users. It is +also good practice to test the examples to make sure they do not become stale. + +To test examples you can use our `[end2end](tests/end2end)` mini framework. + + +## Types + +When defining a property you have to specify its type. Although some products +may not care about types and may convert from/to strings, it is important to +know the type so the compiler can do the best job possible to validate the +input, ensure consistency, etc. + +Currently MM supports the following types: + +- `Api::Type::Array`: Represents an array of values. The type of the values is + identified by the `item\_type`property. +- `Api::Type::Boolean`: A boolean (true or false) value. +- `Api::Type::Constant`: A constant that will be passed to the API. +- `Api::Type::Double`: A double number. +- `Api::Type::Enum`: Input is allowed only from a fixed list of values, + specified by the `values` property. +- `Api::Type::Integer`: An integer number. +- `Api::Type::Long`: A long number +- Api::Type::NameValues +- `Api::Type::NestedObject`: A composite field, composed of inner fields. This + is used for structures that are nested. +- `Api::Type::ResourceRef`: A reference to another object described in the + product. This is used to create strong relationship binding between the + objects, where the generated code will make sure the object depended upon + exists. A `ResourceRef` also specifies which property from the dependent + object we are interested to fetch, by specifying the `resource` and `imports` + fields. +- `Api::Type::String`: A string field. +- `Api::Type::Time`: An RFC 3339 representation of a time stamp. + + +## Exports + +When data needs to be read from another object that we depend on we use exports. + +For example a virtual machine has many disks. When specifying the disks we do +not want the user to worry about which representation Google developers chose +for that object (is it the self\_link? is it just the disk name? is it the +zone+name?). + +Instead we describe the relationship in `api.yaml` and let the generated code +deal with that. The user will only say "vm X uses disks [A, B, C]". As GCP VM +requires the disk self\_link the generated code will compute their values and +pass along. + +All fields need to be explicitly exported before they can be imported and +dependent upon by other objects. This allows us to track and verify the +dependencies more reliably. + +The following exports are allowed: + +- ``: By specifying the `name` of an existing property of the + object, we're allowing other objects to import it. +- `Api::Type::FetchedExternal`: Exports the version of the data that was + returned from the API. That's useful when the user specification of the + property is different from the representation coming from the API +- `Api::Type::SelfLink`: URI that represents the object + +> To avoid overexposure and inefficient code, only export the fields you +> actively require to access from dependent objects, and practice housecleaning +> by removing exports if you change or remove an object that depends on it. + + +[puppet-yaml]: docs/puppet.yaml.md +[chef-yaml]: docs/chef.yaml.md diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000000..161a52cdbd78 --- /dev/null +++ b/Gemfile @@ -0,0 +1,16 @@ +source 'https://rubygems.org' + +gem 'binding_of_caller' + +group :test do + gem 'mocha' + gem 'rspec' + # TODO(alexstephen): Monitor rubocop upsteam changes + # https://github.com/bbatsov/rubocop/pull/4329 + # Change will allow rubocop to use --ignore-parent-exclusion flag + # Current rubocop upstream will not check Chef files because of + # AllCops/Exclude + gem 'rubocop', git: 'https://github.com/nelsonjr/rubocop.git', + branch: 'feature/ignore-parent-exclude' + gem 'simplecov' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000000..d43ac2cb6296 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,66 @@ +GIT + remote: https://github.com/nelsonjr/rubocop.git + revision: 178b3150b7610195c327783575177520316e1985 + branch: feature/ignore-parent-exclude + specs: + rubocop (0.50.0) + parallel (~> 1.10) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 2.2.2, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.3.0) + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) + debug_inspector (0.0.2) + diff-lcs (1.2.5) + docile (1.1.5) + json (2.0.2) + metaclass (0.0.4) + mocha (1.2.1) + metaclass (~> 0.0.1) + parallel (1.12.0) + parser (2.4.0.0) + ast (~> 2.2) + powerpack (0.1.1) + rainbow (2.2.2) + rake + rake (12.1.0) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + ruby-progressbar (1.9.0) + simplecov (0.12.0) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + unicode-display_width (1.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + binding_of_caller + mocha + rspec + rubocop! + simplecov + +BUNDLED WITH + 1.15.3 diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000000..ef51da2b0e8d --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 000000000000..be4dea6dacb7 --- /dev/null +++ b/README.md @@ -0,0 +1,164 @@ + + +# Magic Modules + + +## Overview + +Magic Modules (MM) is a project to auto-generate management libraries (e.g. +plugins) for third party tools to operate Google Cloud Platform (GCP) products +natively. + + +## Motivation + +Customers -- large and small -- use many different deployment tools to provision +and manage their operating environments. Companies like Puppet, Chef, Ansible, +Hashicorp produce open source tools to meet these needs and typically provide +enterprise versions and support. + +Once customers invest in these tools they prefer to use a single management +system (and style). For this reason Google wants to provide integration for +these tools, so that our customers have an easier path to GCP versus abandoning +a tool they're already comfortable with. For example Puppet can provision and +manage VMs in Compute Engine, databases in Cloud SQL, Google Kubernetes Engine +clusters, and so on. + +But given the breadth of products available in Google Cloud Platform, creating +modules by hand is quickly becomes inefficient and costly. It would involve too +much redundant engineering work and create too much code for a team to maintain. +There is also the issue of explosion of modules, if you consider for +comprehensive coverage: +total\_libraries = total\_products \* total\_providers +. +The more products we add to the portfolio, or increase the support to other +tools, doing this work by hand will not scale. + +Enter Magic Modules. + + +## Philosophy + +The philosophy behind Magic Modules is a strongly defined object and object +relationship schema. This schema can in turn be used to map the necessary API +calls to achieve the object state. + +> Magic Modules heavily relies on *object convergence* as its target +> destination. That means that the user will specify the final state they would +> like their objects (and/or object relations to be) and the code generated by +> Magic Modules will do "whatever it takes" to make it so. + +Note that there is not a required 1:1 mapping between objects and API calls to +GCP. This 1:N mapping is key to the simplicity of the interface exposed to +users. There are various wrappers out there that can envelope a Google API into +various languages, e.g. Ruby or Java, but the user needs to know all the +intricacies of the underlying GCP product API. + +[Read more about philosophy][philosophy] + +## Current Coverage + +### Supported Providers + +- Puppet +- Chef + +### Supported Products + +All the necessary data for a product to compile is under the `products/` folder. +For simplicity (yet not a requirement) the folder name is the actual product +name you will use in the `-p` parameter later on. + +To see the full product list visit the [`products`](products/) folder. + + +## (One Time) Setup + +Magic Modules requires: + +- git (so you can checkout the code) +- Ruby 2.0 or higher +- Bundler gem + +### Downloading code + +Depending on the product and provider combination the generated code is stored +on another Git repository. To make it easier to track them all these foreign Git +repositories are tracked as submodules of Magic Modules main repository. + +To fetch all products last known good build, run: + + git submodule update --init + +### Installing bundler + +As you have Ruby installed all you need is Bundler. To install it, run: + + gem install bundler + +### Dependencies install + +Now that we have Bundler installed, go to the root folder where you checked out +the Magic Modules code and run: + + bundle install + +We are now ready to compile a module! + + +## Compiling a module + +Compiling a module is as easy as: + + bundle exec compiler -p -e -o + +For example, to compile Google Compute Engine for Puppet, you invoke: + + bundle exec compiler -p products/compute -e puppet -o build/puppet/compute + +And the generated code should be written to `build/puppet/compute` + + +## Expected output + +You point Magic Modules to an _empty folder_ and it will output a fully +operational module for the selected provider. + +Magic modules will compile the schema and generate the following output: + +1. The production-grade, user facing code in the target tool language and style +2. Documentation for all objects and interactions between objects +3. All tests that certifies the user facing code +4. All API layer data used during test runs +5. All required libraries for the operation + + +## Testing changes to Magic Module + +Use `rspec` to test Magic Module changes: + + bundle exec rspec + + +## Testing the generated code + +Each provider has their own testing mechanism. Run the command in the product's +output folder `build//`, e.g. `build/puppet/compute`. + +Platform | Tool | Test command +---------|-------|-------------- +Puppet | rspec | bundle exec rspec +Chef | rspec | bundle exec rspec + +And refer to the module documentation on how to execute and debug the code or +tests further. + + +## Creating or updating a module + +Please refer to [Developer Guide][developer] for details on how to create a new +product or update an existing one. + + +[developer]: DEVELOPER.md +[philosophy]: docs/philosophy.md diff --git a/api/async.rb b/api/async.rb new file mode 100644 index 000000000000..f93b65609555 --- /dev/null +++ b/api/async.rb @@ -0,0 +1,77 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' + +module Api + # Repesents an asynchronous operation definition + class Async < Api::Object + attr_reader :operation + attr_reader :result + attr_reader :status + attr_reader :error + + # Represents the operations (requests) issues to watch for completion + class Operation < Api::Object + attr_reader :kind + attr_reader :path + attr_reader :base_url + attr_reader :wait_ms + + def validate + super + check_property :kind, String + check_property :path, String + check_property :base_url, String + check_property :wait_ms, Integer + end + end + + # Represents the results of an Operation request + class Result < Api::Object + attr_reader :path + + def validate + super + check_property :path, String + end + end + + # Provides information to parse the result response to check operation + # status + class Status < Api::Object + attr_reader :path + attr_reader :complete + attr_reader :allowed + + def validate + super + check_property :path, String + check_property :complete, String + check_property :allowed, Array + end + end + + # Provides information on how to retrieve errors of the executed operations + class Error < Api::Object + attr_reader :path + attr_reader :message + + def validate + super + check_property :path, String + check_property :message, String + end + end + end +end diff --git a/api/bundle.rb b/api/bundle.rb new file mode 100644 index 000000000000..0077f8007551 --- /dev/null +++ b/api/bundle.rb @@ -0,0 +1,27 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/product' + +module Api + # A bundle for modules of the same provider. A "bundle" is both a summary + # documentation of all the products supported, as well as the means to + # download them all at once. + class Bundle < Product + attr_reader :products # loaded by bundler with products found + + def validate + # Not not perform basic Api::Product validations + end + end +end diff --git a/api/compiler.rb b/api/compiler.rb new file mode 100644 index 000000000000..4b1c5c33b0dc --- /dev/null +++ b/api/compiler.rb @@ -0,0 +1,48 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/async' +require 'api/bundle' +require 'api/product' +require 'api/resource' +require 'api/type' +require 'compile/core' +require 'google/yaml_validator' + +module Api + # Process .yaml and produces output module + class Compiler + include Compile::Core + + attr_reader :product + + def initialize(catalog) + @catalog = catalog + end + + def run + # Compile step #1: compile with generic class to instantiate target class + source = compile(@catalog) + config = Google::YamlValidator.parse(source) + unless config.class <= Api::Product + raise StandardError, "#{@catalog} is #{config.class}"\ + ' instead of Api::Product' \ + end + # Compile step #2: Now that we have the target class, compile with that + # class features + source = config.compile(@catalog, 0) + config = Google::YamlValidator.parse(source) + config + end + end +end diff --git a/api/object.rb b/api/object.rb new file mode 100644 index 000000000000..96e1a043ff73 --- /dev/null +++ b/api/object.rb @@ -0,0 +1,51 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/logger' +require 'google/yaml_validator' + +module Api + # Repesents a base object + class Object < Google::YamlValidator + # Basic functions for defining classes that explicitly marks a property as + # not in use, such as Provider::Config::TestData::NONE + module MissingObject + attr_reader :reason + + def validate + return if @validated + check_property :reason, String + # Now that we verified a reason was provided, delete it so it does not + # end in the object mapping (and eventually fail as the type "reason" + # will likely never exist) + remove_instance_variable('@reason') + super + @validated = true + end + end + + # Represents an object that has a (mandatory) name + class Named < Api::Object + attr_reader :name + + def validate + super + check_property :name, String + end + end + + def out_name + Google::StringUtils.underscore(@name) + end + end +end diff --git a/api/product.rb b/api/product.rb new file mode 100644 index 000000000000..56d8823e7ff6 --- /dev/null +++ b/api/product.rb @@ -0,0 +1,39 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' +require 'google/logger' +require 'compile/core' + +module Api + # Repesents a product to be managed + class Product < Api::Object::Named + attr_reader :base_url + attr_reader :objects + attr_reader :prefix + attr_reader :scopes + + include Compile::Core + + def validate + super + set_variables @objects, :__product + check_property :base_url, String + check_property :objects, Array + check_property_list :objects, @objects, Api::Resource + check_property :prefix, String + check_property :scopes, ::Array + check_property_list :scopes, @scopes, String + end + end +end diff --git a/api/resource.rb b/api/resource.rb new file mode 100644 index 000000000000..c8f3e3351938 --- /dev/null +++ b/api/resource.rb @@ -0,0 +1,328 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' +require 'google/string_utils' + +module Api + # An object available in the product + # rubocop:disable Metrics/ClassLength + class Resource < Api::Object::Named + attr_reader :description + attr_reader :kind + attr_reader :base_url + attr_reader :self_link + attr_reader :self_link_query + # identity: an array with items that uniquely identify the resource. + # default=name + attr_reader :identity + attr_reader :parameters + attr_reader :properties + attr_reader :exclude + attr_reader :virtual + attr_reader :async + attr_reader :readonly + attr_reader :exports + attr_reader :label_override + attr_reader :transport + attr_reader :create_verb + + attr_reader :__product + + # Allows overriding snowflake transport requests + class Transport < Api::Object + attr_reader :encoder + attr_reader :decoder + + def validate + super + check_property :encoder, ::String unless @encoder.nil? + check_property :decoder, ::String unless @decoder.nil? + end + end + + # Allows mapping of requests to specific API layout quirks. + class Wrappers < Api::Object + attr_reader :create + + def validate + super + check_property :create, ::String + end + end + + # Represents a response from the API that returns a list of objects. + class ResponseList < Api::Object + attr_reader :kind + attr_reader :items + + def validate + super + check_property :kind, String + check_property :items, String + end + + def kind? + !@kind.nil? + end + end + + # Represents a hierarchy that has an object as its key. For example, when + # creating test data, we'll do it per type, so it would look like this in + # the provider.yaml file: + # + # test_data: !ruby/object:Api::Resource::HashArray + # Object1: + # - data1 + # - data2 + # Object2: + # - data3 + # - data4 + class HashArray < Api::Object + # A dummy class that identifies the property as deliberately unused. + class NONE < HashArray + include Api::Object::MissingObject + end + + def consume_api(api) + @__api = api + end + + def validate + return unless @__objects.nil? # allows idempotency of calling validate + convert_findings_to_hash + ensure_keys_are_objects unless @__api.nil? + super + end + + def [](index) + @__objects[index] + end + + def each + return enum_for(:each) unless block_given? + @__objects.each { |o| yield o } + self + end + + def select + return enum_for(:select) unless block_given? + @__objects.select { |o| yield o } + self + end + + def fetch(key, *args) + # *args only holds default value. Needs to mimic ::Hash + if args.count > 0 + # args[0] will be returned if key not found + @__objects.fetch(key, args[0]) unless @__objects.nil? + else + # KeyErorr will be thrown if key not found + @__objects.fetch(key) unless @__objects.nil? + end + end + + def key?(key) + @__objects.key?(key) unless @__objects.nil? + end + + private + + # Converts every variable into @__objects + def convert_findings_to_hash + @__objects = {} + instance_variables.each do |var| + next if var.id2name.start_with?('@__') + @__objects[var.id2name[1..-1]] = instance_variable_get(var) + remove_instance_variable(var) + end + end + + def ensure_keys_are_objects + @__objects.each_key do |type| + next unless @__api.objects.select { |o| o.name == type }.empty? + raise [ + "Object #{type} is not a valid type.", + "Allowed types are: #{@__api.objects.map(&:name)}" + ].join(' ') + end + end + end + + def out_name + [@__product.prefix, Google::StringUtils.underscore(@name)].join('_') + end + + # rubocop:disable Lint/DuplicateMethods + def identity + props = all_user_properties + if @identity.nil? + props.select { |p| p.name == Api::Type::String::NAME.name } + else + props.select { |p| @identity.include?(p.name) } + end + end + # rubocop:enable Lint/DuplicateMethods + + # 'identity' is already taken by Ruby. + def __identity + @identity + end + + # Main data validation. As the validation code is simple, but long due to + # the number of properties, it is okay to ignore Rubocop warnings about + # method size and complexity. + # + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/PerceivedComplexity + def validate + super + check_property :async, Api::Async unless @async.nil? + check_property :base_url, String unless @exclude + check_property :description, String + check_property :exclude, :boolean unless @exclude.nil? + check_property :kind, String unless @kind.nil? + check_property :parameters, Array unless @parameters.nil? + check_property :properties, Array unless @exclude + check_property :exports, Array unless @exports.nil? + check_property :self_link, String unless @self_link.nil? + check_property :self_link_query, Api::Resource::ResponseList \ + unless @self_link_query.nil? + check_property :virtual, :boolean unless @virtual.nil? + check_property :readonly, :boolean unless @readonly.nil? + check_property :label_override, String unless @label_override.nil? + check_property :transport, Transport unless @transport.nil? + set_variables(@parameters, :__resource) + set_variables(@properties, :__resource) + check_property_list :parameters, @parameters, Api::Type + check_property_list :properties, @properties, Api::Type + + check_property :create_verb, Symbol if @create_verb + raise "Invalid create verb #{@create_verb}" \ + unless @create_verb.nil? || %i[POST PUT].include?(@create_verb) + + check_identity unless @identity.nil? + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity + + def all_user_properties + (properties || []) + (parameters || []) + end + + def required_properties + all_user_properties.select(&:required) + end + + def exported_properties + return [] if @exports.nil? + from_api = @exports.select { |e| e.is_a?(Api::Type::FetchedExternal) } + .each { |e| e.resource = self } + prop_names = @exports - from_api + all_user_properties.select { |p| prop_names.include?(p.name) } + .concat(from_api) + .sort_by(&:name) + end + + # TODO(alexstephen): Update test_constants to use this function. + # Returns all of the properties that are a part of the self_link or + # collection URLs + def uri_properties + [@base_url, @__product.base_url].map do |url| + parts = url.scan(/\{\{(.*?)\}\}/).flatten + parts << 'name' + parts.delete('project') + parts.map { |pt| all_user_properties.select { |p| p.name == pt }[0] } + end.flatten + end + + def check_identity + check_property :identity, Array + + # Ensures we have all properties defined + @identity.each do |i| + raise "Missing property/parameter for identity #{i}" \ + if all_user_properties.select { |p| p.name == i }.empty? + end + end + + # Returns all resourcerefs at any depth + def all_resourcerefs + resourcerefs_for_properties(all_user_properties, self) + end + + def kind? + !@kind.nil? + end + + def encoder? + return false if @transport.nil? + return false if @transport.encoder.nil? + true + end + + private + + # Given an array of properties, return all ResourceRefs contained within + # Requires: + # props- a list of props + # original_object - the original object containing props. This is to + # avoid self-referencing objects. + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/PerceivedComplexity + def resourcerefs_for_properties(props, original_obj) + rrefs = [] + props.each do |p| + # We need to recurse on ResourceRefs to get all levels + # We do not want to recurse on resourcerefs of type self to avoid + # infinite loop. + if p.is_a? Api::Type::ResourceRef + # We want to avoid a circular reference + # This reference may be the next reference or have some number of refs + # in between it. + next if p.resource_ref == original_obj + next if p.resource_ref == p.__resource + rrefs << p + rrefs.concat(resourcerefs_for_properties(p.resource_ref + .required_properties, + original_obj)) + elsif p.is_a? Api::Type::NestedObject + rrefs.concat(resourcerefs_for_properties(p.properties, original_obj)) + elsif p.is_a? Api::Type::Array + if p.item_type.is_a? Api::Type::NestedObject + rrefs.concat(resourcerefs_for_properties(p.item_type.properties, + original_obj)) + elsif p.item_type.is_a? Api::Type::ResourceRef + rrefs << p.item_type + rrefs.concat(resourcerefs_for_properties(p.item_type.resource_ref + .required_properties, + original_obj)) + end + end + end + rrefs.uniq + end + + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity + end + # rubocop:enable Metrics/ClassLength +end diff --git a/api/type.rb b/api/type.rb new file mode 100644 index 000000000000..9278d68f627f --- /dev/null +++ b/api/type.rb @@ -0,0 +1,381 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' +require 'google/string_utils' + +module Api + # Represents a property type + class Type < Api::Object::Named + attr_reader :description + attr_reader :output # If set value will not be sent to server on sync + attr_reader :input # If set to true value is used only on creation + attr_reader :field + attr_reader :required + + attr_reader :__resource + + MAX_NAME = 20 + + def validate + super + check_property :description, ::String + check_property :output, :boolean unless @output.nil? + check_property :field, ::String unless @field.nil? + check_property :required, :boolean unless @required.nil? + + raise 'Property cannot be output and required at the same time.' \ + if @output && @required + end + + def type + self.class.name.split('::').last + end + + def property_type + property_ns_prefix.concat([type]).join('::') + end + + def requires + File.join( + 'google', + @__resource.__product.prefix[1..-1], + 'property', + type + ).downcase + end + + def field_name + @field || @name + end + + private + + # Shrinks a long composite type name into something that can barely be + # read by humans. + # + # E.g.: Google::Compute::Property::AutoscalerCustomMetricUtilizationsArray + # --> Google::Compute::Property::Autos.....Custo.Metri.Utili.......Arr.. + # --> Google::Compute::Property::AutosCustoMetriUtiliArr + def shrink_type_name(type) + name_parts = shrink_type_name_parts(type) + + # Isolate the Google common prefix + name_parts = name_parts.drop(property_ns_prefix.size) + num_parts = name_parts.flatten.size + shrunk_names = recurse_shrink_name(name_parts, + (1.0 * MAX_NAME / num_parts).round) + type_name = Google::StringUtils.camelize(shrunk_names.flatten.join('_'), + :upper) + property_ns_prefix.concat([type_name]) + end + + def recurse_shrink_name(name, max_size) + return name[0, max_size] unless name.is_a?(::Array) + name.map { |part| recurse_shrink_name(part, max_size) } + end + + def shrink_type_name_parts(type) + type.map do |t| + if t.is_a?(::Array) + t.map { |u| Google::StringUtils.underscore(u).split('_') } + else + Google::StringUtils.underscore(t).split('_') + end + end + end + + # A constant value to be provided as field + class Constant < Type + attr_reader :value + + def validate + @description = "This is always #{value}." + super + end + end + + # Represents a primitive (non-composite) type. + class Primitive < Type + end + + # Represents a boolean + class Boolean < Primitive + end + + # Represents an integer + class Integer < Primitive + end + + # Represents a double + class Double < Primitive + end + + # Represents a string + class String < Primitive + def initialize(name) + @name = name + end + + PROJECT = Api::Type::String.new('project') + NAME = Api::Type::String.new('name') + end + + # Represents a timestamp + class Time < Primitive + end + + # A base class to tag objects that are composed by other objects (arrays, + # nested objects, etc) + class Composite < Type + end + + # Forwarding declaration to allow defining Array::NESTED_ARRAY_TYPE + class NestedObject < Composite + end + + # Forwarding declaration to allow defining Array::RREF_ARRAY_TYPE + class ResourceRef < Type + end + + # Represents an array, and stores its items' type + class Array < Composite + attr_reader :item_type + + STRING_ARRAY_TYPE = [Api::Type::Array, Api::Type::String].freeze + NESTED_ARRAY_TYPE = [Api::Type::Array, Api::Type::NestedObject].freeze + RREF_ARRAY_TYPE = [Api::Type::Array, Api::Type::ResourceRef].freeze + + def validate + super + if @item_type.is_a?(NestedObject) || @item_type.is_a?(ResourceRef) + @item_type.set_variable(@name, :__name) + @item_type.set_variable(@__resource, :__resource) + end + check_property :item_type, [::String, NestedObject, ResourceRef] + unless @item_type.is_a?(NestedObject) || @item_type.is_a?(ResourceRef) \ + || type?(@item_type) + raise "Invalid type #{@item_type}" + end + end + + def item_type_class + return Api::Type::NestedObject if @item_type.is_a? NestedObject + return Api::Type::ResourceRef if @item_type.is_a? ResourceRef + get_type("Api::Type::#{@item_type}") + end + + def property_class + if @item_type.is_a?(NestedObject) || @item_type.is_a?(ResourceRef) + type = @item_type.property_class + else + type = property_ns_prefix + type << get_type(@item_type).new(@name).type + end + type = shrink_type_name(type) + class_name = type.pop + type << "#{class_name}Array" + end + + def property_type + property_class.join('::') + end + + def property_file + File.join( + 'google', @__resource.__product.prefix[1..-1], 'property', + [get_type(@item_type).new(@name).type, 'array'].join('_') + ).downcase + end + + # Returns the file that implements this property + def requires + if @item_type.is_a?(NestedObject) || @item_type.is_a?(ResourceRef) + return @item_type.requires + end + [property_file] + end + end + + # Represents an enum, and store is valid values + class Enum < Primitive + attr_reader :values + + def validate + super + check_property :values, ::Array + end + end + + # Properties that are fetched externally + class FetchedExternal < Type + attr_writer :resource + end + + # Represents a 'selfLink' property, which returns the URI of the resource. + class SelfLink < FetchedExternal + EXPORT_KEY = 'selfLink'.freeze + + attr_reader :resource + + def name + EXPORT_KEY + end + + def out_name + Google::StringUtils.underscore(EXPORT_KEY) + end + + def field_name + name + end + end + + # Represents a reference to another resource + class ResourceRef < Type + ALLOWED_WITHOUT_PROPERTY = [SelfLink::EXPORT_KEY].freeze + + attr_reader :resource + attr_reader :imports + + def out_type + resource_ref.out_name + end + + def validate + super + @name = @resource if @name.nil? + @description = "A reference to #{@resource} resource" + + return if @__resource.nil? || @__resource.exclude + + check_property :resource, ::String + check_property :imports, ::String + check_resource_ref_exists + check_resource_ref_property_exists + end + + def property + props = resource_ref.exported_properties + .select { |prop| prop.name == @imports } + return props.first unless props.empty? + raise "#{@imports} does not exist on #{@resource}" if props.empty? + end + + def resource_ref + product = @__resource.__product + resources = product.objects.select { |obj| obj.name == @resource } + raise "Unknown item type '#{@resource}'" if resources.empty? + resources[0] + end + + def property_class + type = property_ns_prefix + type << [@resource, @imports, 'Ref'] + shrink_type_name(type) + end + + def property_type + property_class.join('::') + end + + def property_file + File.join('google', @__resource.__product.prefix[1..-1], 'property', + "#{resource}_#{@imports}").downcase + end + + def requires + [property_file] + end + + private + + def check_resource_ref_exists + product = @__resource.__product + resources = product.objects.select { |obj| obj.name == @resource } + raise "Missing '#{@resource}'" \ + if resources.empty? || resources[0].exclude + end + + def check_resource_ref_property_exists + exported_props = resource_ref.exported_properties + raise "'#{@imports}' does not exist on '#{@resource}'" \ + if exported_props.none? { |p| p.name == @imports } + end + end + + # An structured object composed of other objects. + class NestedObject < Composite + attr_reader :properties + + def validate + @description = 'A nested object resource' if @description.nil? + @name = @__name if @name.nil? + super + @properties.each { |p| p.set_variable(@__resource, :__resource) } + check_property_list :properties, @properties, Api::Type + end + + def property_class + type = property_ns_prefix + type << [@__resource.name, @name] + shrink_type_name(type) + end + + def property_type + property_class.join('::') + end + + def property_file + File.join( + 'google', @__resource.__product.prefix[1..-1], 'property', + [@__resource.name, Google::StringUtils.underscore(@name)].join('_') + ).downcase + end + + def requires + [property_file].concat(properties.map(&:requires)) + end + end + + # Represents an array of name=value pairs, and stores its items' type + class NameValues < Composite + attr_reader :key_type + attr_reader :value_type + + def validate + check_property :key_type, ::String + check_property :value_type, ::String + raise "Invalid type #{@key_type}" unless type?(@key_type) + raise "Invalid type #{@value_type}" unless type?(@value_type) + end + end + + def type?(type) + !get_type(type).nil? + end + + def get_type(type) + Module.const_get(type) + end + + def property_ns_prefix + [ + 'Google', + Google::StringUtils.camelize(@__resource.__product.prefix[1..-1], + :upper), + 'Property' + ] + end + end +end diff --git a/build/ansible b/build/ansible new file mode 160000 index 000000000000..a52fb20b5498 --- /dev/null +++ b/build/ansible @@ -0,0 +1 @@ +Subproject commit a52fb20b54983303e1a080ed2caadf327b272567 diff --git a/build/chef/_bundle b/build/chef/_bundle new file mode 160000 index 000000000000..a88aefaf8168 --- /dev/null +++ b/build/chef/_bundle @@ -0,0 +1 @@ +Subproject commit a88aefaf8168156e7192f62cf408951d598939d2 diff --git a/build/chef/auth b/build/chef/auth new file mode 160000 index 000000000000..09b7cf6ba982 --- /dev/null +++ b/build/chef/auth @@ -0,0 +1 @@ +Subproject commit 09b7cf6ba9826bc3aa0a2e847fffaa37c7cdb50d diff --git a/build/chef/compute b/build/chef/compute new file mode 160000 index 000000000000..83fcbeaf0ba1 --- /dev/null +++ b/build/chef/compute @@ -0,0 +1 @@ +Subproject commit 83fcbeaf0ba172b3ddeb606bbf4fc6507b90b935 diff --git a/build/chef/container b/build/chef/container new file mode 160000 index 000000000000..8bb344d1713a --- /dev/null +++ b/build/chef/container @@ -0,0 +1 @@ +Subproject commit 8bb344d1713a451813f965a33e4dccc70e8cab4c diff --git a/build/chef/dns b/build/chef/dns new file mode 160000 index 000000000000..deab470ca575 --- /dev/null +++ b/build/chef/dns @@ -0,0 +1 @@ +Subproject commit deab470ca575e7d2fcd1e23641cd73b1edd82b9a diff --git a/build/chef/sql b/build/chef/sql new file mode 160000 index 000000000000..7a91d0c7b799 --- /dev/null +++ b/build/chef/sql @@ -0,0 +1 @@ +Subproject commit 7a91d0c7b7995d4edb29d48cbfaa2f530bd77062 diff --git a/build/chef/storage b/build/chef/storage new file mode 160000 index 000000000000..35bbcfe71396 --- /dev/null +++ b/build/chef/storage @@ -0,0 +1 @@ +Subproject commit 35bbcfe71396aa855327fafc8a31614dff88d351 diff --git a/build/presubmit/git-hook b/build/presubmit/git-hook new file mode 100755 index 000000000000..6530d530b97d --- /dev/null +++ b/build/presubmit/git-hook @@ -0,0 +1,93 @@ +#!/bin/bash +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Magic-Id: f313f5ad-f07f-4ba6-a84e-cd15fc0393ee (used to verify hook is in git) +# +# Ensures that the presubmit was run before allowing push the change upstream. +# +# You can disable verification with a --no-verify option or by setting NO_SQ=1 +# environment variable. + +declare -r z40=0000000000000000000000000000000000000000 +declare -r max_presubmit_age=60 +declare -r disable_presubmit_check=${NO_SQ:-0} + +fatal() { + echo -e "\x1b[31m\x1b[1m[FAILED] \x1b[21m $*\x1b[0m" +} + +success() { + echo -e "\x1b[32m\x1b[1m[SUCCESS] \x1b[21m $*\x1b[0m" +} + +info() { + echo -e "\x1b[36m\x1b[1m[INFO] \x1b[21m $*\x1b[0m" +} + +parse_date() { + case $(uname -s) in + Darwin) + date -j -f '%a, %d %b %Y %T %z' "$*" +%s + ;; + *) + date -d "$*" +%s + ;; + esac +} + +IFS=' ' +while read _ local_sha remote_branch remote_sha +do + if [[ "$local_sha" == "$z40" ]]; then + continue + fi + + if [[ "$remote_sha" == "$z40" ]]; then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + if [[ $remote_branch =~ ^refs/[^/]*/dev/${USER}/ ]]; then + info "Presubmit requirement lifted for developer branch" + disable_presubmit_check=1 + fi + + # Check for age of presubmit + if [[ $disable_presubmit_check -ne 1 ]]; then + commit_date_log=$(git show -s -n 1 --format=%cD "$range") + commit_date=$(parse_date "$commit_date_log") + + presubmit_date_log=$(git rev-list -n 1 --format=%B "$range" \ + | sed -n '/BEGIN/,/END/p' | grep 'Executed @' | sed -e 's/.*@ *//') + if [[ -z "$presubmit_date_log" ]]; then + fatal "Presubmit not found in commit '$range'" + exit 1 + fi + presubmit_date=$(parse_date "$presubmit_date_log") + + presubmit_age=$(( commit_date - presubmit_date )) + info "Presubmit last run ${presubmit_age} seconds ago" + if [[ $presubmit_age -gt $max_presubmit_age ]]; then + fatal "Presubmit run is too old. It was run ${presubmit_age} seconds ago" + exit 1 + else + success "Presubmit is present and recent" + fi + fi +done + +exit 0 diff --git a/build/presubmit/lib/00-logging b/build/presubmit/lib/00-logging new file mode 100644 index 000000000000..a369de935a50 --- /dev/null +++ b/build/presubmit/lib/00-logging @@ -0,0 +1,54 @@ +#!/bin/bash + +log_status() { + ( + flock -w 60 200 + echo -e "$*" >> "${analysis_file}" + ) 200> "${analysis_lock}" +} + +log_coverage_status() { + ( + flock -w 60 200 + echo -e "$*" >> "${coverage_file}" + ) 200> "${coverage_lock}" +} + +echo_console() { + local -r msg="$*" + echo -e "$msg" +} + +[[ -z $nc ]] && declare -r nc="\x1b[0m" +[[ -z $red ]] && declare -r red="\x1b[31m" +fatal() { + local -r msg="${red}\x1b[1m[FAILED] \x1b[21m ${*}${nc}" + log_status "$msg" + echo_console "$msg" +} + +[[ -z $yellow ]] && declare -r yellow="\x1b[33m" +warn() { + local -r msg="${yellow}\x1b[1m[WARNING] \x1b[21m ${*}${nc}" + log_status "$msg" + echo_console "$msg" +} + +[[ -z $green ]] && declare -r green="\x1b[32m" +success() { + local -r msg="${green}\x1b[1m[SUCCESS] \x1b[21m ${*}${nc}" + log_status "$msg" + echo_console "$msg" +} + +[[ -z $cyan ]] && declare -r cyan="\x1b[36m" +info() { + local -r msg="${cyan}\x1b[1m[INFO] \x1b[21m ${*}${nc}" + log_status "$msg" + echo_console "$msg" +} + +log() { + local -r msg="\x1b[35m\x1b[1m[INFO] \x1b[21m ${*}${nc}" + echo_console "$msg" +} diff --git a/build/presubmit/lib/00-timers b/build/presubmit/lib/00-timers new file mode 100644 index 000000000000..75823a253424 --- /dev/null +++ b/build/presubmit/lib/00-timers @@ -0,0 +1,23 @@ +#!/bin/bash + +log_elapsed() { + local -r milestone=$1 + local -r start_time=$2 + local -r now=$(date +%s) + local -r delta=$(date -d@$((now - start_time)) -u +%H:%M:%S) + + ( + flock -w 60 200 + printf "%-23s : %s\n" $milestone $delta >> "${timer_file}" + ) 200> "${timer_lock}" + + log "timer: ${milestone} took ${delta} to complete" +} + +print_timers() { + echo + echo '+----------------------------------------' + echo '| Execution times:' + sed 's/^/| - /' < ${timer_file} + echo '+----------------------------------------' +} diff --git a/build/presubmit/lib/10-attach_report b/build/presubmit/lib/10-attach_report new file mode 100644 index 000000000000..bbccd6e2b25d --- /dev/null +++ b/build/presubmit/lib/10-attach_report @@ -0,0 +1,112 @@ +#!/bin/bash + +generate_report() { + ( + echo + echo '+----------------------------------------------------------------' + echo "| Status report:" + sed -e "s/^/| - /" < ${analysis_file} + echo '+----------------------------------------------------------------' + echo + if [[ -f "${coverage_file}" ]]; then + echo '+----------------------------------------------------------------' + echo "| Coverage:" + sed -e "s/^/| /" < "${coverage_file}" + echo '+----------------------------------------------------------------' + else + info 'No coverage information collected' + fi + echo + ) > "$report_file" + + # + # Generate commit message + # + declare -r lead='^--- BEGIN PRESUBMIT REPORT ---$' + declare -r tail='^--- END PRESUBMIT REPORT ---$' + declare -r cov_lead='^--- BEGIN COVERAGE REPORT ---$' + declare -r cov_tail='^--- END COVERAGE REPORT ---$' + git show -s --format=%B | sed "/$lead/,/$tail/d" \ + | sed "/$cov_lead/,/$cov_tail/d" \ + | grep -v '^Change-Id:' \ + | awk '/^$/ {nlstack=nlstack "\n";next;} {printf "%s",nlstack; + nlstack=""; print;}' \ + > "$commit_file" + declare -r has_report=$(git show -s --format=%B | grep -c "$lead") + declare -r report_date=$(git show -s --format=%B | sed -n "/$lead/,/$tail/p" \ + | grep "Executed @" | sed -e 's/Executed @ //') + cp "$commit_file" "${commit_file}.org" + + if $sync; then declare -r s_m=''; else declare -r s_m='NO '; fi + if $compile; then declare -r c_m=''; else declare -r c_m='NO '; fi + if $rubocop; then declare -r r_m=''; else declare -r r_m='NO '; fi + if $rspec; then declare -r t_m=''; else declare -r t_m='NO '; fi + if $hook; then declare -r g_m=''; else declare -r g_m='NO '; fi + if $ignorecovonly; then declare -r i_m=''; else declare -r i_m='NO '; fi + if $parallel; then declare -r p_m=''; else declare -r p_m='NO '; fi + + cat >>"$commit_file" </dev/null + declare -r needs_stash=$? + declare -r before_stashes=$(git stash list | wc -l) + [[ $needs_stash -eq 1 ]] && git stash 1>/dev/null + declare -r after_stashes=$(git stash list | wc -l) + git commit --amend --file="$commit_file" --allow-empty 1>/dev/null + declare -r stashed=$(( after_stashes - before_stashes )) + [[ $stashed -eq 1 ]] && git stash pop 1>/dev/null + else + warn 'Skipping attach report to commit' + fi + fi + else + warn 'Not attaching report to commit' + fi +} diff --git a/build/presubmit/lib/10-common b/build/presubmit/lib/10-common new file mode 100644 index 000000000000..4a2a8c036d80 --- /dev/null +++ b/build/presubmit/lib/10-common @@ -0,0 +1,44 @@ +#!/bin/bash + +declare -r perc_bar_builder="${my_dir}/build/presubmit/perc_bar.rb" + +# float number comparison +float_eq() { + awk -v n1="$1" -v n2="$2" 'BEGIN {if (n1==n2) exit 0; exit 1}' +} + +perc_as_blocks() { + local -r before=${1:-0} + local -r after=${2:-0} + local -r slots=20 + + if float_eq "$after" 100.0; then + ruby -e "puts \"[#{'!' * ${slots}}]\"" + else + ruby "${perc_bar_builder}" "${before}" "${after}" "${slots}" + fi +} + +# Prompts for the Y/N and waits for the answer. +# Function will block untill either Y or N is pressed. +# +# Returns: 0 = yes, 1 = no +confirm() { + local -r msg=${1:-Confirm} + echo + while true; do + read -n1 -p "${msg} (y/n) ? " confirm + if [[ -z $confirm ]]; then + echo " You need to select either Y or N. Try again!" + elif [[ 'Yy' =~ $confirm ]]; then + echo + return 0 + elif [[ 'Nn' =~ $confirm ]]; then + echo + return 1 + else + echo ". '${confirm}'!? You need to select either Y or N. Try again!" + fi + done + echo +} diff --git a/build/presubmit/lib/10-coverage b/build/presubmit/lib/10-coverage new file mode 100644 index 000000000000..cbd7d6f8775c --- /dev/null +++ b/build/presubmit/lib/10-coverage @@ -0,0 +1,36 @@ +#!/bin/bash + +gather_code_coverage() { + local -r proj=$1 + local before_coverage='' + local after_coverage='' + + if git show origin/master:coverage/.last_run.json 1>/dev/null 2>&1; then + before_coverage=$(git show origin/master:coverage/.last_run.json \ + | ruby -e 'require "json"; + puts JSON.parse(STDIN.read)["result"]["covered_percent"]') + fi + if [[ -f coverage/.last_run.json ]]; then + after_coverage=$(ruby -e 'require "json"; + puts JSON.parse(STDIN.read)["result"] ["covered_percent"]' \ + < coverage/.last_run.json) + fi + if [[ -z ${before_coverage} && -z ${after_coverage} ]]; then + log_coverage_status "Project '${proj}' has no coverage information :-(" + elif [[ -z ${before_coverage} ]]; then + local -r blocks=$(perc_as_blocks 0 "${after_coverage}") + log_coverage_status "${blocks} Project '${proj}' ${after_coverage}% :-)" + elif [[ ! -z ${before_coverage} && -z ${after_coverage} ]]; then + local -r blocks=$(perc_as_blocks "${before_coverage}" 0) + log_coverage_status \ + "${blocks} Project '${proj}' removed coverage information :-(" + elif float_eq "$before_coverage" "$after_coverage"; then + local -r blocks=$(perc_as_blocks "${before_coverage}" "${before_coverage}") + log_coverage_status "${blocks} Project '${proj}' ${before_coverage}%" + else + local -r blocks=$(perc_as_blocks "${before_coverage}" "${after_coverage}") + s=$(ruby -e "puts ${before_coverage} > ${after_coverage} ? ':-(' : ':-)'") + log_coverage_status \ + "${blocks} Project '${proj}' ${before_coverage}% -> ${after_coverage}% $s" + fi +} diff --git a/build/presubmit/lib/10-log_failures b/build/presubmit/lib/10-log_failures new file mode 100644 index 000000000000..1b39e35c2bdb --- /dev/null +++ b/build/presubmit/lib/10-log_failures @@ -0,0 +1,12 @@ +#!/bin/bash + +log_failures() { + local -r name=$1 + local -r fail_count=$2 + + if [[ $fail_count -eq 0 ]]; then + success "Project '${name}' passed" + else + fatal "Project '${name}' failed (${fail_count} failures)" + fi +} diff --git a/build/presubmit/lib/10-submodule_init b/build/presubmit/lib/10-submodule_init new file mode 100644 index 000000000000..b1e082c3451d --- /dev/null +++ b/build/presubmit/lib/10-submodule_init @@ -0,0 +1,52 @@ +#!/bin/bash + +submodule_init() { + if git submodule status "$mod" | grep '^-' 1>/dev/null; then + log "Initializing repository" + declare module_init=1 + declare module_head=$(git submodule status -- "$mod" | awk '{print $1}' \ + | tr -d '-') + git submodule update --init --force -- "$mod" 1>/dev/null + + local updated_head=$(git submodule status -- "$mod" | awk '{print $1}' \ + | tr -d '-' | tr -d '+') + # If git changed the hash it means we could not fetch the commit. So let's + # try to fetch it manually. This is a strong indication the change is not + # committed yet. + if [[ $updated_head != $module_head ]]; then + pushd "${mod}" 1>/dev/null + git fetch origin "${module_head}" + err=$? + popd 1>/dev/null + if [[ $err -ne 0 ]]; then + fatal "Module commit '${module_head}' cannot be retrieved (fetch)" + else + # Attempt to fetch a second time + git submodule update --force -- "$mod" + if [[ $? -ne 0 ]]; then + fatal "Module commit '${module_head}' cannot be retrieved (update)" + fi + fi + fi + + export module_init + export module_head + fi +} + +submodule_update() { + if [[ $module_init -eq 1 ]]; then + declare my_head=$(git rev-parse HEAD) + if [[ $module_head != $my_head ]]; then + warn "Project '$mod' has a non-official module reference" + git fetch origin "${module_head}" 1>/dev/null + + declare module_head=$(git rev-parse "${hash_style}" "${module_head}") + + log "Moving '$mod' to '${module_head}'" + git checkout FETCH_HEAD 1>/dev/null 2>&1 + + export module_head + fi + fi +} diff --git a/build/presubmit/perc_bar.rb b/build/presubmit/perc_bar.rb new file mode 100644 index 000000000000..b7d273d521ff --- /dev/null +++ b/build/presubmit/perc_bar.rb @@ -0,0 +1,59 @@ +#!/bin/ruby +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Google + class ProgressBar + BAD_COVERAGE = -5 # drop in 5% is really bad + GOOD_COVERAGE = 95 # happy if coverage is over 95% + + COLOR_CYAN = "\x1b[36m" + COLOR_GREEN = "\x1b[32m" + COLOR_RED = "\x1b[31m" + COLOR_YELLOW = "\x1b[33m" + COLOR_NONE = "\x1b[0m" + COLOR_NO_COLOR = '' + + def self.build(before, after, slots) + step = 100.0 / slots + real_delta = after - before + # If any delta, we should at minimum 1 char + delta = [(real_delta.abs / step).round, 1].max + base = [([before, after].min / step).round - 1, 0].max + gap = (slots - delta - base - 1) + color = if real_delta <= BAD_COVERAGE + COLOR_RED + elsif real_delta < 0 + COLOR_YELLOW + elsif real_delta > 0 && after >= GOOD_COVERAGE + COLOR_GREEN + elsif real_delta > 0 + COLOR_CYAN + else + COLOR_NO_COLOR + end + if before > after + "#{color}[#{'-' * base}|#{'X' * delta}#{' ' * gap}]#{COLOR_NONE}" \ + elsif before == after + "#{color}[#{'-' * base}#{'-' * delta}|#{' ' * gap}]" \ + else + "#{color}[#{'-' * base}|#{'+' * delta}#{' ' * gap}]#{COLOR_NONE}" \ + end + end + end +end + +# Launched from command line +if __FILE__ == $0 + puts Google::ProgressBar.build(ARGV[0].to_f, ARGV[1].to_f, ARGV[2].to_f) +end diff --git a/build/presubmit/presubmit.d/00-bundler b/build/presubmit/presubmit.d/00-bundler new file mode 100644 index 000000000000..d2fa55785743 --- /dev/null +++ b/build/presubmit/presubmit.d/00-bundler @@ -0,0 +1,19 @@ +#!/bin/bash + +PREFLIGHT_TESTS+=('check_bundler') + +check_bundler() { + if $bundler || $rspec; then + if ! which bundle 1>/dev/null 2>&1; then + fatal "Bundle tool not found" + cat </dev/null; then + fatal "Git hook not found" + cat </dev/null 2>&1; then + warn "Bundle tool not found. Not updating dependencies for '${name}'" + return 1 + fi + + bundle install 1>&2 + if [[ $? -ne 0 ]]; then + fatal "Bundle failed for '${name}'" + return 1 + fi + else + warn "Bundle skipped for '${name}'" + return 1 + fi + + log "No bundle failures for '${name}'" 1>&2 + return 0 +} diff --git a/build/presubmit/presubmit.d/10-gerrit_closed_change b/build/presubmit/presubmit.d/10-gerrit_closed_change new file mode 100644 index 000000000000..c21156040186 --- /dev/null +++ b/build/presubmit/presubmit.d/10-gerrit_closed_change @@ -0,0 +1,28 @@ +#!/bin/bash + +PREFLIGHT_TESTS+=('check_gerrit_closed_change') + +# +# Ensure we are not testing a closed CL +# +is_change_closed() { + local -r my_head=$(git rev-parse HEAD) + local -r change_path=$(git ls-remote -q | grep "$my_head" 2>/dev/null \ + | grep refs/changes/ \ + | awk '{print $2}' | cut -d/ -f3,4) + [[ -z "${change_path}" ]] && return 1 # Our commit does not exist upstream + local -r change_meta="refs/changes/${change_path}/meta" + git fetch -q origin "${change_meta}" + local -r status=$(git log FETCH_HEAD | grep Status: | tr -d ' ' | head -1 \ + | cut -d: -f2) + [[ $status == 'new' ]] && return 1 # Our change is open for code review + return 0 +} + +check_gerrit_closed_change() { + if is_change_closed; then + log 'Presubmit running on a closed change.' + else + log 'Running on an open (or unsubmitted) change.' + fi +} diff --git a/build/presubmit/presubmit.d/20-80_chars b/build/presubmit/presubmit.d/20-80_chars new file mode 100644 index 000000000000..62b425110bda --- /dev/null +++ b/build/presubmit/presubmit.d/20-80_chars @@ -0,0 +1,62 @@ +#!/bin/bash + +CORE_TESTS+=('check_80_chars') +MOD_TESTS+=('check_80_chars') + +# Returns the entries in the config that have 80_chars:forgive for the product +# being tested. +forgive_80chars() { + local -r name=$1 + + grep -E "${name}.*=.*80_chars:forgive" "${presubmit_cfg}" +} + +# Executes trailing_spaces check +# @return 0 if the test passed, 1 otherwise +check_80_chars() { + local -r name=$1 + local -r extensions=('rb' 'erb' 'yaml' 'sh') + + if $spaces; then + files=() + for ext in ${extensions[*]}; do + files+=( + $(find . -type f -name "*.${ext}" | grep build/presubmit) + $(find . -type f -name "*.${ext}" | grep -v build \ + | grep -v "files/spec~.*yaml" \ + | grep -v "./spec/data/network/.*.yaml" \ + | grep -v "./tests/end2end/plan.yaml") + ) + done + + forgive_files=$(forgive_80chars "${name//build\//}") + + if [[ -z ${files[*]} ]]; then + log "No files for >80 characters test" + else + violations=$(grep -Hn '.\{81\}' ${files[*]}) + + if [[ ! -z $violations && ! -z $forgive_files ]]; then + # Let's try to forgive violations based on config + IFS='|' + while read dummy file mask; do + violations=$(grep -v -E ".\/${file}:.*${mask}" <<< "${violations}") + done <<< "$forgive_files" + unset IFS + fi + + if [[ ! -z $violations ]]; then + echo "Files with >80 characters per line:" + echo "${violations}" + fatal "80 characters failed for '${name}'" + return 1 + fi + fi + else + warn "80 characters skipped for '${name}'" + return 1 + fi + + log "No 80 characters failures for '${name}'" 1>&2 + return 0 +} diff --git a/build/presubmit/presubmit.d/20-rspec b/build/presubmit/presubmit.d/20-rspec new file mode 100644 index 000000000000..27a01c670110 --- /dev/null +++ b/build/presubmit/presubmit.d/20-rspec @@ -0,0 +1,24 @@ +#!/bin/bash + +CORE_TESTS+=('check_rspec') +MOD_TESTS+=('check_rspec') + +# Executes rspec check +# @return 0 if the test passed, 1 otherwise +check_rspec() { + local -r name=$1 + + if $rspec; then + bundle exec rspec 1>&2 + if [[ $? -ne 0 ]]; then + fatal "Tests failed for '${name}'" + return 1 + fi + else + warn "Tests skipped for '${name}'" + return 1 + fi + + log "No test failures for '${name}'" + return 0 +} diff --git a/build/presubmit/presubmit.d/20-rubocop b/build/presubmit/presubmit.d/20-rubocop new file mode 100644 index 000000000000..336112a0457a --- /dev/null +++ b/build/presubmit/presubmit.d/20-rubocop @@ -0,0 +1,59 @@ +#!/bin/bash + +CORE_TESTS+=('check_rubocop') +MOD_TESTS+=('check_rubocop') + +# Checks if presubmit.cfg specifies to not use --ignore-parent-exclusion flag +# @return 0 if the flag should be not used, 1 otherwise +rubocop_flag() { + local -r provider=$1 + local -r product=$2 + + grep -E "${provider}/${product}.*=.*rubocop:noflag" "${presubmit_cfg}" \ + 1>/dev/null +} + +run_rubocop() { + if rubocop_flag "${provider}" "${product}"; then + log "Module ${name} will not use custom rubocop flag" 1>&2 + bundle exec rubocop 1>&2 + else + bundle exec rubocop --ignore-parent-exclusion 1>&2 + fi +} + +# Executes rubocop check +# @return 0 if the test passed, 1 otherwise +check_rubocop() { + local -r name=$1 + + if $rubocop; then + # TODO(alexstephen): Monitor rubocop upsteam changes + # https://github.com/bbatsov/rubocop/pull/4329 + # Change will allow rubocop to use --ignore-parent-exclusion flag + # Current rubocop upstream will not check Chef files because of + # AllCops/Exclude + # Remove no_rubocop_flag check once this reaches upstream. + + attempts=1 + run_rubocop + local status=$? + while [[ $status == 2 && attempts -lt 3 ]]; do + log "Rubocop failed due to internal error. Trying again." 1>&2 + attempts=$((attempts + 1)) + run_rubocop + status=$? + done + + if [[ $status -ne 0 ]]; then + fatal "Rubocop failed for '${name}'" + return 1 + fi + else + warn "Rubocop skipped for '${name}'" + return 1 + fi + + log "No rubocop failures for '${name}'" 1>&2 + return 0 +} diff --git a/build/presubmit/presubmit.d/20-tabs b/build/presubmit/presubmit.d/20-tabs new file mode 100644 index 000000000000..869987692df3 --- /dev/null +++ b/build/presubmit/presubmit.d/20-tabs @@ -0,0 +1,37 @@ +#!/bin/bash + +CORE_TESTS+=('check_tabs') +MOD_TESTS+=('check_tabs') + +# Executes trailing_spaces check +# @return 0 if the test passed, 1 otherwise +check_tabs() { + local -r name=$1 + local -r extensions=('rb' 'erb' 'yaml' 'sh') + + if $spaces; then + files=() + for ext in ${extensions[*]}; do + files+=( + $(find . -type f -name "*.${ext}" | grep build/presubmit) + $(find . -type f -name "*.${ext}" | grep -v build) + ) + done + + if [[ -z ${files[*]} ]]; then + log "No files for tabs test" + elif grep -cP "\t" ${files[*]} | grep -v "^0$" | grep -c -v ":0$" \ + 1>/dev/null; then + echo "Files with tabs:" + grep -HnP "\t" ${files[*]} + fatal "Tabs failed for '${name}'" + return 1 + fi + else + warn "Tabs skipped for '${name}'" + return 1 + fi + + log "No tabs failures for '${name}'" 1>&2 + return 0 +} diff --git a/build/presubmit/presubmit.d/20-trailing_spaces b/build/presubmit/presubmit.d/20-trailing_spaces new file mode 100644 index 000000000000..b7039d5ffc39 --- /dev/null +++ b/build/presubmit/presubmit.d/20-trailing_spaces @@ -0,0 +1,37 @@ +#!/bin/bash + +CORE_TESTS+=('check_trailing_spaces') +MOD_TESTS+=('check_trailing_spaces') + +# Executes trailing_spaces check +# @return 0 if the test passed, 1 otherwise +check_trailing_spaces() { + local -r name=$1 + local -r extensions=('rb' 'erb' 'yaml' 'sh' 'md') + + if $spaces; then + files=() + for ext in ${extensions[*]}; do + files+=( + $(find . -type f -name "*.${ext}" | grep build/presubmit) + $(find . -type f -name "*.${ext}" | grep -v build) + ) + done + + if [[ -z ${files[*]} ]]; then + log "No files for trailing spaces test" + elif grep -c " *$" ${files[*]} | grep -v "^0$" | grep -c -v ":0$" \ + 1>/dev/null; then + echo "Files with trailing spaces:" + grep -Hn " *$" ${files[*]} + fatal "Trailing spaces failed for '${name}'" + return 1 + fi + else + warn "Trailing spaces skipped for '${name}'" + return 1 + fi + + log "No trailing spaces failures for '${name}'" 1>&2 + return 0 +} diff --git a/build/presubmit/presubmit.d/20-uncommitted b/build/presubmit/presubmit.d/20-uncommitted new file mode 100644 index 000000000000..5ff82da33059 --- /dev/null +++ b/build/presubmit/presubmit.d/20-uncommitted @@ -0,0 +1,28 @@ +#!/bin/bash + +MOD_TESTS+=('check_uncommitted') +POST_MOD_TESTS+=('check_uncommitted') + +check_uncommitted() { + local -r name=$1 + + IFS=' ' + local -r uncommitted=$(git status --untracked-files=no --short) + if [[ ! -z "${uncommitted}" ]]; then + local -r all_but_coverage=$( + grep -v -c coverage/.last_run.json <<< "$uncommitted") + if [[ $all_but_coverage -eq 0 ]]; then + if $ignorecovonly; then + log "Reverting coverage for ${name} back to HEAD." + git checkout -- coverage/.last_run.json + else + log "Only change in '${name}' is coverage stats. Ignoring." + fi + else + while read action file; do + warn "Project '${name}' has uncommitted: ${file} (${action})" + done <<< "$uncommitted" + fi + fi + IFS=$'\n' +} diff --git a/build/presubmit/run b/build/presubmit/run new file mode 100755 index 000000000000..091f4fb9c35c --- /dev/null +++ b/build/presubmit/run @@ -0,0 +1,417 @@ +#!/bin/bash +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Executes code generation on all products and providers, performs QA tests and +# produces output report. + +# Force all background jobs and tasks to exit as well. +trap 'kill -9 $(jobs -p) 2>/dev/null' EXIT SIGTERM + +declare -r presubmit_dir="$(dirname "$0")" +declare -r my_dir=$PWD +declare -r presubmit_cfg="${my_dir}/products/presubmit.cfg" + +declare hash_style='' +[[ -z "${LONG_HASH}" ]] && hash_style='--short' +readonly hash_style + +declare attach=true +declare bundler=true +declare compile=true +declare force=false +declare force_attach=false +declare hook=true +declare ignorecovonly=false +declare parallel=false +declare plugin_list= +declare product_list= +declare rspec=true +declare rubocop=true +declare sync=true + +declare sed_extended='-r' +[[ $(uname -s) == 'Darwin' ]] && sed_extended='-E' +readonly sed_extended +export sed_extended + +declare start_time=$(date +%s) + +help() { + cat </dev/null +} + +while getopts 'cfgGhipPrstM:m:' arg; do + case $arg in + G) force_attach=true ;; + M) plugin_list=$OPTARG ;; + P) parallel=true ;; + c) compile=false ;; + f) force=true ;; + g) attach=false ;; + h) help ;; + i) ignorecovonly=true ;; + m) product_list=$OPTARG ;; + p) hook=false ;; + r) rubocop=false ;; + s) sync=false ;; + t) rspec=false ;; + *) exit 1 ;; + esac +done + +readonly attach +readonly bundler +readonly compile +readonly force +readonly force_attach +readonly hook +readonly ignorecovonly +readonly parallel +readonly plugin_list +readonly rspec +readonly rubocop +readonly sync + +export attach +export bundler +export compile +export force +export force_attach +export hook +export ignorecovonly +export rspec +export rubocop +export sync + +# Minimalistic console output until logging module gets loaded +log() { echo "[INFO] $*"; } + +declare PREFLIGHT_TESTS=() +declare CORE_TESTS=() +declare MOD_TESTS=() +declare POST_MOD_TESTS=() + +# Load all test plugins +for mod in $presubmit_dir/lib/* $presubmit_dir/presubmit.d/*; do + log "Loaded '${mod//build\/presubmit\//}'" + source "$mod" +done + +if $parallel && ! $force; then + fatal 'Parallel mode (-P) requires to force overwrite submodules (-f)' + exit 1 +fi + +readonly PREFLIGHT_TESTS +readonly CORE_TESTS +readonly MOD_TESTS +readonly POST_MOD_TESTS + +for mod in ${PREFLIGHT_TESTS[*]}; do + log "(active) preflight test '$mod'" +done +for mod in ${CORE_TESTS[*]}; do + log "(active) core test '${mod//check_/}'" +done +for mod in ${MOD_TESTS[*]}; do + log "(active) module test '${mod//check_/}'" +done +for mod in ${POST_MOD_TESTS[*]}; do + log "(active) post module test '${mod//check_/}'" +done + +if [[ ! -z $product_list ]]; then + warn 'Running presubmit for select modules only' +fi + +if [[ ! -z $plugin_lits ]]; then + warn 'Running presubmit for select plugins only' +fi + +# Execute "pre-flight" checks. If any fails we are not clear to run the +# presubmit tests. +for mod in ${PREFLIGHT_TESTS[*]}; do + if [[ ! -z $plugin_list && ! (${mod//check_/} =~ $plugin_list) ]]; then + echo "Skipping ${mod//check_/}" + continue + fi + $mod +done + +# +# Warns if multiple local branches matches the commit, in case various pending +# CLs exist in the local repository. +# +declare -r my_head_short=$(git rev-parse --short HEAD) +declare -r branches_matching=$(git branch -v | grep -c "$my_head_short") +if [[ $branches_matching -gt 1 ]]; then + log 'More than 1 branch matches our commit. Proceed with caution.' +fi + +git submodule init + +# +# Check our own project +# +declare codegen_failures=0 + +declare codegen_start=$(date +%s) +if [[ -z $product_list || $(grep codegen <<< $product_list) ]]; then + # Execute "core" checks. A core test is a test against our own code base. + for mod in ${CORE_TESTS[*]}; do + if [[ ! -z $plugin_list && ! (${mod//check_/} =~ $plugin_list) ]]; then + echo "Skipping ${mod//check_/}" + continue + fi + $mod 'codegen' + codegen_failures=$((codegen_failures + $?)) + done + + gather_code_coverage 'codegen' +else + warn "Skipping 'codegen'" +fi +log_elapsed 'codegen' $codegen_start + +# +# Check generated modules +# +module_checker() { + local -r mod=$1 + local -r provider=$2 + local -r product=$3 + local -r mod_start=$(date +%s) + + echo '-----------------------------------------------------------------' + echo "Checking: $mod (product:${product} | provider:${provider})" + + if [[ ! -z $product_list \ + && ! $(grep "${provider}/${product}" <<< $product_list) ]]; then + warn "Skipping module '${mod}'" + continue + fi + + declare module_init=0 + + submodule_init + + pushd "$mod" 1>/dev/null + + submodule_update + + declare mod_failures=0 + + git fetch 1>/dev/null + + declare my_hash=$(git rev-parse ${hash_style} HEAD) + + # Retrieve the hash from origin/master, unless a variable + # MASTER__ is defined, e.g. MASTER_PUPPET_DNS + declare master_hash_mock="master_${provider}_${product}" + declare master_hash_mock_var=$(tr /a-z/ /A-Z/ <<< "${master_hash_mock}") + declare master_hash=${!master_hash_mock_var} + + if [[ ! $master_hash ]]; then + master_hash=$(git rev-parse ${hash_style} origin/master) + else + master_hash=$(git rev-parse ${hash_style} "${master_hash}") + info "Forcing ${provider}/${product} to ${master_hash}" + fi + + if ! git status | grep 'nothing to commit, working .* clean' \ + 1>/dev/null; then + if [[ $force != true ]]; then + if ! confirm 'Okay to wipe all changes'; then + fatal "Skipping project '$mod' to avoid losing local changes" + popd; continue + fi + else + warn "Project '$mod' has local changes" + fi + fi + + if [[ "${my_hash}" != "${master_hash}" ]]; then + log "Local : ${my_hash}" + log "Master : ${master_hash}" + if $sync; then + log 'Moving head and add our change to this commit' + git reset --hard "${master_hash}" + git clean -fdx + else + info "Keeping ${provider}/${product} at local ${my_hash}" + warn "Not syncing master for '$mod'" + fi + fi + + if ! skip_phase 'compile' "${provider}" "${product}"; then + if $compile; then + # Let's generate the code and see if changes happen + pushd "$my_dir" 1>/dev/null + ./compiler -p "products/${product}" -e "${provider}" -o "$mod" 2>/dev/null + err=$? + popd 1>/dev/null + if [[ $err -ne 0 ]]; then + fatal 'Executing compiler failed' + exit 1 + fi + else + warn "Skipping compile for '$mod'" + fi + else + log "Module ${product} skipped by the presubmit configuration." + fi + + if git status -s | grep -v coverage/.last_run.json 1>/dev/null; then + warn "Project '$mod' had changes upon it not committed yet" + git status -s + fi + + # Execute "module" checks. A module test is a test against our dependencies. + for mod_test in ${MOD_TESTS[*]}; do + if [[ ! -z $plugin_list && ! (${mod_test//check_/} =~ $plugin_list) ]]; then + echo "Skipping ${mod_test//check_/} in ${mod}" + continue + fi + $mod_test "$mod" + mod_failures=$((mod_failures + $?)) + done + + gather_code_coverage "$mod" + + popd 1>/dev/null + + log_failures "${mod}" "${mod_failures}" + + log_elapsed "${provider}/${product}" $mod_start +} + +module_logfile() { + local -r mod=$1 + echo "${parallel_output_prefix}-${mod//\//-}" +} + +module_list() { + git submodule status | grep build/ | awk '{print $2}' +} + +for mod in $(module_list); do + declare provider=$(cut -d/ -f2 <<< "$mod") + declare product=$(cut -d/ -f3 <<< "$mod") + + if $parallel; then + module_checker $mod $provider $product 2>&1 \ + | tee "$(module_logfile $mod)" \ + | sed "s/^/${provider}(${product}):/" & + else + module_checker $mod $provider $product + fi +done + +if $parallel; then + wait # for all submodule jobs to finish + + # Emit the output so it can be captured by the systems + for mod in $(module_list); do + cat "$(module_logfile $mod)" + done +fi + +# Execute post mod checks. A post mod test is a test that runs after all module +# tests are executed (e.g. aggregate statistics) +for mod in ${POST_MOD_TESTS[*]}; do + if [[ ! -z $plugin_list && ! (${mod//check_/} =~ $plugin_list) ]]; then + echo "Skipping ${mod//check_/}" + continue + fi + $mod 'codegen' + codegen_failures=$((codegen_failures + $?)) +done + +log_failures 'codegen' "${codegen_failures}" + +declare -r change_id=$(git show -s --format=%B | grep '^Change-Id:') +export change_id + +log_elapsed 'presubmit' $start_time +print_timers + +generate_report +attach_report + +cleanup diff --git a/build/puppet/_bundle b/build/puppet/_bundle new file mode 160000 index 000000000000..86167d46b2c3 --- /dev/null +++ b/build/puppet/_bundle @@ -0,0 +1 @@ +Subproject commit 86167d46b2c3ebcf3556224f029a2a5fb7d59f28 diff --git a/build/puppet/auth b/build/puppet/auth new file mode 160000 index 000000000000..19370596b455 --- /dev/null +++ b/build/puppet/auth @@ -0,0 +1 @@ +Subproject commit 19370596b4556389cefcb1875b2ca72024ed5d8e diff --git a/build/puppet/compute b/build/puppet/compute new file mode 160000 index 000000000000..51f6bd077791 --- /dev/null +++ b/build/puppet/compute @@ -0,0 +1 @@ +Subproject commit 51f6bd07779198be96e2a80eaaf00e046cbe3a33 diff --git a/build/puppet/container b/build/puppet/container new file mode 160000 index 000000000000..eb156bb801ce --- /dev/null +++ b/build/puppet/container @@ -0,0 +1 @@ +Subproject commit eb156bb801cede9fd80a03c800b00b5eb64acc77 diff --git a/build/puppet/dns b/build/puppet/dns new file mode 160000 index 000000000000..b2eb23efc7e6 --- /dev/null +++ b/build/puppet/dns @@ -0,0 +1 @@ +Subproject commit b2eb23efc7e653621424cda0ad4e4f2c95bf4a3d diff --git a/build/puppet/logging b/build/puppet/logging new file mode 160000 index 000000000000..6efee6416812 --- /dev/null +++ b/build/puppet/logging @@ -0,0 +1 @@ +Subproject commit 6efee64168120bd7222eb7d0f7c99b04b7d27dd7 diff --git a/build/puppet/pubsub b/build/puppet/pubsub new file mode 160000 index 000000000000..0fd61194944e --- /dev/null +++ b/build/puppet/pubsub @@ -0,0 +1 @@ +Subproject commit 0fd61194944e81d9d82325e7d652c5c615ff3851 diff --git a/build/puppet/spanner b/build/puppet/spanner new file mode 160000 index 000000000000..45bc0019b2e7 --- /dev/null +++ b/build/puppet/spanner @@ -0,0 +1 @@ +Subproject commit 45bc0019b2e7e6439bb2424b716c42b07072d194 diff --git a/build/puppet/sql b/build/puppet/sql new file mode 160000 index 000000000000..760c8a2cfeab --- /dev/null +++ b/build/puppet/sql @@ -0,0 +1 @@ +Subproject commit 760c8a2cfeab98579d19a90c61149645be1a4581 diff --git a/build/puppet/storage b/build/puppet/storage new file mode 160000 index 000000000000..e4daf6f78df9 --- /dev/null +++ b/build/puppet/storage @@ -0,0 +1 @@ +Subproject commit e4daf6f78df9edce0371abe4077417fd0c573d2f diff --git a/compile/core.rb b/compile/core.rb new file mode 100644 index 000000000000..04422f04f73c --- /dev/null +++ b/compile/core.rb @@ -0,0 +1,144 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'binding_of_caller' +require 'erb' + +module Compile + # Unique ID for the Google libraries to be compiled/used by modules + module Libraries + NETWORK = 'network'.freeze + end + + # Helper functions to aid compiling and including files + module Core + def compiler + "#{self.class.name.split('::').last.downcase}-codegen".freeze + end + + def include(file) + get_helper_file(file) + end + + def compile(file, caller_frame = 1) + ctx = binding.of_caller(caller_frame) + has_erbout = ctx.local_variables.include?(:_erbout) + content = ctx.local_variable_get(:_erbout) if has_erbout # save code + ctx.local_variable_set(:compiler, compiler) + Google::LOGGER.info "Compiling #{file}" + input = ERB.new get_helper_file(file, true), nil, '-%>' + compiled = input.result(ctx) + ctx.local_variable_set(:_erbout, content) if has_erbout # restore code + compiled + end + + def compile_if(config, node) + file = Google::HashUtils.navigate(config, node) + compile(file, 2) unless file.nil? + end + + def indent(text, spaces) + indent_array(text, spaces).join("\n") + end + + def indent_list(text, spaces, last_line_comma = false) + if last_line_comma + [indent_array(text, spaces).join(",\n"), ','].join + else + indent_array(text, spaces).join(",\n") + end + end + + def indent_array(text, spaces) + return [] if text.nil? + lines = text.class <= Array ? text : text.split("\n") + lines.map do |line| + if line.class <= Array + indent(line, spaces) + elsif line.include?("\n") + indent(line.split("\n"), spaces) + elsif line.strip.empty? + '' + else + ' ' * spaces + line.gsub(/\n/, "\n" + ' ' * spaces) + end + end + end + + # Includes a require clause and schedules library to be copied, potentially + # with its dependencies & tests. + def emit_google_lib(ctx, lib, file) + product_ns = ctx.local_variable_get(:object).__product.prefix[1..-1] + + files = case lib + when Libraries::NETWORK + google_lib_network file, product_ns + else + google_lib_basic lib, file, product_ns + end + + emit_libraries(ctx.local_variable_get(:output_folder), product_ns, files) + + File.join(*%w[google].concat([product_ns, lib, file])) + end + + private + + def get_helper_file(file, remove_copyright_notice = true) + content = IO.read(file) + remove_copyright_notice ? strip_copyright_notice(content) : content + end + + def strip_copyright_notice(content, comment_marker = '#') + lines = content.split("\n") + return content unless lines[0].include?('Copyright 20') + lines = lines.drop(1) while lines[0].start_with?(comment_marker) + lines = lines.drop(1) while lines[0].strip.empty? + lines.join("\n") + end + + def emit_libraries(output_folder, product_name, files) + product_ns = product_name.downcase + compile_file_list(output_folder, files, + product_ns: Google::StringUtils.camelize(product_name, + :upper), + product_ns_dir: product_ns) + end + + def google_lib_basic_files(file, product_ns, *google_root) + { + File.join(*google_root, product_ns, folder, "#{file}.rb") => + File.join('templates', folder, "#{file}.rb.erb"), + File.join('spec', "#{folder}_#{file}_spec.rb") => + File.join('templates', folder, "#{file}_spec.rb.erb") + } + end + + def google_lib_network_files(file, product_ns, *google_root) + files = {} + folder = Libraries::NETWORK + ['base', file].each do |f| + files[File.join(*google_root, product_ns, folder, "#{f}.rb")] = + File.join('templates', folder, "#{f}.rb.erb") + base_spec = File.join('templates', folder, "#{f}_spec.rb.erb") + files[File.join('spec', "#{folder}_#{f}_spec.rb")] = base_spec \ + if f != 'base' || File.exist?(base_spec) + end + %w[network_blocker network_blocker_spec].each do |f| + files[File.join('spec', "#{f}.rb")] = File.join('templates', folder, + "#{f}.rb.erb") + end + files + end + end +end diff --git a/compiler b/compiler new file mode 120000 index 000000000000..8d27d51c7704 --- /dev/null +++ b/compiler @@ -0,0 +1 @@ +compiler.rb \ No newline at end of file diff --git a/compiler.rb b/compiler.rb new file mode 100755 index 000000000000..64727570dda8 --- /dev/null +++ b/compiler.rb @@ -0,0 +1,86 @@ +#!/usr/bin/env ruby +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +$LOAD_PATH.unshift File.dirname(__FILE__) + +# Run from compiler dir so all references are relative to the compiler +# executable. This allows the following command line: +# puppet-google-compute$ ../puppet-codegen/compiler products/compute $PWD +Dir.chdir(File.dirname(__FILE__)) + +# Our default timezone is UTC, to avoid local time compromise test code seed +# generation. +ENV['TZ'] = 'UTC' + +require 'api/compiler' +require 'optparse' +require 'provider/ansible' +require 'provider/chef' +require 'provider/chef/bundle' +require 'provider/example' +require 'provider/puppet' +require 'provider/puppet/bundle' +require 'pp' if ENV['COMPILER_DEBUG'] + +catalog = nil +output = nil +provider = nil +types_to_generate = [] + +ARGV << '-h' if ARGV.empty? + +OptionParser.new do |opt| + opt.on('-p', '--product PRODUCT', 'Folder with product catalog') do |p| + catalog = p + end + opt.on('-o', '--output OUTPUT', 'Folder for module output') do |o| + output = o + end + opt.on('-e', '--engine ENGINE', 'Technology to build for') do |e| + provider = "#{e}.yaml" + end + opt.on('-t', '--type TYPE[,TYPE...]', Array, 'Types to generate') do |t| + types_to_generate = t + end + opt.on('-h', '--help', 'Show this message') do + puts opt + exit + end +end.parse! + +raise 'Option -p/--product is a required parameter' if catalog.nil? +raise 'Option -o/--output is a required parameter' if output.nil? +raise 'Option -e/--engine is a required parameter' if provider.nil? + +raise "Product '#{catalog}' does not have api.yaml" \ + unless File.exist?(File.join(catalog, 'api.yaml')) +raise "Product '#{catalog}' does not have #{provider} settings" \ + unless File.exist?(File.join(catalog, provider)) + +raise "Output '#{output}' is not a directory" unless Dir.exist?(output) + +Google::LOGGER.info "Compiling '#{catalog}' output to '#{output}'" +Google::LOGGER.info \ + "Generating types: #{types_to_generate.empty? ? 'ALL' : types_to_generate}" + +api = Api::Compiler.new(File.join(catalog, 'api.yaml')).run +api.validate +pp api if ENV['COMPILER_DEBUG'] + +config = Provider::Config.parse(File.join(catalog, provider), api) +config.validate +pp config if ENV['COMPILER_DEBUG'] + +provider = config.provider.new(config, api) +provider.generate output, types_to_generate diff --git a/coverage/.last_run.json b/coverage/.last_run.json new file mode 100644 index 000000000000..05d5202b9204 --- /dev/null +++ b/coverage/.last_run.json @@ -0,0 +1,5 @@ +{ + "result": { + "covered_percent": 80.81 + } +} diff --git a/dependencies/dependency_graph.rb b/dependencies/dependency_graph.rb new file mode 100644 index 000000000000..d6bef805b02a --- /dev/null +++ b/dependencies/dependency_graph.rb @@ -0,0 +1,135 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'dependencies/graph' + +module Dependencies + # Responsible for holding all objects being used throughout the creation + # of unit tests. + # + # These objects include the object being tested and all of the needed + # dependencies to create that object. + class DependencyGraph + def initialize(datagen) + @datagen = datagen + end + + def add(object, seed, kind, extra) + if @graph.nil? + @graph = Graph.new(object, seed) + else + @graph.add_object(object, seed) + end + collect_refs(object.all_user_properties, kind, seed, extra) + end + + def each + @graph.each { |node| yield node } + end + + def map + @graph.map { |node| yield(node) } + end + + private + + def collect_refs(props, kind, seed, extra) + props = select_properties(props, kind, extra) + collect_refs_child(props, seed) + end + + def collect_refs_child(props, seed) + props.each do |p| + emit_manifest_assign(p, seed) + end + end + + # prop_name_method is a method that returns the proper name of the object + # rref_value: If true, return the value being exported by the ref'd block + # If false, return the title of the block being referenced + def emit_manifest_assign(prop, seed) + if prop.is_a?(Api::Type::Array) + emit_manifest_array(prop, seed) + elsif prop.is_a?(Api::Type::NestedObject) + emit_nested(prop, seed) + elsif prop.is_a?(Api::Type::ResourceRef) + emit_resource(prop, seed) + end + end + + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity + # prop_name_method should be a valid method on a Api::Type::* + # Typically, this will be "out_name" or "field_name" + def emit_manifest_array(prop, seed) + if prop.item_type.is_a?(Api::Type::NestedObject) + size = @datagen.object_size(prop, seed, true) + (1..size).each do |index| + collect_refs(prop.item_type.properties, :title, seed + index - 1, + ensure: 'present') + end + elsif prop.item_type.is_a?(Api::Type::ResourceRef) + size = @datagen.object_size(prop, seed, true) + (1..size).each do |index| + emit_resource(prop.item_type, (seed + index - 1) % 3) + end + end + end + # rubocop:enable Metrics/MethodLength + + def emit_nested(prop, seed) + collect_refs_child(prop.properties, seed) + end + + def emit_resource(prop, seed) + # % 3 because only 3 different network test data files per Resource + @graph.add_ref(prop, seed % 3) + + # Recurse through referenced object for more resourcerefs + # Don't recurse on resourceref of same type. + return if prop.resource_ref == prop.__resource + + # When building resourcerefs in manifests/catalogs, we use the + # smallest set of properties possible. When looking recursively, we + # should only look through this smallest set of properties. Otherwise, + # recursive resource refs may be created that do not get referenced. + collect_refs(prop.resource_ref.required_properties, + :name, seed, {}) + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + def select_properties(props, kind, extra) + props = props.reject(&:output) + + props = props.select { |p| p.required || p.name == 'name' } \ + if (extra.key?(:ensure) && extra[:ensure].to_sym == :absent) || + (extra.key?(:action) && extra[:action] == ':delete') + + if kind == :resource + props.select { |p| p.required || p.name == 'name' } + elsif kind == :title + props.reject { |p| p.name == 'name' } + else + props + end + end + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/ClassLength + end +end diff --git a/dependencies/graph.rb b/dependencies/graph.rb new file mode 100644 index 000000000000..3cfa40669215 --- /dev/null +++ b/dependencies/graph.rb @@ -0,0 +1,131 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Dependencies + # Represents a full graph made up of Api::Resources + class Graph + attr_reader :node + attr_reader :pointers + attr_reader :start + + def initialize(object, seed) + @node = ItemListNode.new(object, seed, nil) + @pointers = { object.name => @node } + @start = object.name + end + + # Adds an object to the graph. This object is represented by a ResourceRef + # property + def add_ref(prop, seed) + raise 'Only ResourceRef may be inserted' unless \ + prop.is_a? Api::Type::ResourceRef + + # Add each item to its proper node. + referenced_by = prop.__resource.name + object = prop.resource_ref.name + + if @pointers.key?(object) + @pointers[object].add(prop.resource_ref, seed, prop) + else + node = ItemListNode.new(prop.resource_ref, seed, prop) + @pointers[object] = node + end + + # Ensure that a link exists between this object and the object + # that referenced it. + @pointers[referenced_by].add_child(object) + end + + def add_object(object, seed) + @pointers[object.name].add(object, seed, nil) + end + + def each + sort.each do |obj_type| + @pointers[obj_type].objects.each do |obj| + yield obj + end + end + end + + def map + sort.map do |obj_type| + @pointers[obj_type].objects.map do |obj| + yield(obj) + end + end + end + + private + + # Depth first search through grid + # See https://en.wikipedia.org/wiki/Topological_sorting for algorithm + # (listed under Depth First Search) + def sort + markers = @pointers.keys.product(['not visited']).to_h + sorted = [] + visit(markers.keys[0], sorted, markers) until markers.empty? + sorted.reverse + end + + def visit(node, sorted, markers) + return unless markers.key?(node) + return if markers[node] != 'not visited' + markers[node] = 'temp' + @pointers[node].children.each do |child| + visit(child, sorted, markers) + end + markers.delete(node) + sorted.unshift(node) + end + end + + # A list of objects of a certain type. + # This represents a single node in the graph. + class ItemListNode + attr_reader :type + attr_reader :objects + attr_reader :children + + def initialize(object, seed, parent) + @type = object.name + @objects = [] + @children = [] + + add(object, seed, parent) + end + + def add(object, seed, parent) + @objects << Item.new(object, seed, parent) + @objects = @objects.sort_by(&:seed).uniq + end + + def add_child(name) + @children << name unless @children.include? name + end + end + + # A single instance of an object with parent property and seed value. + # No knowledge of the graph is necessary when handling an Item. + class Item + attr_reader :object + attr_reader :seed + attr_reader :parent + + def initialize(object, seed, parent) + @object = object + @seed = seed + @parent = parent + end + end +end diff --git a/docs/chef.yaml.md b/docs/chef.yaml.md new file mode 100644 index 000000000000..f8067a5ae1b3 --- /dev/null +++ b/docs/chef.yaml.md @@ -0,0 +1,468 @@ +# products/{{product}}/chef.yaml + +The `chef.yaml` file contains Chef specific settings, overrides and other +customizations to build the Chef cookbook for the product. + +A `chef.yaml` file should derive from `Provider::Chef::Config` object. + +> Please note that you may find Ruby code inlined in the `chef.yaml` +> throughout this doc and in the products/*/chef.yaml. The main reason for +> that are 2 fold: +> +> 1. Chef is in Ruby. The code listed will be placed into the final product +> generated, so it *has* to be in Ruby. If your provider is targeting +> something on another language, e.g. Go or Python, those scripts will likely +> be written in Go or Python respectively. +> 2. We decided to inline the Ruby functions because they were usually small and +> managing many small files. That was an arbitrary decision, but localized to +> the Chef provider. If you write another provider you are free to do in a +> different way if you wish. +> +> You will still have to deal with a minor amount of Ruby, mostly +> [ERB (Embedded RuBy)][erb-home]. However it will be very minimal and +> restricted to helping you build the code in your target language. For example +> you can use ERB to iterate through objects, properties and other niceties +> provided by the compiler. For example: +> +> ``` +> <% +> object.properties.each do |prop| +> ... your code (or template) iterated with every property +> end +> -%> +> ``` + +Example: + + --- !ruby/object:Provider::Chef::Config + manifest: !ruby/object:Provider::Chef::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/chef-google-compute' + ... + ... + + +## Skeleton + + --- !ruby/object:Provider::Chef::Config + manifest: !ruby/object:Provider::Chef::Manifest + version: + source: + issues: + summary: + description: + depends: + - !ruby/object:Provider::Config::Requirements + - ... + operating_systems: + - !ruby/object:Provider::Config::OperatingSystem + - ... + objects: !ruby/object:Api::Resource::HashArray + : + update: + provider_helpers: + include: + - ... + overrides: + : + examples: !ruby/object:Api::Resource::HashArray + : + - .rb + - delete_.rb + - ... + files: !ruby/object:Provider::Config::Files + copy: + : + ... + compile: + : + ... + test_data: !ruby/object:Provider::Config::TestData + network: !ruby/object:Api::Resource::HashArray + : + - + - ... + style: + - !ruby/object:Provider::Config::StyleException + name: + pinpoints: + - function: + exceptions: + - ... + - class: + exceptions: + - ... + - cookbook: + exceptions: + - ... + - test: matrix > ... + exceptions: + - ... + - ... + +## Template Files + + - provider/chef/common\~operating_systems.yaml + - provider/chef/common\~copy.yaml + - provider/chef/common\~compile\~before.yaml + - provider/chef/common\~compile\~after.yaml + +When creating examples you can inlcude the credential block that defines, and +explains how to use the credentials: + + - templates/chef/example\~auth.rb.erb + +See [Compute instance.rb example][compute-instance-example] as reference. + +## Features + +TODO(nelsonjr): Get a list of features here and how they relate to the settings. + + +## Types + + +### `Api::Resource::HashArray` + +A hash array is the same as a hash with the exception that the key should match +an existing object. If the object is not defined in the `api.yaml` the compiler +will fail with error. + +This is to ensure deleted objects are not left behind and litters the +`chef.yaml` files. + + +## Settings + + +### `manifest` (`Provider::Chef::Manifest`) + +Every Chef cookbook contains a manifest, which provides the basic information +for the cookbook. This information is parsed by Chef (and Chef Supermarket) to +reason about the cookbook (name, version, etc). + +Example: + + manifest: !ruby/object:Provider::Chef::Manifest + version: '0.1.0' + ... + +#### `version` (string) + +The version of the cookbook, in [SemVer][semver-home] format. Example: + + version: '0.1.0' + +#### `source` (URL) + +The URL of the source code for the cookbook. Example: + + source: 'https://github.com/GoogleCloudPlatform/chef-google-compute' + +#### `issues` (URL) + +The URL of the issues tracker for the cookbook. Example: + + homepage: 'https://github.com/GoogleCloudPlatform/chef-google-compute/issues' + +#### `summary` (string) + +A short description of the cookbook. Example: + + summary: 'A Chef cookbook to manage Google Compute Engine resources' + +#### `description` (string) + +A longer description of the cookbook. Example: + + description: | + This cookbook provides the built-in types and services for Chef + to manage Google Cloud DNS resources, as native Chef types. + +#### `requires` (list(`Provider::Config::Requirements`)) + +A list of cookbooks that this product cookbook depends on. Example: + + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google-gauth' + versions: '< 0.2.0' + +> Note: `google-gauth` should always be listed in the required cookbooks. + +#### `operating_systems` (list(`Provider::Config::OperatingSystem`)) + +The list of operating systems (and versions) that this cookbook fully supports. +Example: + + operating_systems: + - !ruby/object:Provider::Config::OperatingSystem + name: RedHat + versions: + - '6' + - '7' + +Note that we have a file that can be included with the common operating systems +we run Chef cookbooks on. Then the definition should be: + + operating_systems: + <%= indent(include('provider/chef/common~operating_systems.yaml'), 4) %> + + +### `objects` (`Api::Resource::HashArray`) + +Provides the root of customizations for a resource defined in the `api.yaml`. + +Example ([Compute][compute-chef] `Disk` cannot be changed via API updates): + + objects: !ruby/object:Api::Resource::HashArray + Disk: + flush: raise 'Disk cannot be edited' + +#### `objects//create` (code) + +Creates a custom `create` function. Chef uses `create` when it detects the +object does not exist and user specified `ensure => present` in the manifest. + +Example: [products/compute/chef.yaml][dns-chef] + +#### `objects//delete` (code) + +Creates a custom `delete` function. Chef uses `delete` when it detects the +object exists and user specified `ensure => absent` in the manifest. + +Example: [products/compute/chef.yaml][dns-chef] + +#### `objects//flush` (code) + +Creates a custom `flush` function. Chef uses `flush` to update values for the +resources if they mismatch with the catalog. + +Example: [products/compute/chef.yaml][dns-chef] + +> Note that `flush` is called after `create` or `delete`. Usually you should not +> execute `flush` when `create` or `delete` is called. Those functions by +> default assign true to a boolean to `@created` and `@deleted` respectively. So +> a guard like this is in the default `flush` function: +> +> `return if @created || @deleted || !@dirty` + +#### `objects//resource_to_request_patch` (code) + +Code provided in this section will be added to `resource_to_request` method. +This allows performing object specific changes to the request before it is send +to the API. This is useful when there are special changes to the object being +submitted aside from the properties defined in the `api.yaml` file. + +Example ([Compute][compute-chef] `BackendService` requires that we provide the +original `fingerprint` value to avoid concurrent changes since our last read): + + objects: !ruby/object:Api::Resource::HashArray + BackendService: + resource_to_request_patch: | + unless @fetched.nil? + # Convert to pure JSON + request = JSON.parse(request.to_json) + request['fingerprint'] = @fetched['fingerprint'] + end + +#### `objects//resource_to_query` (code) + +A function that is used to filter objects returned from the API. When the API +does not provide a `GET` operation to fetch the object (e.g. fetching a VM by +name and zone), but instead it provides a `LIST` operation that returns all +objects in a group (e.g. all DNS records for a zone) we need to filter the +results to find the object user is determine to operate on. + +Example: [products/compute/chef.yaml][dns-chef] + +#### `objects//provider_helpers` + +Defines provider specific changes to the provider. + +#### `objects//provider_helpers/visible` + +Emits or suppresses specific functions from being generated in the object. This +is useful when the provider needs to have a special 'snowflake' version of the +function because the API is not straightforward. + +Functions that can be overriden: + + - unwrap_resource + - resource_to_request + - return_if_object + +Example: + + objects: !ruby/object:Api::Resource::HashArray + ResourceRecordSet: + provider_helpers: + visible: + unwrap_resource: false + +##### `objects//provider_helpers/include` (list(string)) + +Appends the files listed into the provider generated file + +Example ([Compute][compute-chef] includes a file with helper functions to be +used by the `Disk` object): + + objects: !ruby/object:Api::Resource::HashArray + Disk: + provider_helpers: + include: + - 'products/compute/helpers/provider_disk_type.rb' + +#### `objects//overrides` (hash) + +Maps a field to another name. This is useful when the field name conflicts with +a reserved platform specific keyword. For example the keyword `deprecated` is +reserved on Chef and cannot be used whereas GCE MachineType has a deprecated +field. + +Example: + + objects: !ruby/object:Api::Resource::HashArray + MachineType: + overrides: + deprecated: _deprecated + +### `tests` (`Api::Resource::HashArray`) + +Specifies extra tests to be added to the [rspec][rspec-home] unit tests. + +This will display code that should override auto-generated testing code. View +the [DNS tests][dns-chef-test] for the most comprehensive view of altered tests. + +### `examples` (`Api::Resource::HashArray`) + +Lists the file names of each example included for each object. All examples +must be located in a product's files/ directory with the prefix +'examples\~cookbook\~'. Each file will be included in the cookbook's recipes +folder with the prefix 'examples\~'. The convention is to include a +.rb file and a delete_.rb file + +Example: + + examples: !ruby/object:Api::Resource::HashArray + Instance: + - instance.rb # refers to files/examples~cookbook~instance.rb + - delete_instance.rb + + +### `files` (`Provider::Config::Files`) + +Lists files to be copied or compiled into the final product. We can add files on +a per-product basis at our whim. If the file is simply copied use the `copy` +section. If you need to execute code to build the file use the `compile` +section instead. + +Example: + + files: !ruby/object:Provider::Config::Files + copy: + libraries/google/copy_file.rb: copy_file.rb + compile: + libraries/google/object_store.rb: google/object_store.rb + libraries/chef/functions/gcompute_image_family.rb: + products/compute/files/function~image_family.rb.erb + +### `files/copy` (`Hash`) + +Lists files that should be copied directly to the cookbook without any +compilation. List the location in the cookbook as the key (relative to the cookbook +root) and the location in the code generator (relative to Magic Module root) as +the value. + +### `files/compile` (`Hash`) + +Lists files that should be compiled and then copied to the cookbook. List the +location in the cookbook as the key (relative to the cookbook root) and the +location in the code generator (relative to Magic Module root) as the value. + +### `test_data` (`Provider::Config::TestData`) + +Data files that will be used to generate canned responses from the API. During +tests we trap all API calls and return canned data to avoid the need to hit GCP +with a real request. + +This is useful to both make the tests faster and simple, as well as avoid +charges and operating setup to developers helping us improve the cookbooks. + +Example: + + test_data: + network: !ruby/object:Api::Resource::HashArray + ResourceRecordSet: + - create~name + - create~title + +May be of type Provider::Config::TestData::NONE with a reason. This means that +all test data will be automatically generated. + +Example: + + test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'Test Data will be automatically generated' + +#### `test_data/network` (`Api::Resource::HashArray`) + +A list of objects that have manually written network data files for unit tests. + +#### `test_data/network/` (list(`String`)) + +A list of test data files to be included with the cookbook. These files will be +taken from files/ and are all prefixed with 'spec\~'. They will be copied to +spec/data/network// + + +### `style` (list(`Provider::Config::StyleException`)) + +Applies [rubocop][rubocop-home] exemptions to generated filed. This is useful +when the generated file violates a Rubocop rule and we want a one-off pass +instead of disabling the rule. + +For example, if there are too many properties in an object, the +resource_to_request will have too many lines for rubocop taste. + +In that case we can whitelist the resource_to_request to be exempt without the +need to allow any other method to have the same leniency. + +Example: + + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/instance_disks.rb + pinpoints: + - function: resource_to_request + exceptions: + - Metrics/MethodLength + +### `style[]/name` (`String`) + +The filename where the exception is needed. + +### `style[]/pinpoints` (list(`Hash`)) + +List of places in the file where exceptions are needed. + +### `style[]/pinpoints[]/function` (`String`) + +The name of the function where exceptions are needed. Prefix with class name +of multiple versions of the function exist and only one needs exceptions + +### `style[]/pinpoints[]/class` (`String`) + +The name of the class where exceptions are needed. Prefix with cookbook chain +if multiple classes of the same name exist and only one needs exceptions. + +### `style[]/pinpoints[]/exceptions` (list(`String`)) + +A list of necessary Rubocop exceptions. + +[compute-chef]: ../products/compute/chef.yaml +[dns-chef]: ../products/dns/chef.yaml +[dns-chef-test]: ../products/dns/test.yaml +[compute-instance-example]: ../products/compute/files/examples~cookbook~instance.rb +[semver-home]: http://semver.org/ +[rspec-home]: http://rspec.info/ +[rubocop-home]: https://github.com/bbatsov/rubocop diff --git a/docs/philosophy.md b/docs/philosophy.md new file mode 100644 index 000000000000..5589760c2eb8 --- /dev/null +++ b/docs/philosophy.md @@ -0,0 +1,69 @@ +# Module Philosophy + +There are various processors out there that ingests a manifest and produce a +client library. You can get client libraries for Java, Ruby, .NET, etc. However +for deployment tools a library is not enough. _These tools do not care about an +abstraction layer as they are not a script language_. + +## Convergence To Desired State + +Our modules care about converging objects from any state into a final desired +state specified by the administrator. The administrator declares his intent in +a product specific DSL, and the infrastructure is supposed to make it happen. +For example consider that the administrator declared: + + gcompute_instance { 'my-middle-tier-vm': + ensure => running, + zone => 'us-central1-a', + } + +If the machine is not running, the infrastructure is supposed to make it +happen: start if stopped, resume if paused, restore if archived. Also if the +machine is not on the us-central1-a zone, it should move it there. If something +described above cannot be accomplished the tool will fail with a clear and +actionable message of why it failed and what the administrator needs to do to +correct the issue. + +## Idempotency + +Contrary to script languages declarative tools (like Chef or Puppet) specify +intent and the system's final behavior. So that means if the system is already +in the desired state nothing should happen or be changed, or attempted to be +changed, _even if the final result is the same_. + +Consider the following bash script: + + #!/bin/bash + apt-get install apache2 + cp my-template.conf /etc/httpd/conf.d/25-mysite.conf + chmod 644 /etc/httpd/conf.d/25-mysite.conf + chattr -i /etc/httpd/conf.d/25-mysite.conf + +In the simple script above we're installing Apache, copying our site +definitions and protecting the file. _Note that you cannot run that script +twice without it failing_ as chattr prevents the next cp to work. Also note that +if apache2 is already installed, although apt-get will not attempt to install +it again, the full apt-get install process will happen again. For example +catalog being updated or cleaned, block until another installation is in +progress, add installation to system log, etc. + +So if you do the following in Puppet: + + package { 'apache2': + ensure => installed, + } + + file { '/etc/httpd/conf.d/25-mysite.conf': + ensure => file, + mode => '0644', + } + + chattr::attribute_add { '/etc/httpd/conf.d/25-mysite.conf': + attribute => 'i', + require => File['/etc/httpd/conf.d/25-mysite.conf'], + } + +Puppet/Chef will not attempt to start installing apache2 if it is already there +(apt-get will not even be called). Similarly if the properties of the +configuration file are correct (mode, immutability bit, etc) Puppet/Chef will +not attempt to change anything. This leads to a cleaner execution diff --git a/docs/puppet.yaml.md b/docs/puppet.yaml.md new file mode 100644 index 000000000000..27891f241473 --- /dev/null +++ b/docs/puppet.yaml.md @@ -0,0 +1,550 @@ +# products/{{product}}/puppet.yaml + +The `puppet.yaml` file contains Puppet specific settings, overrides and other +customizations to build the Puppet module for the product. + +A `puppet.yaml` file should derive from `Provider::Puppet::Config` object. + +> Please note that you may find Ruby code inlined in the `puppet.yaml` +> throughout this doc and in the products/*/puppet.yaml. The main reason for +> that are 2 fold: +> +> 1. Puppet is in Ruby. The code listed will be placed into the final product +> generated, so it *has* to be in Ruby. If your provider is targeting +> something on another language, e.g. Go or Python, those scripts will likely +> be written in Go or Python respectively. +> 2. We decided to inline the Ruby functions because they were usually small and +> managing many small files. That was an arbitrary decision, but localized to +> the Puppet provider. If you write another provider you are free to do in a +> different way if you wish. +> +> You will still have to deal with a minor amount of Ruby, mostly +> [ERB (Embedded RuBy)][erb-home]. However it will be very minimal and +> restricted to helping you build the code in your target language. For example +> you can use ERB to iterate through objects, properties and other niceties +> provided by the compiler. For example: +> +> ``` +> <% +> object.properties.each do |prop| +> ... your code (or template) iterated with every property +> end +> -%> +> ``` + +Example: + + --- !ruby/object:Provider::Puppet::Config + manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/puppet-google-compute' + ... + ... + + +## Skeleton + + --- !ruby/object:Provider::Puppet::Config + manifest: !ruby/object:Provider::Puppet::Manifest + version: + source: + homepage: + issues: + summary: + tags: + - ... + requires: + - !ruby/object:Provider::Config::Requirements + - ... + operating_systems: + - !ruby/object:Provider::Config::OperatingSystem + - ... + objects: !ruby/object:Api::Resource::HashArray + : + create: + delete: + flush: + resource_to_request_patch: + resource_to_query: + provider_helpers: + visible: + unwrap_resource: + resource_to_request: + return_if_object: + include: + - + - ... + functions: + - !ruby/object:Provider::Puppet::Function + name: + description: + arguments: + - !ruby/object:Provider::Puppet::Function::Argument + name: + type: + description: + - ... + examples: + - ... + notes: + - ... + examples: !ruby/object:Api::Resource::HashArray + : + - .pp + - delete_.pp + - ... + files: !ruby/object:Provider::Config::Files + copy: + : + ... + compile: + : + ... + test_data: !ruby/object:Provider::Config::TestData + network: !ruby/object:Api::Resource::HashArray + : + - + - ... + style: + - !ruby/object:Provider::Config::StyleException + name: + pinpoints: + - function: + exceptions: + - ... + - module: + exceptions: + - ... + - test: matrix > ... + exceptions: + - ... + - ... + +## Template Files + + - provider/puppet/common\~operating_systems.yaml + - provider/puppet/common\~copy.yaml + - provider/puppet/common\~compile\~before.yaml + - provider/puppet/common\~compile\~after.yaml + +When creating examples you can inlcude the credential block that defines, and +explains how to use the credentials: + + - templates/puppet/examples\~credential.pp.erb + +See [Compute instance.pp example][compute-instance-example] as reference. + +## Features + +TODO(nelsonjr): Get a list of features here and how they relate to the settings. + + +## Types + + +### `Api::Resource::HashArray` + +A hash array is the same as a hash with the exception that the key should match +an existing object. If the object is not defined in the `api.yaml` the compiler +will fail with error. + +This is to ensure deleted objects are not left behind and litters the +`puppet.yaml` files. + + +## Settings + + +### `manifest` (`Provider::Puppet::Manifest`) + +Every Puppet module contains a manifest, which provides the basic information +for the module. This information is parsed by Puppet (and Puppet Forge) to +reason about the module (name, version, etc). + +Example: + + manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.1.0' + ... + +#### `version` (string) + +The version of the module, in [SemVer][semver-home] format. Example: + + version: '0.1.0' + +#### `source` (URL) + +The URL of the source code for the module. Example: + + source: 'https://github.com/GoogleCloudPlatform/puppet-google-compute' + +#### `homepage` (URL) + +The URL of the project page for the module. Example: + + homepage: 'https://github.com/GoogleCloudPlatform/puppet-google-compute' + +#### `issues` (URL) + +The URL where bugs, issues and feature requests are tracked. Example: + + issues: + 'https://github.com/GoogleCloudPlatform/puppet-google-compute/issues' + +#### `summary` (string) + +A short description of the module. Example: + + summary: 'A Puppet module to manage Google Compute Engine resources' + +#### `tags` (list(string)) + +A list of tags for the module. Used by Puppet Forge to build keyword search. +Example: + + tags: + - google + - cloud + - compute + - engine + - gce + +#### `requires` (list(`Provider::Config::Requirements`)) + +A list of modules that this product module depends on. Example: + + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google-gauth' + versions: '< 0.2.0' + +> Note: `google-gauth` should always be listed in the required modules. + +#### `operating_systems` (list(`Provider::Config::OperatingSystem`)) + +The list of operating systems (and versions) that this module fully supports. +Example: + + operating_systems: + - !ruby/object:Provider::Config::OperatingSystem + name: RedHat + versions: + - '6' + - '7' + +Note that we have a file that can be included with the common operating systems +we run Puppet modules on. Then the definition should be: + + operating_systems: + <%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> + + +### `objects` (`Api::Resource::HashArray`) + +Provides the root of customizations for a resource defined in the `api.yaml`. + +Example ([Compute][compute-puppet] `Disk` cannot be changed via API updates): + + objects: !ruby/object:Api::Resource::HashArray + Disk: + flush: raise 'Disk cannot be edited' + +#### `objects//create` (code) + +Creates a custom `create` function. Puppet uses `create` when it detects the +object does not exist and user specified `ensure => present` in the manifest. + +Example: [products/compute/puppet.yaml][dns-puppet] + +#### `objects//delete` (code) + +Creates a custom `delete` function. Puppet uses `delete` when it detects the +object exists and user specified `ensure => absent` in the manifest. + +Example: [products/compute/puppet.yaml][dns-puppet] + +#### `objects//flush` (code) + +Creates a custom `flush` function. Puppet uses `flush` to update values for the +resources if they mismatch with the catalog. + +Example: [products/compute/puppet.yaml][dns-puppet] + +> Note that `flush` is called after `create` or `delete`. Usually you should not +> execute `flush` when `create` or `delete` is called. Those functions by +> default assign true to a boolean to `@created` and `@deleted` respectively. So +> a guard like this is in the default `flush` function: +> +> `return if @created || @deleted || !@dirty` + +#### `objects//resource_to_request_patch` (code) + +Code provided in this section will be added to `resource_to_request` method. +This allows performing object specific changes to the request before it is send +to the API. This is useful when there are special changes to the object being +submitted aside from the properties defined in the `api.yaml` file. + +Example ([Compute][compute-puppet] `BackendService` requires that we provide the +original `fingerprint` value to avoid concurrent changes since our last read): + + objects: !ruby/object:Api::Resource::HashArray + BackendService: + resource_to_request_patch: | + unless @fetched.nil? + # Convert to pure JSON + request = JSON.parse(request.to_json) + request['fingerprint'] = @fetched['fingerprint'] + end + +#### `objects//resource_to_query` (code) + +A function that is used to filter objects returned from the API. When the API +does not provide a `GET` operation to fetch the object (e.g. fetching a VM by +name and zone), but instead it provides a `LIST` operation that returns all +objects in a group (e.g. all DNS records for a zone) we need to filter the +results to find the object user is determine to operate on. + +Example: [products/compute/puppet.yaml][dns-puppet] + +#### `objects//provider_helpers` + +Defines provider specific changes to the provider. + +#### `objects//provider_helpers/visible` + +Emits or suppresses specific functions from being generated in the object. This +is useful when the provider needs to have a special 'snowflake' version of the +function because the API is not straightforward. + +Functions that can be overriden: + + - unwrap_resource + - resource_to_request + - return_if_object + +Example: + + objects: !ruby/object:Api::Resource::HashArray + ResourceRecordSet: + provider_helpers: + visible: + unwrap_resource: false + +##### `objects//provider_helpers/include` (list(string)) + +Appends the files listed into the provider generated file + +Example ([Compute][compute-puppet] includes a file with helper functions to be +used by the `Disk` object): + + objects: !ruby/object:Api::Resource::HashArray + Disk: + provider_helpers: + include: + - 'products/compute/helpers/provider_disk_type.rb' + + +### `properties` (list(string)) + +> Deprecated: Properties are automatically inferred and included into the +> providers and types automatically. + + +### `tests` (`Api::Resource::HashArray`) + +Specifies extra tests to be added to the [rspec][rspec-home] unit tests. + +This will display code that should override auto-generated testing code. View +the [DNS tests][dns-puppet-test] for the most comprehensive view of altered tests. + + +### `functions` (list(`Provider::Puppet::Function`)) + +Describes Puppet client functions provided by this module. + +> To add the same documentation to the function code use the provider function +> `emit_function_doc(name)`. +> For example `emit_function_doc('gcompute_image_family')`. + +TODO(nelsonjr): Make the `functions:` actually generate the function and remove +the requirement above about files:compile: + +> Note that this definition does not include the actual function code (yet). It +> generates the documentation and README updates, but it is not involved in the +> function generation, so it is still required to have the function listed in +> the files:compile: section. + +#### `name` (string) + +The name of the function + +#### `description` (string) + +The description of the function + +#### `arguments` (list(`Provider::Puppet::Function::Argument`)) + +The arguments that the function requires. Example: + + arguments: + - !ruby/object:Provider::Puppet::Function::Argument + name: image_family + type: Api::Type::String + description: 'the name of the family, e.g. ubuntu-1604-lts' + +#### `examples` (list(string)) + +Examples to be included in the documentation + + examples: + - gcompute_image_family('ubuntu-1604-lts', 'ubuntu-os-cloud') + - gcompute_image_family('my-web-server', 'my-project') + +#### `notes` (string) + +Additional notes to be added at the end of the documentation. + + notes: | + Note: In the case of private images, your credentials will need to have + the proper permissions to access the image. + + To get a list of supported families you can use the gcloud utility: + + gcloud compute images list + + +### `examples` (`Api::Resource::HashArray`) + +Lists the file names of each example included for each object. All examples +must be located in a product's files/ directory. Each file will be included in +the examples folder with the prefix 'examples\~'. The convention is to include a +.pp file and a delete_.pp file + +Example: + + examples: !ruby/object:Api::Resource::HashArray + Instance: + - instance.rb # refers to files/examples~cookb + - delete_instance.rb + +### `files` (`Provider::Config::Files`) + +Lists files to be copied or compiled into the final product. We can add files on +a per-product basis at our whim. If the file is simply copied use the `copy` +section. If you need to execute code to build the file use the `compile` +section instead. + +Example: + + files: !ruby/object:Provider::Config::Files + copy: + lib/google/copy_file.rb: copy_file.rb + compile: + lib/google/object_store.rb: google/object_store.rb + lib/puppet/functions/gcompute_image_family.rb: + products/compute/files/function~image_family.rb.erb + +### `files/copy` (`Hash`) + +Lists files that should be copied directly to the module without any +compilation. List the location in the module as the key (relative to the module +root) and the location in the code generator (relative to Magic Module root) as +the value. + +### `files/compile` (`Hash`) + +Lists files that should be compiled and then copied to the module. List the +location in the module as the key (relative to the module root) and the +location in the code generator (relative to Magic Module root) as the value. + + +### `test_data` (`Provider::Config::TestData`) + +Data files that will be used to generate canned responses from the API. During +tests we trap all API calls and return canned data to avoid the need to hit GCP +with a real request. + +This is useful to both make the tests faster and simple, as well as avoid +charges and operating setup to developers helping us improve the modules. + +Example: + + test_data: + network: !ruby/object:Api::Resource::HashArray + ResourceRecordSet: + - create~name + - create~title + +May be of type Provider::Config::TestData::NONE with a reason. This means that +all test data will be automatically generated. + +Example: + + test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'Test Data will be automatically generated' + +#### `test_data/network` (`Api::Resource::HashArray`) + +A list of objects that have manually written network data files for unit tests. + +#### `test_data/network/` (list(`String`)) + +A list of test data files to be included with the module. These files will be +taken from files/ and are all prefixed with 'spec\~'. They will be copied to +spec/data/network// + +### `style` (list(`Provider::Config::StyleException`)) + +Applies [rubocop][rubocop-home] exemptions to generated filed. This is useful +when the generated file violates a Rubocop rule and we want a one-off pass +instead of disabling the rule. + +For example, if there are too many properties in an object, the +resource_to_request will have too many lines for rubocop taste. + +In that case we can whitelist the resource_to_request to be exempt without the +need to allow any other method to have the same leniency. + +Example: + + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/instance_disks.rb + pinpoints: + - function: resource_to_request + exceptions: + - Metrics/MethodLength + +### `style[]/name` (`String`) + +The filename where the exception is needed. + +### `style[]/pinpoints` (list(`Hash`)) + +List of places in the file where exceptions are needed. + +### `style[]/pinpoints[]/function` (`String`) + +The name of the function where exceptions are needed. Prefix with class name +of multiple versions of the function exist and only one needs exceptions + +### `style[]/pinpoints[]/class` (`String`) + +The name of the class where exceptions are needed. Prefix with module chain +if multiple classes of the same name exist and only one needs exceptions. + +### `style[]/pinpoints[]/exceptions` (list(`String`)) + +A list of necessary Rubocop exceptions. + +TODO(nelsonjr): Add a link to each example pointing to the other `puppet.yaml` +files that exist in the products. + + +[compute-puppet]: ../products/compute/puppet.yaml +[dns-puppet]: ../products/dns/puppet.yaml +[dns-puppet-test]: ../products/dns/test.yaml +[compute-instance-example]: ../products/compute/files/examples~instance.pp +[semver-home]: http://semver.org/ +[rspec-home]: http://rspec.info/ +[rubocop-home]: https://github.com/bbatsov/rubocop diff --git a/google/hash_utils.rb b/google/hash_utils.rb new file mode 100644 index 000000000000..4c759e6d4f27 --- /dev/null +++ b/google/hash_utils.rb @@ -0,0 +1,55 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/string_utils' + +module Google + # Helper class to process and mutate hashes. + class HashUtils + # Converts all keys to symbols + def self.camelize_keys(source) + result = source.clone + # rubocop:disable Performance/HashEachMethods + result.keys.each do |k| + result[Google::StringUtils.camelize(k.to_s)] = result.delete(k) + end + # rubocop:enable Performance/HashEachMethods + result + end + + def self.symbolize_keys(source) + result = source.clone + # rubocop:disable Performance/HashEachMethods + result.keys.each do |k| + result[Google::StringUtils.symbolize(k)] = result.delete(k) + end + # rubocop:enable Performance/HashEachMethods + result + end + + # Allows fetching objects within a tree path. + def self.navigate(source, path, default = nil) + key = path.take(1)[0] + path = path.drop(1) + return default unless source.key?(key) + result = source.fetch(key) + return HashUtils.navigate(result, path, default) unless path.empty? + return result if path.empty? + end + + # Converts a path in the form a/b/c/d into %w(a b c d) + def self.path2navigate(path) + "%w[#{path.split('/').join(' ')}]" + end + end +end diff --git a/google/integer_utils.rb b/google/integer_utils.rb new file mode 100644 index 000000000000..2e89c177ad5d --- /dev/null +++ b/google/integer_utils.rb @@ -0,0 +1,28 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Google + # A helper to convert integer to 1_000_222_333 Ruby underscore notation. + class IntegerUtils + def self.underscore(value) + return '0' if value.zero? + result = [] + while value > 0 + value, part = value.divmod(1000) + result << format('%03d', part) if value > 0 + result << part if value.zero? + end + result.reverse.join('_') + end + end +end diff --git a/google/logger.rb b/google/logger.rb new file mode 100644 index 000000000000..d0140189ffb8 --- /dev/null +++ b/google/logger.rb @@ -0,0 +1,32 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'logger' +require 'singleton' + +# A convenience singleton of Logger for using within the application +class Logger + include Singleton + + # rubocop:disable Style/ClassVars + @@old_initialize = Logger.instance_method :initialize + + def initialize + @@old_initialize.bind(self).call(STDERR) unless ENV['GOOGLE_LOGGER'] == '0' + end + # rubocop:enable Style/ClassVars +end + +module Google + LOGGER = Logger.instance +end diff --git a/google/object_store.rb b/google/object_store.rb new file mode 100644 index 000000000000..ca346da47660 --- /dev/null +++ b/google/object_store.rb @@ -0,0 +1,47 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Google + # A helper class to aggregate all resources when multiple providers produce + # them at the same time. This singleton is used to collect different + # "flavors" of resources, managed by different providers, when the client + # does not care which provider created it. + # + # For example when authenticating requests, as long as the resource honors + # the "authenticate" API which provider, or which parameters it needed to + # create itself matters little to the consumer of the authenticator. + class ObjectStore + include Singleton + + attr_reader :resources + + def initialize + @resources = {} + end + + # Adds an instance of the resource to the global collection. + def add(type, resource) + Puppet.debug "Registering resource #{resource}" + @resources[type] = [] if @resources[type].nil? + @resources[type] << resource + end + + def [](type) + if @resources[type].nil? + [] + else + @resources[type] + end + end + end +end diff --git a/google/string_utils.rb b/google/string_utils.rb new file mode 100644 index 000000000000..cd4b1805dfe6 --- /dev/null +++ b/google/string_utils.rb @@ -0,0 +1,45 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Google + # Helper class to process and mutate strings. + class StringUtils + # Converts string from underscore to camel case + def self.camelize(source, style = :lower) + camelized = source.gsub(/_(.)/, &:upcase).delete('_') + case style + when :lower + camelized[0] = camelized[0].downcase + when :upper + camelized[0] = camelized[0].upcase + else + raise "Unknown camel case style: #{style}" + end + camelized + end + + # Converts string from camel case to underscore + def self.underscore(source) + source.gsub(/::/, '/') + .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') + .gsub(/([a-z\d])([A-Z])/, '\1_\2') + .tr('-', '_') + .tr('.', '_') + .downcase + end + + def self.symbolize(key) + key.to_sym unless key.nil? + end + end +end diff --git a/google/yaml_validator.rb b/google/yaml_validator.rb new file mode 100644 index 000000000000..bf0b0e3f8e08 --- /dev/null +++ b/google/yaml_validator.rb @@ -0,0 +1,122 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/logger' +require 'yaml' + +module Google + # A helper class to validate contents coming from YAML files. + class YamlValidator + class << self + def parse(content) + # TODO(nelsonjr): Allow specifying which symbols to restrict it further. + # But it requires inspecting all configuration files for symbol sources, + # such as Enum values. Leaving it as a nice-to-have for the future. + YAML.safe_load(content, allowed_classes) + end + + def allowed_classes + ObjectSpace.each_object(Class).select do |klass| + klass < Google::YamlValidator + end.concat([Time, Symbol]) + end + end + + def validate + Google::LOGGER.info "Validating #{self.class} '#{@name}'" + check_extraneous_properties + end + + def set_variable(value, property) + Google::LOGGER.info "Setting variable of #{value} to #{self}" + ensure_property_does_not_exist property + instance_variable_set("@#{property}", value) + end + + private + + def check_types(objects, type) + return if objects.nil? + objects.each do |object| + log_check_type object + check_type object, type + end + end + + def check_type(name, object, type) + if type == :boolean + return unless [TrueClass, FalseClass].find_index(object.class).nil? + elsif type.is_a? ::Array + return unless type.find_index(object.class).nil? + elsif object.is_a?(type) + return + end + raise "Property '#{name}' is '#{object.class}' instead of '#{type}'" + end + + def log_check_type(object) + if object.respond_to?(:name) + Google::LOGGER.info "Checking object #{object.name}" + else + Google::LOGGER.info "Checking object #{object}" + end + end + + def check_property(property, type = nil) + check_property_value property, instance_variable_get("@#{property}"), type + end + + def check_property_value(property, prop_value, type) + Google::LOGGER.info "Checking '#{property}' on #{object_display_name}" + raise "Missing '#{property}' on #{object_display_name}" if prop_value.nil? + check_type property, prop_value, type unless type.nil? + prop_value.validate if prop_value.is_a?(Api::Object) + end + + def check_extraneous_properties + instance_variables.each do |variable| + var_name = variable.id2name[1..-1] + next if var_name.start_with?('__') + Google::LOGGER.info "Validating '#{var_name}' on #{object_display_name}" + raise "Extraneous variable '#{var_name}' in #{object_display_name}" \ + unless methods.include?(var_name.intern) + end + end + + def check_property_list(name, obj_list, type = nil) + if obj_list.nil? + Google::LOGGER.info "No next level @ #{object_display_name}: #{name}" + else + Google::LOGGER.info \ + "Checking next level for #{object_display_name}: #{name}" + obj_list.each { |o| check_property_value "#{name}:item", o, type } + end + end + + def set_variables(objects, property) + return if objects.nil? + objects.each do |object| + object.set_variable(self, property) if object.respond_to?(:set_variable) + end + end + + def ensure_property_does_not_exist(property) + raise "Conflict of property '#{property}' for object '#{self}'" \ + unless instance_variable_get("@#{property}").nil? + end + + def object_display_name + "#{@name}<#{self.class.name}>" + end + end +end diff --git a/mm_logo.png b/mm_logo.png new file mode 100644 index 000000000000..8989a7161313 Binary files /dev/null and b/mm_logo.png differ diff --git a/products/_bundle/api.yaml b/products/_bundle/api.yaml new file mode 100644 index 000000000000..c1984ed310f0 --- /dev/null +++ b/products/_bundle/api.yaml @@ -0,0 +1,16 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Bundle +name: Google Cloud Platform +prefix: cloud diff --git a/products/_bundle/chef.yaml b/products/_bundle/chef.yaml new file mode 100644 index 000000000000..f0e079f15715 --- /dev/null +++ b/products/_bundle/chef.yaml @@ -0,0 +1,39 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::ChefBundle::Config +manifest: !ruby/object:Provider::ChefBundle::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/chef-google' + issues: 'https://github.com/GoogleCloudPlatform/chef-google/issues' + summary: 'Bundle cookbook to install all Chef GCP cookbooks.' + description: | + This cookbook provides the built-in types and services for Chef to manage + Google Cloud Engine resources. + depends: + - !ruby/object:Provider::Config::Requirements + name: 'google-gauth' + versions: '< 0.2.0' + operating_systems: +<%= indent(include('provider/chef/common~operating_systems.yaml'), 4) %> +files: !ruby/object:Provider::Config::Files + copy: + 'LICENSE': 'templates/LICENSE' + compile: + 'README.md': 'products/_bundle/templates/chef/README.md.erb' + '.gitignore': 'templates/dot~gitignore' + 'Berksfile': 'templates/chef/Berksfile.erb' + 'chefignore': 'templates/chef/chefignore.erb' + 'metadata.rb': 'templates/chef/metadata.rb.erb' + 'CONTRIBUTING.md': 'templates/CONTRIBUTING.md.erb' + # CONTRIBUTING.md has to be the last file to be compiled. diff --git a/products/_bundle/puppet.yaml b/products/_bundle/puppet.yaml new file mode 100644 index 000000000000..35c318bb4404 --- /dev/null +++ b/products/_bundle/puppet.yaml @@ -0,0 +1,75 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::PuppetBundle::Config +manifest: !ruby/object:Provider::PuppetBundle::Manifest + version: '0.2.2' + source: 'https://github.com/GoogleCloudPlatform/puppet-google' + homepage: 'https://github.com/GoogleCloudPlatform/puppet-google' + issues: + 'https://github.com/GoogleCloudPlatform/puppet-google/issues' + summary: 'Puppet module bundle all Google Cloud Platform modules' + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google/glogging' + versions: '>= 0.1.0 < 0.2.0' + tags: + - google + - cloud + - compute + - gcp + - gce + operating_systems: +<%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> +files: !ruby/object:Provider::Config::Files + copy: + 'LICENSE': 'templates/LICENSE' + compile: + 'README.md': 'products/_bundle/templates/puppet/README.md.erb' + 'metadata.json': 'templates/puppet/metadata.json.erb' + 'CONTRIBUTING.md': 'templates/CONTRIBUTING.md.erb' + # CONTRIBUTING.md has to be the last file to be compiled. +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.2.2' + date: 2017-10-10T13:30:00-0700 + fixes: + - Added documentation on how to upgrade pre-release versions + - !ruby/object:Provider::Config::Changelog + version: '0.2.1' + date: 2017-10-10T13:30:00-0700 + features: + - Support for Bolt on `gstorage` + - !ruby/object:Provider::Config::Changelog + version: '0.2.0' + date: 2017-10-10T06:00:00-0700 + features: + - Various improvements on `gcompute` module + - Support for Bolt on `gcompute` + - Support for Bolt on `gsql` + - !ruby/object:Provider::Config::Changelog + version: '0.1.1' + date: 2017-08-22T13:24:39-0700 + fixes: + - 'Fix typo on product name' + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-08-22T09:00:00-0700 + general: 'Initial release' + features: + - Support for Google Compute Engine + - Support for Google Container Engine + - Support for Google Cloud DNS + - Support for Google Cloud SQL + - Support for Google Cloud Storage + - Support for Google Authentication diff --git a/products/_bundle/templates/chef/README.md.erb b/products/_bundle/templates/chef/README.md.erb new file mode 100644 index 000000000000..f0dafd0e075b --- /dev/null +++ b/products/_bundle/templates/chef/README.md.erb @@ -0,0 +1,115 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> + +Google Cloud Platform for Chef +-------------------------------- + +This cookbook installs all Google cookbooks for Chef to allow managing +[Google Cloud Platform][gcp] resources from your Chef environment + +#### Table of Contents + +1. [Description](#description) +2. [Setup](#setup) +3. [Supported Google Cloud Platform Products][supported-products] +4. [Summary of Supported Products Types / Providers][supported-types] +5. [Supported Operating Systems](#supported-operating-systems) + + +## Description + +This cookbook is a convenience to install all Google Cloud Platform cookbooks +for Chef with a single command. You can install them individually if you wish +as well. + + +## Setup + +To install this cookbook, insert the following into your Berksfile. + + cookbook 'google-cloud', '~> <%= manifest.version -%>' + +## Supported Google Cloud Platform Products + +The `google/cloud` cookbook installs the following cookbooks automatically: + +<%= + lines(indent(products.keys.map do |p| + "- [#{p.name}](##{p.name.downcase.tr(' ', '-')})" + end, 2)) +-%> + - [Google Authentication](#google-authentication) + + +## Summary of Supported Products Types / Providers + +Below you can find a summary of each supported type and a brief description of +its intended behavior. For full details about each provider, properties, +parameters, usage and examples please visit its respective Chef cookbook project +page. + +<% products.each do |product, config| -%> + +### <%= product.name -%> + +Detailed information can be found at the +[google-<%= product.prefix -%>][] project home page. +The list below is a summary of the supported types by the cookbook: + +<% product.objects.reject(&:exclude).each do |object| -%> + +- `<%= object.out_name -%>` +<%= wrap_field(object.description, 2) -%> + +<% end # objects.each -%> +<% end # products.each -%> + +### Google Authentication + +This cookbook provides the types to authenticate with Google Cloud Platform. +When executing operations on Google Cloud Platform, e.g. creating a virtual +machine, a SQL database, etc., you need to be authenticated to be able to carry +on with the request. All Google Cloud Platform cookbooks use an unified +authentication mechanism, provided by this cookbook. + +For examples, installation and usage visit the [google-gauth][] cookbook home +page. + + +## Supported Operating Systems + + + +<% products.map do |product, config| -%> +<% os_list = config.manifest.operating_systems -%> + + + + +<% end # products.map -%> +
ProductOperating Systems
<%= product.name -%> +<%= indent(os_list.map(&:all_versions).join("
\n"), 6) %> +
+ + +[supported-products]: #supported-google-cloud-platform-products +[supported-types]: #summary-of-supported-products-types--providers +<%= + lines(products.map do |product, config| + "[google-#{product.prefix}]: #{config.manifest.source}" + end) +-%> +[google-gauth]: https://github.com/GoogleCloudPlatform/chef-google-auth/blob/master/README.md +[gcp]: https://cloud.google.com diff --git a/products/_bundle/templates/puppet/README.md.erb b/products/_bundle/templates/puppet/README.md.erb new file mode 100644 index 000000000000..3cbe85ae1165 --- /dev/null +++ b/products/_bundle/templates/puppet/README.md.erb @@ -0,0 +1,157 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> + +Google Cloud Platform for Puppet +-------------------------------- + +<%= lines(['[![Puppet Forge](', + "http://img.shields.io/puppetforge/v/google/#{@api.prefix}.svg", + ")](https://forge.puppetlabs.com/google/#{@api.prefix})"].join) -%> + +This module installs all Google modules for Puppet to allow managing +[Google Cloud Platform][gcp] resources from your Puppet environment + +#### Table of Contents + +1. [Module Description](#module-description) +2. Setup + * [Installing](#setup) + * [Upgrading pre-release versions](#upgrading-pre_release-versions) +3. [Supported Google Cloud Platform Products][supported-products] +4. [Summary of Supported Products Types / Providers][supported-types] +5. [Supported Operating Systems](#supported-operating-systems) + + +## Module Description + +This module is a convenience to install all Google Cloud Platform modules for +Puppet with a single command. You can install them individually if you wish as +well. + + +## Setup + +To install this module on your Puppet Master (or Puppet Client/Agent), use the +Puppet module installer: + + puppet module install google/cloud + +### Upgrading pre-release versions + +If you already have pre-release modules installed (pre-release = modules with +version < 1.0.0) you need to force update of the modules, as they are +specifically strict about pre-release versions. You can do that by using the +`--force` argument on upgrade: + + puppet module upgrade --force google/cloud --version= + +You can find the latest version on [google/cloud][google-cloud-forge] Forge +page. If you omit the version you may get an intermedia upgrade, so please +re-run until you get the latest version. + +Watch out for missing dependencies when you do that. If there are any missing +dependencies they will have a red `invalid` after the name of the module that +requires upgrade as well. The safest way is to upgrade all Google modules at +once (or remove them and do `puppet module install` again). Optionally you can +run a script to upgrade all Google modules, like this: + + puppet module list | awk '{print $2}' | grep '^google-' \ + | xargs -I{} puppet module upgrade --force {} + +## Supported Google Cloud Platform Products + +The `google/cloud` module installs the following modules automatically: + +<%= + lines(indent(products.keys.map do |p| + "- [#{p.name}](##{p.name.downcase.tr(' ', '-')})" + end, 2)) +-%> + - [Google Authentication](#google-authentication) + + +## Summary of Supported Products Types / Providers + +Below you can find a summary of each supported type and a brief description of +its intended behavior. For full details about each provider, properties, +parameters, usage and examples please visit its respective Puppet module project +page. + +<% products.each do |product, config| -%> + +### <%= product.name -%> + +Detailed information can be found at the +[google-<%= product.prefix -%>][] project home page. +The list below is a summary of the supported types by the module: + +<% product.objects.reject(&:exclude).each do |object| -%> + +- `<%= object.out_name -%>` +<%= wrap_field(object.description, 2) -%> + +<% end # objects.each -%> +<% unless config.bolt_tasks.nil? -%> + +#### Bolt Tasks + +<% config.bolt_tasks.each do |task| -%> + +- `<%= task.target_file -%>` +<%= wrap_field(task.description_display, 2) -%> + +<% end # config.bolt_tasks.each -%> + +<% end # config.bolt_tasks.nil? -%> +<% end # products.each -%> + +### Google Authentication + +This module provides the types to authenticate with Google Cloud Platform. When +executing operations on Google Cloud Platform, e.g. creating a virtual machine, +a SQL database, etc., you need to be authenticated to be able to carry on with +the request. All Google Cloud Platform modules use an unified authentication +mechanism, provided by this module. + +For examples, installation and usage visit the [google-gauth][] module home +page. + + +## Supported Operating Systems + + + +<% products.map do |product, config| -%> +<% os_list = config.manifest.operating_systems -%> + + + + +<% end # products.map -%> +
ProductOperating Systems
<%= product.name -%> +<%= indent(os_list.map(&:all_versions).join("
\n"), 6) %> +
+ + +[supported-products]: #supported-google-cloud-platform-products +[supported-types]: #summary-of-supported-products-types--providers +<%= + lines(products.map do |product, config| + "[google-#{product.prefix}]: #{config.manifest.homepage}" + end) +-%> +[google-gauth]: https://github.com/GoogleCloudPlatform/puppet-google-auth/blob/master/README.md +[gcp]: https://cloud.google.com +[google-cloud-forge]: https://forge.puppet.com/google/cloud diff --git a/products/compute/api.yaml b/products/compute/api.yaml new file mode 100644 index 000000000000..f2c32f3a1fdb --- /dev/null +++ b/products/compute/api.yaml @@ -0,0 +1,3042 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Make all Zone and Region resource ref + +--- !ruby/object:Api::Product +name: Google Compute Engine +prefix: gcompute +base_url: https://www.googleapis.com/compute/v1/ +scopes: + - https://www.googleapis.com/auth/compute +objects: + - !ruby/object:Api::Resource + name: 'Address' + kind: 'compute#address' + base_url: projects/{{project}}/regions/{{region}}/addresses + exports: + - !ruby/object:Api::Type::FetchedExternal + name: address + description: | + Represents an Address resource. + + Each virtual machine instance has an ephemeral internal IP address and, + optionally, an external IP address. To communicate between instances on + the same network, you can use an instance's internal IP address. To + communicate with the Internet and instances outside of the same network, + you must specify the instance's external IP address. + + Internal IP addresses are ephemeral and only belong to an instance for + the lifetime of the instance; if the instance is deleted and recreated, + the instance is assigned a new internal IP address, either by Compute + Engine or by you. External IP addresses can be either ephemeral or + static. +<%= indent(compile('templates/regional_async.yaml.erb'), 4) %> + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'name' + description: | + URL of the region where the regional address resides. + This field is not applicable to global addresses. + required: true + properties: + - !ruby/object:Api::Type::String + name: 'address' + description: | + The static external IP address represented by this + resource. Only IPv4 is supported. + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: 'Name of the resource.' + # TODO(alexstephen): Add in status with exclude attribute. What does this + # mean? + - !ruby/object:Api::Type::Array + name: 'users' + description: 'The URLs of the resources that are using this address.' + item_type: Api::Type::String + output: true + - !ruby/object:Api::Resource + name: 'BackendBucket' + kind: 'compute#backendBucket' + base_url: projects/{{project}}/global/backendBuckets + description: | + Backend buckets allow you to use Google Cloud Storage buckets with HTTP(S) + load balancing. + + An HTTP(S) load balancing can direct traffic to specified URLs to a + backend bucket rather than a backend service. It can send requests for + static content to a Cloud Storage bucket and requests for dynamic content + a virtual machine instance. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::String + name: 'bucketName' + description: 'Cloud Storage bucket name.' + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional textual description of the resource; provided by the + client when the resource is created. + - !ruby/object:Api::Type::Boolean + name: 'enableCdn' + description: 'If true, enable Cloud CDN for this BackendBucket.' + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'Unique identifier for the resource.' + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and + match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + the first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the + last character, which cannot be a dash. + - !ruby/object:Api::Resource + name: 'BackendService' + kind: 'compute#backendService' + base_url: projects/{{project}}/global/backendServices + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + Creates a BackendService resource in the specified project using the data + included in the request. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Integer + name: 'affinityCookieTtlSec' + description: | + Lifetime of cookies in seconds if session_affinity is + GENERATED_COOKIE. If set to 0, the cookie is non-persistent and lasts + only until the end of the browser session (or equivalent). The + maximum allowed value for TTL is one day. + + When the load balancing scheme is INTERNAL, this field is not used. + - !ruby/object:Api::Type::Array + name: 'backends' + description: | + The list of backends that serve this BackendService. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::Enum + name: 'balancingMode' + description: | + Specifies the balancing mode for this backend. + + For global HTTP(S) or TCP/SSL load balancing, the default is + UTILIZATION. Valid values are UTILIZATION, RATE (for HTTP(S)) + and CONNECTION (for TCP/SSL). + + This cannot be used for internal load balancing. + values: + - :UTILIZATION + - :RATE + - :CONNECTION + - !ruby/object:Api::Type::Double + name: 'capacityScaler' + description: | + A multiplier applied to the group's maximum servicing capacity + (based on UTILIZATION, RATE or CONNECTION). + + Default value is 1, which means the group will serve up to 100% + of its configured capacity (depending on balancingMode). A + setting of 0 means the group is completely drained, offering + 0% of its available Capacity. Valid range is [0.0,1.0]. + + This cannot be used for internal load balancing. + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. + Provide this property when you create the resource. + - !ruby/object:Api::Type::ResourceRef + name: 'group' + resource: 'InstanceGroup' + imports: 'selfLink' + description: | + This instance group defines the list of instances that serve + traffic. Member virtual machine instances from each instance + group must live in the same zone as the instance group itself. + + No two backends in a backend service are allowed to use same + Instance Group resource. + + When the BackendService has load balancing scheme INTERNAL, the + instance group must be in a zone within the same region as the + BackendService. + - !ruby/object:Api::Type::Integer + name: 'maxConnections' + description: | + The max number of simultaneous connections for the group. Can + be used with either CONNECTION or UTILIZATION balancing modes. + + For CONNECTION mode, either maxConnections or + maxConnectionsPerInstance must be set. + + This cannot be used for internal load balancing. + - !ruby/object:Api::Type::Integer + name: 'maxConnectionsPerInstance' + description: | + The max number of simultaneous connections that a single + backend instance can handle. This is used to calculate the + capacity of the group. Can be used in either CONNECTION or + UTILIZATION balancing modes. + + For CONNECTION mode, either maxConnections or + maxConnectionsPerInstance must be set. + + This cannot be used for internal load balancing. + - !ruby/object:Api::Type::Integer + name: 'maxRate' + description: | + The max requests per second (RPS) of the group. + + Can be used with either RATE or UTILIZATION balancing modes, + but required if RATE mode. For RATE mode, either maxRate or + maxRatePerInstance must be set. + + This cannot be used for internal load balancing. + - !ruby/object:Api::Type::Double + name: 'maxRatePerInstance' + description: | + The max requests per second (RPS) that a single backend + instance can handle. This is used to calculate the capacity of + the group. Can be used in either balancing mode. For RATE mode, + either maxRate or maxRatePerInstance must be set. + + This cannot be used for internal load balancing. + - !ruby/object:Api::Type::Double + name: 'maxUtilization' + description: | + Used when balancingMode is UTILIZATION. This ratio defines the + CPU utilization target for the group. The default is 0.8. Valid + range is [0.0, 1.0]. + + This cannot be used for internal load balancing. + - !ruby/object:Api::Type::NestedObject + name: 'cdnPolicy' + description: 'Cloud CDN configuration for this BackendService.' + properties: + - !ruby/object:Api::Type::NestedObject + name: 'cacheKeyPolicy' + description: 'The CacheKeyPolicy for this CdnPolicy.' + properties: + - !ruby/object:Api::Type::Boolean + name: 'includeHost' + description: | + If true requests to different hosts will be cached separately. + - !ruby/object:Api::Type::Boolean + name: 'includeProtocol' + description: | + If true, http and https requests will be cached separately. + - !ruby/object:Api::Type::Boolean + name: 'includeQueryString' + description: | + If true, include query string parameters in the cache key + according to query_string_whitelist and + query_string_blacklist. If neither is set, the entire query + string will be included. + + If false, the query string will be excluded from the cache + key entirely. + - !ruby/object:Api::Type::Array + name: 'queryStringBlacklist' + description: | + Names of query string parameters to exclude in cache keys. + + All other parameters will be included. Either specify + query_string_whitelist or query_string_blacklist, not both. + '&' and '=' will be percent encoded and not treated as + delimiters. + item_type: Api::Type::String + - !ruby/object:Api::Type::Array + name: 'queryStringWhitelist' + description: | + Names of query string parameters to include in cache keys. + + All other parameters will be excluded. Either specify + query_string_whitelist or query_string_blacklist, not both. + '&' and '=' will be percent encoded and not treated as + delimiters. + item_type: Api::Type::String + - !ruby/object:Api::Type::NestedObject + name: 'connectionDraining' + description: 'Settings for connection draining' + properties: + - !ruby/object:Api::Type::Integer + name: 'drainingTimeoutSec' + description: | + Time for which instance will be drained (not accept new + connections, but still work to finish started). + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + - !ruby/object:Api::Type::Boolean + name: 'enableCDN' + description: | + If true, enable Cloud CDN for this BackendService. + + When the load balancing scheme is INTERNAL, this field is not used. + # 'fingerprint' is not suitable for resource convergence. + # TODO(nelsonjr): Limitation: healthChecks can point to various distinct + # object types and there's no way to differentiate them right now. + # Investigate if there is a way to enforce this on the client side as it + # depends on the value of load balancing being internal or external. + # TODO(nelsonjr): Make 'healthChecks' into a single object as the API + # cannot take 2+ anyway. + - !ruby/object:Api::Type::Array + name: 'healthChecks' + description: | + The list of URLs to the HttpHealthCheck or HttpsHealthCheck resource + for health checking this BackendService. Currently at most one health + check can be specified, and a health check is required. + + For internal load balancing, a URL to a HealthCheck resource must be + specified instead. + item_type: Api::Type::String + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + # 'loadBalancingScheme' is undefined: string? enum? ref? http://b/63215956 + # | - !ruby/object:Api::Type::??? + # | name: 'loadBalancingScheme' + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + # 'port' is deprecated + - !ruby/object:Api::Type::String + name: 'portName' + description: | + Name of backend port. The same name should appear in the instance + groups referenced by this service. Required when the load balancing + scheme is EXTERNAL. + + When the load balancing scheme is INTERNAL, this field is not used. + - !ruby/object:Api::Type::Enum + name: 'protocol' + description: | + The protocol this BackendService uses to communicate with backends. + Possible values are HTTP, HTTPS, TCP, and SSL. The default is HTTP. + + For internal load balancing, the possible values are TCP and UDP, and + the default is TCP. + values: + - :HTTP + - :HTTPS + - :TCP + - :SSL + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'selfLink' + description: | + The region where the regional backend service resides. + This field is not applicable to global backend services. + - !ruby/object:Api::Type::Enum + name: 'sessionAffinity' + description: | + Type of session affinity to use. The default is NONE. + + When the load balancing scheme is EXTERNAL, can be NONE, CLIENT_IP, or + GENERATED_COOKIE. + + When the load balancing scheme is INTERNAL, can be NONE, CLIENT_IP, + CLIENT_IP_PROTO, or CLIENT_IP_PORT_PROTO. + + When the protocol is UDP, this field is not used. + values: + - :NONE + - :CLIENT_IP + - :GENERATED_COOKIE + - :CLIENT_IP_PROTO + - :CLIENT_IP_PORT_PROTO + - !ruby/object:Api::Type::Integer + name: 'timeoutSec' + description: | + How many seconds to wait for the backend before considering it a + failed request. Default is 30 seconds. Valid range is [1, 86400]. + - !ruby/object:Api::Resource + name: 'DiskType' + kind: 'compute#diskType' + base_url: projects/{{project}}/zones/{{zone}}/diskTypes + # TODO(nelsonjr): Search all documentation for references of using URL (like + # the description below) and replace with the proper reference to the + # corresponding type. + description: | + Represents a DiskType resource. A DiskType resource represents the type + of disk to use, such as a pd-ssd or pd-standard. To reference a disk + type, use the disk type's full or partial URL. + # TODO(nelsonjr): Temporarily make DiskType virtual so no tests gets + # triggered for create. Implement support for read only objects, and delete + # the virtual tag + # | readonly: true + virtual: true + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'zone' + resource: 'Zone' + imports: 'name' + description: 'A reference to the zone where the disk type resides.' + required: true + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::Integer + name: 'defaultDiskSizeGb' + description: 'Server-defined default disk size in GB.' + output: true + - !ruby/object:Api::Type::Time + name: 'deprecated.deleted' + description: | + An optional RFC3339 timestamp on or after which the deprecation state + of this resource will be changed to DELETED. + output: true + - !ruby/object:Api::Type::Time + name: 'deprecated.deprecated' + description: | + An optional RFC3339 timestamp on or after which the deprecation state + of this resource will be changed to DEPRECATED. + output: true + - !ruby/object:Api::Type::Time + name: 'deprecated.obsolete' + description: | + An optional RFC3339 timestamp on or after which the deprecation state + of this resource will be changed to OBSOLETE. + output: true + - !ruby/object:Api::Type::String + name: 'deprecated.replacement' + description: | + The URL of the suggested replacement for a deprecated resource. The + suggested replacement resource must be the same kind of resource as + the deprecated resource. + output: true + - !ruby/object:Api::Type::Enum + name: 'deprecated.state' + description: | + The deprecation state of this resource. This can be DEPRECATED, + OBSOLETE, or DELETED. Operations which create a new resource using a + DEPRECATED resource will return successfully, but with a warning + indicating the deprecated resource and recommending its replacement. + Operations which use OBSOLETE or DELETED resources will be rejected + and result in an error. + values: + - :DEPRECATED + - :OBSOLETE + - :DELETED + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + output: true + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: 'Name of the resource.' + - !ruby/object:Api::Type::String + name: 'validDiskSize' + description: | + An optional textual description of the valid disk size, such as + "10GB-10TB". + output: true + - !ruby/object:Api::Resource + name: 'Disk' + # TODO(nelsonjr): Implement disk special actions as defined in the API: + # - resize + # - createSnapshot + # - setLabels + kind: 'compute#disk' + base_url: projects/{{project}}/zones/{{zone}}/disks + exports: + - 'name' + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + Persistent disks are durable storage devices that function similarly to + the physical disks in a desktop or a server. Compute Engine manages the + hardware behind these devices to ensure data redundancy and optimize + performance for you. Persistent disks are available as either standard + hard disk drives (HDD) or solid-state drives (SSD). + + Persistent disks are located independently from your virtual machine + instances, so you can detach or move persistent disks to keep your data + even after you delete your instances. Persistent disk performance scales + automatically with size, so you can resize your existing persistent disks + or add more persistent disks to an instance to meet your performance and + storage space requirements. + + Add a persistent disk to your instance when you need reliable and + affordable storage with consistent performance characteristics. +<%= indent(compile('templates/zonal_async.yaml.erb'), 4) %> + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'zone' + resource: 'Zone' + imports: 'name' + description: 'A reference to the zone where the disk resides.' + required: true + - !ruby/object:Api::Type::NestedObject + name: 'diskEncryptionKey' + description: | + Encrypts the disk using a customer-supplied encryption key. + + After you encrypt a disk with a customer-supplied key, you must + provide the same key if you use the disk later (e.g. to create a disk + snapshot or an image, or to attach the disk to a virtual machine). + + Customer-supplied encryption keys do not protect access to metadata of + the disk. + + If you do not provide an encryption key when creating the disk, then + the disk will be encrypted using an automatically generated key and + you do not need to provide a key to use the disk later. + properties: + - !ruby/object:Api::Type::String + name: 'rawKey' + description: | + Specifies a 256-bit customer-supplied encryption key, encoded in + RFC 4648 base64 to either encrypt or decrypt this resource. + - !ruby/object:Api::Type::String + name: 'sha256' + description: | + The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied + encryption key that protects this resource. + output: true + input: true + - !ruby/object:Api::Type::NestedObject + name: 'sourceImageEncryptionKey' + description: | + The customer-supplied encryption key of the source image. Required if + the source image is protected by a customer-supplied encryption key. + properties: + - !ruby/object:Api::Type::String + name: 'rawKey' + description: | + Specifies a 256-bit customer-supplied encryption key, encoded in + RFC 4648 base64 to either encrypt or decrypt this resource. + - !ruby/object:Api::Type::String + name: 'sha256' + description: | + The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied + encryption key that protects this resource. + output: true + input: true + - !ruby/object:Api::Type::String + name: 'sourceImageId' + description: | + The ID value of the image used to create this disk. This value + identifies the exact image that was used to create this persistent + disk. For example, if you created the persistent disk from an image + that was later deleted and recreated under the same name, the source + image ID would identify the exact version of the image that was used. + output: true + # TODO(nelsonjr): Investigate if 'sourceSnapshot' is suitable to be a + # resource reference, as it takes 'global/' snapshots. + - !ruby/object:Api::Type::String + name: 'sourceSnapshot' + description: | + The source snapshot used to create this disk. You can provide this as + a partial or full URL to the resource. For example, the following are + valid values: + + * https://www.googleapis.com/compute/v1/projects/project/global/ + snapshots/snapshot + * projects/project/global/snapshots/snapshot + * global/snapshots/snapshot + - !ruby/object:Api::Type::NestedObject + name: 'sourceSnapshotEncryptionKey' + description: | + The customer-supplied encryption key of the source snapshot. Required + if the source snapshot is protected by a customer-supplied encryption + key. + properties: + - !ruby/object:Api::Type::String + name: 'rawKey' + description: | + Specifies a 256-bit customer-supplied encryption key, encoded in + RFC 4648 base64 to either encrypt or decrypt this resource. + - !ruby/object:Api::Type::String + name: 'sha256' + description: | + The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied + encryption key that protects this resource. + output: true + input: true + - !ruby/object:Api::Type::String + name: 'sourceSnapshotId' + description: | + The unique ID of the snapshot used to create this disk. This value + identifies the exact snapshot that was used to create this persistent + disk. For example, if you created the persistent disk from a snapshot + that was later deleted and recreated under the same name, the source + snapshot ID would identify the exact version of the snapshot that was + used. + output: true + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::Time + name: 'lastAttachTimestamp' + description: 'Last attach timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::Time + name: 'lastDetachTimestamp' + description: 'Last dettach timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::Array + name: 'licenses' + description: 'Any applicable publicly visible licenses.' + item_type: Api::Type::String + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + # 'options' fields is internal use only. do not expose. + # | - !ruby/object:Api::Type::String + # | name: 'options' + # | description: 'Internal use only' + - !ruby/object:Api::Type::Integer + name: 'sizeGb' + description: | + Size of the persistent disk, specified in GB. You can specify this + field when creating a persistent disk using the sourceImage or + sourceSnapshot parameter, or specify it alone to create an empty + persistent disk. + + If you specify this field along with sourceImage or sourceSnapshot, + the value of sizeGb must not be less than the size of the sourceImage + or the size of the snapshot. + - !ruby/object:Api::Type::String + name: 'sourceImage' + description: | + The source image used to create this disk. If the source image is + deleted, this field will not be set. + + To create a disk with one of the public operating system images, + specify the image by its family name. For example, specify + family/debian-8 to use the latest Debian 8 image: + + projects/debian-cloud/global/images/family/debian-8 + + Alternatively, use a specific version of a public operating system + image: + + projects/debian-cloud/global/images/debian-8-jessie-vYYYYMMDD + + To create a disk with a private image that you created, specify the + image name in the following format: + + global/images/my-private-image + + You can also specify a private image by its image family, which + returns the latest version of the image in that family. Replace the + image name with family/family-name: + + global/images/family/my-private-family + input: true + # TODO(nelsonjr): Make this a resource refefence + - !ruby/object:Api::Type::String + name: 'type' + description: | + URL of the disk type resource describing which disk type to use to + create the disk. Provide this when creating the disk. + output: true + - !ruby/object:Api::Type::Array + name: 'users' + description: | + Links to the users of the disk (attached instances) in form: + project/zones/zone/instances/instance + item_type: Api::Type::String + output: true + - !ruby/object:Api::Resource + name: 'Firewall' + kind: 'compute#firewall' + base_url: projects/{{project}}/global/firewalls + description: | + Each network has its own firewall controlling access to and from the + instances. + + All traffic to instances, even from other instances, is blocked by the + firewall unless firewall rules are created to allow it. + + The default network has automatically created firewall rules that are + shown in default firewall rules. No manually created network has + automatically created firewall rules except for a default "allow" rule for + outgoing traffic and a default "deny" for incoming traffic. For all + networks except the default network, you must create any firewall rules + you need. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + # TODO(nelsonjr): [nice to have] Make the format here simpler to use, in + # the form of # 22/tcp, [12345-23456]/tcp. It requires a conversion + # function to the # final JSON format expected by the API for this + # proposal to work. + - !ruby/object:Api::Type::Array + name: 'allowed' + description: | + The list of ALLOW rules specified by this firewall. Each rule + specifies a protocol and port-range tuple that describes a permitted + connection. + item_type: !ruby/object:Api::Type::NestedObject + properties: + # IPProtocol has to be string, instead of Enum because user can + # specify the protocol by number as well. + - !ruby/object:Api::Type::String + name: 'ip_protocol' + description: | + The IP protocol to which this rule applies. The protocol type is + required when creating a firewall rule. This value can either be + one of the following well known protocol strings (tcp, udp, + icmp, esp, ah, sctp), or the IP protocol number. + field: 'IPProtocol' + required: true + - !ruby/object:Api::Type::Array + item_type: Api::Type::String + name: 'ports' + description: | + An optional list of ports to which this rule applies. This field + is only applicable for UDP or TCP protocol. Each entry must be + either an integer or a range. If not specified, this rule + applies to connections through any port. + + Example inputs include: ["22"], ["80","443"], and + ["12345-12349"]. + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + # TODO(nelsonjr): Convert this to a ResourceRef. + - !ruby/object:Api::Type::String + name: 'network' + description: | + URL of the network resource for this firewall rule. If not specified + when creating a firewall rule, the default network is used: + + global/networks/default + + If you choose to specify this property, you can specify the network as + a full or partial URL. For example, the following are all valid URLs: + + https://www.googleapis.com/compute/v1/projects/myproject/global/ + networks/my-network + projects/myproject/global/networks/my-network + global/networks/default + - !ruby/object:Api::Type::Array + name: 'sourceRanges' + description: | + If source ranges are specified, the firewall will apply only to + traffic that has source IP address in these ranges. These ranges must + be expressed in CIDR format. One or both of sourceRanges and + sourceTags may be set. If both properties are set, the firewall will + apply to traffic that has source IP address within sourceRanges OR the + source IP that belongs to a tag listed in the sourceTags property. The + connection does not need to match both properties for the firewall to + apply. Only IPv4 is supported. + item_type: Api::Type::String + - !ruby/object:Api::Type::Array + name: 'sourceTags' + description: | + If source tags are specified, the firewall will apply only to traffic + with source IP that belongs to a tag listed in source tags. Source + tags cannot be used to control traffic to an instance's external IP + address. Because tags are associated with an instance, not an IP + address. One or both of sourceRanges and sourceTags may be set. If + both properties are set, the firewall will apply to traffic that has + source IP address within sourceRanges OR the source IP that belongs to + a tag listed in the sourceTags property. The connection does not need + to match both properties for the firewall to apply. + item_type: Api::Type::String + - !ruby/object:Api::Type::Array + name: 'targetTags' + description: | + A list of instance tags indicating sets of instances located in the + network that may make network connections as specified in allowed[]. + If no targetTags are specified, the firewall rule applies to all + instances on the specified network. + item_type: Api::Type::String + - !ruby/object:Api::Resource + name: 'ForwardingRule' + kind: 'compute#forwardingRule' + base_url: projects/{{project}}/regions/{{region}}/forwardingRules + description: | + A ForwardingRule resource. A ForwardingRule resource specifies which pool + of target virtual machines to forward a packet to if it matches the given + [IPAddress, IPProtocol, portRange] tuple. +<%= indent(compile('templates/regional_async.yaml.erb'), 4) %> + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'name' + description: | + A reference to the region where the regional forwarding rule resides. + This field is not applicable to global forwarding rules. + required: true + properties: +<%= indent(compile('products/compute/forwarding_rule_properties.yaml'), 6) %> + - !ruby/object:Api::Type::ResourceRef + name: 'target' + resource: 'TargetPool' + imports: 'selfLink' + description: | + A reference to a TargetPool resource to receive the matched traffic. + For regional forwarding rules, this target must live in the same + region as the forwarding rule. For global forwarding rules, this + target must be a global load balancing resource. The forwarded traffic + must be of a type appropriate to the target object. + + This field is not used for internal load balancing. + - !ruby/object:Api::Resource + name: 'GlobalAddress' + kind: 'compute#address' + base_url: projects/{{project}}/global/addresses + description: | + Represents a Global Address resource. Global addresses are used for + HTTP(S) load balancing. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::String + name: 'address' + description: | + The static external IP address represented by this resource. + output: true + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. + Provide this property when you create the resource. + - !ruby/object:Api::Type::Integer + name: 'id' + description: | + The unique identifier for the resource. This identifier is defined by + the server. + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and + match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + the first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'selfLink' + description: | + A reference to the region where the regional address resides. This + field is not applicable to global addresses. + output: true + # status is not useful for state convergence + # users[] is not useful for state convergence + - !ruby/object:Api::Resource + name: 'GlobalForwardingRule' + kind: 'compute#forwardingRule' + base_url: projects/{{project}}/global/forwardingRules + description: | + Represents a GlobalForwardingRule resource. Global forwarding rules are + used to forward traffic to the correct load balancer for HTTP load + balancing. Global forwarding rules can only be used for HTTP load + balancing. + + For more information, see + https://cloud.google.com/compute/docs/load-balancing/http/ +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: +<%= indent(compile('products/compute/forwarding_rule_properties.yaml'), 6) %> + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'name' + description: | + A reference to the region where the regional forwarding rule resides. + This field is not applicable to global forwarding rules. + output: true + # TODO(nelsonjr): Create a new type, WeakResourceRef that could accept + # various different objects, instead of one, to enable these scenarios + # where a field can receive multiple types (ditto in other places, look + # for "mysterious" Api::Type::String based objects) + - !ruby/object:Api::Type::String + name: 'target' + description: | + This target must be a global load balancing resource. The forwarded + traffic must be of a type appropriate to the target object. + + Valid types: HTTP_PROXY, HTTPS_PROXY, SSL_PROXY, TCP_PROXY + - !ruby/object:Api::Resource + name: 'HttpHealthCheck' + kind: 'compute#httpHealthCheck' + base_url: projects/{{project}}/global/httpHealthChecks + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + An HttpHealthCheck resource. This resource defines a template for how + individual VMs should be checked for health, via HTTP. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Integer + name: 'checkIntervalSec' + description: | + How often (in seconds) to send a health check. The default value is 5 + seconds. + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + - !ruby/object:Api::Type::Integer + name: 'healthyThreshold' + description: | + A so-far unhealthy instance will be marked healthy after this many + consecutive successes. The default value is 2. + - !ruby/object:Api::Type::String + name: 'host' + description: | + The value of the host header in the HTTP health check request. If + left empty (default value), the public IP on behalf of which this + health check is performed will be used. + - !ruby/object:Api::Type::Integer + name: 'id' + description: | + The unique identifier for the resource. This identifier is defined by + the server. + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and + match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + the first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the + last character, which cannot be a dash. + - !ruby/object:Api::Type::Integer + name: 'port' + description: | + The TCP port number for the HTTP health check request. + The default value is 80. + - !ruby/object:Api::Type::String + name: 'requestPath' + description: | + The request path of the HTTP health check request. + The default value is /. + - !ruby/object:Api::Type::Integer + name: 'timeoutSec' + description: | + How long (in seconds) to wait before claiming failure. + The default value is 5 seconds. It is invalid for timeoutSec to have + greater value than checkIntervalSec. + - !ruby/object:Api::Type::Integer + name: 'unhealthyThreshold' + description: | + A so-far healthy instance will be marked unhealthy after this many + consecutive failures. The default value is 2. + - !ruby/object:Api::Resource + name: 'HttpsHealthCheck' + kind: 'compute#httpsHealthCheck' + base_url: projects/{{project}}/global/httpsHealthChecks + description: | + An HttpsHealthCheck resource. This resource defines a template for how + individual VMs should be checked for health, via HTTPS. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Integer + name: 'checkIntervalSec' + description: | + How often (in seconds) to send a health check. The default value is 5 + seconds. + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + - !ruby/object:Api::Type::Integer + name: 'healthyThreshold' + description: | + A so-far unhealthy instance will be marked healthy after this many + consecutive successes. The default value is 2. + - !ruby/object:Api::Type::String + name: 'host' + description: | + The value of the host header in the HTTPS health check request. If + left empty (default value), the public IP on behalf of which this + health check is performed will be used. + - !ruby/object:Api::Type::Integer + name: 'id' + description: | + The unique identifier for the resource. This identifier is defined by + the server. + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and + match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + the first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the + last character, which cannot be a dash. + - !ruby/object:Api::Type::Integer + name: 'port' + description: | + The TCP port number for the HTTPS health check request. + The default value is 80. + - !ruby/object:Api::Type::String + name: 'requestPath' + description: | + The request path of the HTTPS health check request. + The default value is /. + - !ruby/object:Api::Type::Integer + name: 'timeoutSec' + description: | + How long (in seconds) to wait before claiming failure. + The default value is 5 seconds. It is invalid for timeoutSec to have + greater value than checkIntervalSec. + - !ruby/object:Api::Type::Integer + name: 'unhealthyThreshold' + description: | + A so-far healthy instance will be marked unhealthy after this many + consecutive failures. The default value is 2. + - !ruby/object:Api::Resource + name: 'HealthCheck' + kind: 'compute#healthCheck' + base_url: projects/{{project}}/global/healthChecks + description: + An HealthCheck resource. This resource defines a template for how + individual virtual machines should be checked for health, via one of the + supported protocols. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Integer + name: 'checkIntervalSec' + description: | + How often (in seconds) to send a health check. The default value is 5 + seconds. + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + - !ruby/object:Api::Type::Integer + name: 'healthyThreshold' + description: | + A so-far unhealthy instance will be marked healthy after this many + consecutive successes. The default value is 2. + - !ruby/object:Api::Type::Integer + name: 'id' + description: | + The unique identifier for the resource. This identifier is defined by + the server. + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and + match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + the first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the + last character, which cannot be a dash. + - !ruby/object:Api::Type::Integer + name: 'timeoutSec' + description: | + How long (in seconds) to wait before claiming failure. + The default value is 5 seconds. It is invalid for timeoutSec to have + greater value than checkIntervalSec. + - !ruby/object:Api::Type::Integer + name: 'unhealthyThreshold' + description: | + A so-far healthy instance will be marked unhealthy after this many + consecutive failures. The default value is 2. + - !ruby/object:Api::Type::Enum + name: 'type' + description: | + Specifies the type of the healthCheck, either TCP, SSL, HTTP or + HTTPS. If not specified, the default is TCP. Exactly one of the + protocol-specific health check field must be specified, which must + match type field. + values: + - :TCP + - :SSL + - :HTTP + - !ruby/object:Api::Type::NestedObject + name: 'httpHealthCheck' + properties: +<%= + protocol = 'HTTP' + port = 80 + indent(compile('products/compute/healthcheck_protocol_props.yaml.erb'), 12) +%> + - !ruby/object:Api::Type::NestedObject + name: 'httpsHealthCheck' + properties: +<%= + protocol = 'HTTPS' + port = 443 + indent(compile('products/compute/healthcheck_protocol_props.yaml.erb'), 12) +%> + - !ruby/object:Api::Type::NestedObject + name: 'tcpHealthCheck' + properties: +<%= + protocol = 'TCP' + indent(compile('products/compute/healthcheck_protocol_props.yaml.erb'), 12) +%> + - !ruby/object:Api::Type::NestedObject + name: 'sslHealthCheck' + properties: +<%= + protocol = 'SSL' + indent(compile('products/compute/healthcheck_protocol_props.yaml.erb'), 12) +%> + - !ruby/object:Api::Resource + name: 'InstanceTemplate' + kind: 'compute#instanceTemplate' + base_url: projects/{{project}}/global/instanceTemplates + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + Defines an Instance Template resource that provides configuration settings + for your virtual machine instances. Instance templates are not tied to the + lifetime of an instance and can be used and reused as to deploy virtual + machines. You can also use different templates to create different virtual + machine configurations. Instance templates are required when you create a + managed instance group. + + Tip: Disks should be set to autoDelete=true + so that leftover disks are not left behind on machine deletion. +<% type = 'InstanceTemplate' -%> +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + transport: !ruby/object:Api::Resource::Transport + encoder: encode_request + decoder: decode_response + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + - !ruby/object:Api::Type::Integer + name: 'id' + description: | + The unique identifier for the resource. This identifier + is defined by the server. + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. The name is 1-63 characters long + and complies with RFC1035. + required: true + - !ruby/object:Api::Type::NestedObject + name: 'properties' + description: 'The instance properties for this instance template.' + properties: + - !ruby/object:Api::Type::Boolean + name: 'canIpForward' + description: | + Enables instances created based on this template to send packets + with source IP addresses other than their own and receive packets + with destination IP addresses other than their own. If these + instances will be used as an IP gateway or it will be set as the + next-hop in a Route resource, specify true. If unsure, leave this + set to false. + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional text description for the instances that are created + from this instance template. +<%= indent(compile('products/compute/instance_disks.yaml'), 10) %> + # This machineType seems to be the shortname. + # This is because machineType selfLinks are zone specific. + - !ruby/object:Api::Type::ResourceRef + name: 'machineType' + description: | + Reference to a gcompute_machine_type resource. + # InstanceTemplates take a name. Instances take a self-link + required: true + resource: 'MachineType' + imports: 'name' +<%= indent(compile('products/compute/instance_metadata.yaml'), 10) %> +<%= indent(compile('products/compute/instance_guestaccelerators.yaml'), 10) %> +<%= + indent(compile('products/compute/instance_networkinterfaces.yaml'), 10) +%> +<%= indent(compile('products/compute/instance_scheduling.yaml'), 10) %> +<%= indent(compile('products/compute/instance_serviceaccounts.yaml'), 10) %> +<%= indent(compile('products/compute/instance_tags.yaml'), 10) %> + - !ruby/object:Api::Resource + name: 'License' + kind: 'compute#license' + base_url: /projects/{{project}}/global/licenses + virtual: true + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + A License resource represents a software license. Licenses are used to + track software usage in images, persistent disks, snapshots, and virtual + machine instances. + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. The name is 1-63 characters long + and complies with RFC1035. + output: true + - !ruby/object:Api::Type::Boolean + name: 'chargesUseFee' + description: | + If true, the customer will be charged license fee for + running software that contains this license on an instance. + output: true + - !ruby/object:Api::Resource + name: 'Image' + kind: 'compute#image' + base_url: projects/{{project}}/global/images + description: | + Represents an Image resource. + + Google Compute Engine uses operating system images to create the root + persistent disks for your instances. You specify an image when you create + an instance. Images contain a boot loader, an operating system, and a + root file system. Linux operating system images are also capable of + running containers on Compute Engine. + + Images can be either public or custom. + + Public images are provided and maintained by Google, open-source + communities, and third-party vendors. By default, all projects have + access to these images and can use them to create instances. Custom + images are available only to your project. You can create a custom image + from root persistent disks and other images. Then, use the custom image + to create an instance. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Integer + name: 'archiveSizeBytes' + description: | + Size of the image tar.gz archive stored in Google Cloud Storage (in + bytes). + output: true + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::NestedObject + name: 'deprecated' + description: The deprecation status associated with this image. + output: true + properties: + - !ruby/object:Api::Type::Time + name: 'deleted' + description: | + An optional RFC3339 timestamp on or after which the state of this + resource is intended to change to DELETED. This is only + informational and the status will not change unless the client + explicitly changes it. + - !ruby/object:Api::Type::Time + name: 'deprecated' + description: | + An optional RFC3339 timestamp on or after which the state of this + resource is intended to change to DEPRECATED. This is only + informational and the status will not change unless the client + explicitly changes it. + - !ruby/object:Api::Type::Time + name: 'obsolete' + description: | + An optional RFC3339 timestamp on or after which the state of this + resource is intended to change to OBSOLETE. This is only + informational and the status will not change unless the client + explicitly changes it. + - !ruby/object:Api::Type::String + name: 'replacement' + description: | + The URL of the suggested replacement for a deprecated resource. + The suggested replacement resource must be the same kind of + resource as the deprecated resource. + - !ruby/object:Api::Type::Enum + name: 'state' + description: | + The deprecation state of this resource. This can be DEPRECATED, + OBSOLETE, or DELETED. Operations which create a new resource + using a DEPRECATED resource will return successfully, but with a + warning indicating the deprecated resource and recommending its + replacement. Operations which use OBSOLETE or DELETED resources + will be rejected and result in an error. + values: + - :DEPRECATED + - :OBSOLETE + - :DELETED + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + - !ruby/object:Api::Type::Integer + name: 'diskSizeGb' + description: | + Size of the image when restored onto a persistent disk (in GB). + # TODO(alexstephen): Build family support. + # Families use a different API + - !ruby/object:Api::Type::String + name: 'family' + description: | + The name of the image family to which this image belongs. You can + create disks by specifying an image family instead of a specific + image name. The image family always returns its latest image that is + not deprecated. The name of the image family must comply with + RFC1035. + - !ruby/object:Api::Type::Array + name: 'guestOsFeatures' + description: | + A list of features to enable on the guest OS. Applicable for + bootable images only. Currently, only one feature can be enabled, + VIRTIO_SCSI_MULTIQUEUE, which allows each virtual CPU to have its + own queue. For Windows images, you can only enable + VIRTIO_SCSI_MULTIQUEUE on images with driver version 1.2.0.1621 or + higher. Linux images with kernel versions 3.17 and higher will + support VIRTIO_SCSI_MULTIQUEUE. + + For new Windows images, the server might also populate this field + with the value WINDOWS, to indicate that this is a Windows image. + This value is purely informational and does not enable or disable + any features. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::Enum + name: 'type' + description: | + The type of supported feature. Currenty only + VIRTIO_SCSI_MULTIQUEUE is supported. For newer Windows images, + the server might also populate this property with the value + WINDOWS to indicate that this is a Windows image. This value is + purely informational and does not enable or disable any + features. + values: + - :VIRTIO_SCSI_MULTIQUEUE + - !ruby/object:Api::Type::Integer + name: 'id' + description: | + The unique identifier for the resource. This identifier + is defined by the server. + output: true + - !ruby/object:Api::Type::NestedObject + name: 'imageEncryptionKey' + description: | + Encrypts the image using a customer-supplied encryption key. + + After you encrypt an image with a customer-supplied key, you must + provide the same key if you use the image later (e.g. to create a + disk from the image) + properties: + - !ruby/object:Api::Type::String + name: 'rawKey' + description: | + Specifies a 256-bit customer-supplied encryption key, encoded in + RFC 4648 base64 to either encrypt or decrypt this resource. + - !ruby/object:Api::Type::String + name: 'sha256' + description: | + The RFC 4648 base64 encoded SHA-256 hash of the + customer-supplied encryption key that protects this resource. + output: true + # TODO(alexstephen): Change to ResourceRef with array support + - !ruby/object:Api::Type::Array + name: 'licenses' + description: Any applicable license URI. + item_type: Api::Type::String + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource; provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and + match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + the first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the + last character, which cannot be a dash. + - !ruby/object:Api::Type::NestedObject + name: 'rawDisk' + description: The parameters of the raw disk image. + properties: + - !ruby/object:Api::Type::Enum + name: 'containerType' + description: | + The format used to encode and transmit the block device, which + should be TAR. This is just a container and transmission format + and not a runtime format. Provided by the client when the disk + image is created. + values: + - :TAR + - !ruby/object:Api::Type::String + name: 'sha1Checksum' + description: | + An optional SHA1 checksum of the disk image before unpackaging. + This is provided by the client when the disk image is created. + # TODO(alexstephen): Figure out cross-module ResourceRefs + - !ruby/object:Api::Type::String + name: 'source' + description: | + The full Google Cloud Storage URL where disk storage is stored + You must provide either this property or the sourceDisk property + but not both. + - !ruby/object:Api::Type::ResourceRef + name: 'sourceDisk' + description: | + Refers to a gcompute_disk object + You must provide either this property or the + rawDisk.source property but not both to create an image. + resource: 'Disk' + imports: 'selfLink' + - !ruby/object:Api::Type::NestedObject + name: 'sourceDiskEncryptionKey' + description: | + The customer-supplied encryption key of the source disk. Required if + the source disk is protected by a customer-supplied encryption key. + properties: + - !ruby/object:Api::Type::String + name: 'rawKey' + description: | + Specifies a 256-bit customer-supplied encryption key, encoded in + RFC 4648 base64 to either encrypt or decrypt this resource. + - !ruby/object:Api::Type::String + name: 'sha256' + description: | + The RFC 4648 base64 encoded SHA-256 hash of the + customer-supplied encryption key that protects this resource. + output: true + - !ruby/object:Api::Type::String + name: 'sourceDiskId' + description: | + The ID value of the disk used to create this image. This value may + be used to determine whether the image was taken from the current + or a previous instance of a given disk name. + - !ruby/object:Api::Type::Enum + name: 'sourceType' + description: | + The type of the image used to create this disk. The default and + only value is RAW + values: + - :RAW + # State is not applicable for state convergence. + - !ruby/object:Api::Resource + name: 'Instance' + kind: 'compute#instance' + base_url: projects/{{project}}/zones/{{zone}}/instances + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + An instance is a virtual machine (VM) hosted on Google's infrastructure. +<% type = 'Instance' -%> +<%= indent(compile('templates/zonal_async.yaml.erb'), 4) %> + transport: !ruby/object:Api::Resource::Transport + encoder: encode_request + decoder: decode_response + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'zone' + resource: 'Zone' + imports: 'name' + description: 'A reference to the zone where the machine resides.' + required: true + properties: + - !ruby/object:Api::Type::Boolean + name: 'canIpForward' + description: | + Allows this instance to send and receive packets with non-matching + destination or source IPs. This is required if you plan to use this + instance to forward routes. + - !ruby/object:Api::Type::String + name: 'cpuPlatform' + description: The CPU platform used by this instance. + output: true + - !ruby/object:Api::Type::String + name: 'creationTimestamp' + description: Creation timestamp in RFC3339 text format. + output: true +<%= indent(compile('products/compute/instance_disks.yaml'), 6) %> +<%= indent(compile('products/compute/instance_guestaccelerators.yaml'), 6) %> + - !ruby/object:Api::Type::Integer + name: 'id' + description: | + The unique identifier for the resource. This identifier is defined by + the server. + output: true + # TODO(alexstephen): Investigate creating a bytes type + # TOOO(alexstephen): Add label support that uses the setLabels function. + - !ruby/object:Api::Type::String + name: 'labelFingerprint' + description: | + A fingerprint for this request, which is essentially a hash of the + metadata's contents and used for optimistic locking. The fingerprint + is initially generated by Compute Engine and changes after every + request to modify or update metadata. You must always provide an + up-to-date fingerprint hash in order to update or change metadata. +<%= indent(compile('products/compute/instance_metadata.yaml'), 6) %> + - !ruby/object:Api::Type::ResourceRef + name: 'machineType' + resource: 'MachineType' + imports: 'selfLink' + description: 'A reference to a machine type which defines VM kind.' + # TODO(alexstephen): Add metatdata + - !ruby/object:Api::Type::String + name: 'minCpuPlatform' + description: | + Specifies a minimum CPU platform for the VM instance. Applicable + values are the friendly names of CPU platforms + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name of the resource, provided by the client when initially + creating the resource. The resource name must be 1-63 characters long, + and comply with RFC1035. Specifically, the name must be 1-63 + characters long and match the regular expression + [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a + lowercase letter, and all following characters must be a dash, + lowercase letter, or digit, except the last character, which cannot + be a dash. +<%= + indent(compile('products/compute/instance_networkinterfaces.yaml'), 6) +%> +<%= indent(compile('products/compute/instance_scheduling.yaml'), 6) %> +<%= indent(compile('products/compute/instance_serviceaccounts.yaml'), 6) %> + - !ruby/object:Api::Type::String + name: 'status' + description: | + The status of the instance. One of the following values: + PROVISIONING, STAGING, RUNNING, STOPPING, SUSPENDING, SUSPENDED, + and TERMINATED. + output: true + - !ruby/object:Api::Type::String + name: 'statusMessage' + description: An optional, human-readable explanation of the status. + output: true +<%= indent(compile('products/compute/instance_tags.yaml'), 6) %> + - !ruby/object:Api::Resource + name: 'InstanceGroup' + kind: 'compute#instanceGroup' + base_url: projects/{{project}}/zones/{{zone}}/instanceGroups + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + Represents an Instance Group resource. Instance groups are self-managed + and can contain identical or different instances. Instance groups do not + use an instance template. Unlike managed instance groups, you must create + and add instances to an instance group manually. +<%= indent(compile('templates/zonal_async.yaml.erb'), 4) %> + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'zone' + resource: 'Zone' + imports: 'name' + description: 'A reference to the zone where the instance group resides.' + required: true + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + # 'fingerprint' not applicable to state convergence. + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'A unique identifier for this instance group.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name of the instance group. + The name must be 1-63 characters long, and comply with RFC1035. + - !ruby/object:Api::Type::Array + name: 'namedPorts' + description: | + Assigns a name to a port number. + For example: {name: "http", port: 80}. + + This allows the system to reference ports by the assigned name + instead of a port number. Named ports can also contain multiple + ports. + + For example: [{name: "http", port: 80},{name: "http", port: 8080}] + + Named ports apply to all instances in this instance group. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name for this named port. + The name must be 1-63 characters long, and comply with RFC1035. + - !ruby/object:Api::Type::Integer + name: 'port' + description: | + The port number, which can be a value between 1 and 65535. + - !ruby/object:Api::Type::ResourceRef + name: 'network' + resource: 'Network' + imports: 'selfLink' + description: | + The network to which all instances in the instance group belong. + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'selfLink' + description: | + The region where the instance group is located + (for regional resources). + - !ruby/object:Api::Type::ResourceRef + name: 'subnetwork' + resource: 'Subnetwork' + imports: 'selfLink' + description: | + The subnetwork to which all instances in the instance group belong. + - !ruby/object:Api::Resource + name: 'InstanceGroupManager' + kind: 'compute#instanceGroupManager' + base_url: projects/{{project}}/zones/{{zone}}/instanceGroupManagers + description: | + Creates a managed instance group using the information that you specify in + the request. After the group is created, it schedules an action to create + instances in the group using the specified instance template. This + operation is marked as DONE when the group is created even if the + instances in the group have not yet been created. You must separately + verify the status of the individual instances. + + A managed instance group can have up to 1000 VM instances per group. +<%= indent(compile('templates/zonal_async.yaml.erb'), 4) %> + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'zone' + resource: 'Zone' + imports: 'name' + description: 'The zone the managed instance group resides.' + required: true + properties: + - !ruby/object:Api::Type::String + name: 'baseInstanceName' + description: | + The base instance name to use for instances in this group. The value + must be 1-58 characters long. Instances are named by appending a + hyphen and a random four-character string to the base instance name. + The base instance name must comply with RFC1035. + required: true + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: | + The creation timestamp for this managed instance group in RFC3339 + text format. + output: true + - !ruby/object:Api::Type::NestedObject + name: 'currentActions' + description: | + The list of instance actions and the number of instances in this + managed instance group that are scheduled for each of those actions. + properties: + - !ruby/object:Api::Type::Integer + name: 'abandoning' + description: | + The total number of instances in the managed instance group that + are scheduled to be abandoned. Abandoning an instance removes it + from the managed instance group without deleting it. + output: true + - !ruby/object:Api::Type::Integer + name: 'creating' + description: | + The number of instances in the managed instance group that are + scheduled to be created or are currently being created. If the + group fails to create any of these instances, it tries again until + it creates the instance successfully. + + If you have disabled creation retries, this field will not be + populated; instead, the creatingWithoutRetries field will be + populated. + output: true + - !ruby/object:Api::Type::Integer + name: 'creatingWithoutRetries' + description: | + The number of instances that the managed instance group will + attempt to create. The group attempts to create each instance only + once. If the group fails to create any of these instances, it + decreases the group's targetSize value accordingly. + output: true + - !ruby/object:Api::Type::Integer + name: 'deleting' + description: | + The number of instances in the managed instance group that are + scheduled to be deleted or are currently being deleted. + output: true + - !ruby/object:Api::Type::Integer + name: 'none' + description: | + The number of instances in the managed instance group that are + running and have no scheduled actions. + output: true + - !ruby/object:Api::Type::Integer + name: 'recreating' + description: | + The number of instances in the managed instance group that are + scheduled to be recreated or are currently being being recreated. + Recreating an instance deletes the existing root persistent disk + and creates a new disk from the image that is defined in the + instance template. + output: true + - !ruby/object:Api::Type::Integer + name: 'refreshing' + description: | + The number of instances in the managed instance group that are + being reconfigured with properties that do not require a restart + or a recreate action. For example, setting or removing target + pools for the instance. + output: true + - !ruby/object:Api::Type::Integer + name: 'restarting' + description: | + The number of instances in the managed instance group that are + scheduled to be restarted or are currently being restarted. + output: true + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + input: true + # fingerprint ignored as it is an internal locking detail + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'A unique identifier for this resource' + output: true + - !ruby/object:Api::Type::ResourceRef + name: 'instanceGroup' + resource: 'InstanceGroup' + imports: 'selfLink' + description: 'The instance group being managed' + output: true + - !ruby/object:Api::Type::ResourceRef + name: 'instanceTemplate' + resource: 'InstanceTemplate' + imports: 'selfLink' + description: | + The instance template that is specified for this managed instance + group. The group uses this template to create all new instances in the + managed instance group. + required: true + # kind is internal transport detail + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name of the managed instance group. The name must be 1-63 + characters long, and comply with RFC1035. + required: true + # TODO(nelsonjr): Make namedPorts a NameValue(name[string], port[integer]) + - !ruby/object:Api::Type::Array + name: 'namedPorts' + description: + Named ports configured for the Instance Groups complementary to this + Instance Group Manager. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name for this named port. The name must be 1-63 characters + long, and comply with RFC1035. + - !ruby/object:Api::Type::Integer + name: 'port' + description: + The port number, which can be a value between 1 and 65535. + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'selfLink' + description: | + The region this managed instance group resides + (for regional resources). + output: true + - !ruby/object:Api::Type::Array + name: 'targetPools' + description: | + TargetPool resources to which instances in the instanceGroup field are + added. The target pools automatically apply to all of the instances in + the managed instance group. + item_type: !ruby/object:Api::Type::ResourceRef + name: 'targetPool' + description: 'The targetPool to receive managed instances.' + resource: 'TargetPool' + imports: 'selfLink' + - !ruby/object:Api::Type::Integer + name: 'targetSize' + description: | + The target number of running instances for this managed instance + group. Deleting or abandoning instances reduces this number. Resizing + the group changes this number. + - !ruby/object:Api::Resource + name: 'MachineType' + kind: 'compute#machineType' + base_url: projects/{{project}}/zones/{{zone}}/machineTypes + exports: + - 'name' + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + virtual: true + description: | + Represents a MachineType resource. Machine types determine the virtualized + hardware specifications of your virtual machine instances, such as the + amount of memory or number of virtual CPUs. + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::NestedObject + name: 'deprecated' + description: 'The deprecation status associated with this machine type.' + properties: + - !ruby/object:Api::Type::Time + name: 'deleted' + description: | + An optional RFC3339 timestamp on or after which the state of this + resource is intended to change to DELETED. This is only + informational and the status will not change unless the client + explicitly changes it. + output: true + - !ruby/object:Api::Type::Time + name: 'deprecated' + description: | + An optional RFC3339 timestamp on or after which the state of this + resource is intended to change to DEPRECATED. This is only + informational and the status will not change unless the client + explicitly changes it. + output: true + - !ruby/object:Api::Type::Time + name: 'obsolete' + description: | + An optional RFC3339 timestamp on or after which the state of this + resource is intended to change to OBSOLETE. This is only + informational and the status will not change unless the client + explicitly changes it. + output: true + - !ruby/object:Api::Type::String + name: 'replacement' + description: | + The URL of the suggested replacement for a deprecated resource. + The suggested replacement resource must be the same kind of + resource as the deprecated resource. + output: true + - !ruby/object:Api::Type::Enum + name: 'state' + description: | + The deprecation state of this resource. This can be DEPRECATED, + OBSOLETE, or DELETED. Operations which create a new resource + using a DEPRECATED resource will return successfully, but with a + warning indicating the deprecated resource and recommending its + replacement. Operations which use OBSOLETE or DELETED resources + will be rejected and result in an error. + values: + - :DEPRECATED + - :OBSOLETE + - :DELETED + output: true + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional textual description of the resource.' + output: true + - !ruby/object:Api::Type::Integer + name: 'guestCpus' + description: | + The number of virtual CPUs that are available to the instance. + output: true + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::Boolean + name: 'isSharedCpu' + description: | + Whether this machine type has a shared CPU. See Shared-core machine + types for more information. + output: true + - !ruby/object:Api::Type::Integer + name: 'maximumPersistentDisks' + description: 'Maximum persistent disks allowed.' + output: true + - !ruby/object:Api::Type::Integer + name: 'maximumPersistentDisksSizeGb' + description: 'Maximum total persistent disks size (GB) allowed.' + output: true + - !ruby/object:Api::Type::Integer + name: 'memoryMb' + description: | + The amount of physical memory available to the instance, defined in + MB. + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: 'Name of the resource.' + - !ruby/object:Api::Type::ResourceRef + name: 'zone' + resource: 'Zone' + imports: 'name' + description: 'The zone the machine type is defined.' + required: true + - !ruby/object:Api::Resource + name: 'Network' + kind: 'compute#network' + base_url: projects/{{project}}/global/networks + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + Represents a Network resource. + + Your Cloud Platform Console project can contain multiple networks, and + each network can have multiple instances attached to it. A network allows + you to define a gateway IP and the network range for the instances + attached to that network. Every project is provided with a default network + with preset configurations and firewall rules. You can choose to customize + the default network by adding or removing rules, or you can create new + networks in that project. Generally, most users only need one network, + although you can have up to five networks per project by default. + + A network belongs to only one project, and each instance can only belong + to one network. All Compute Engine networks use the IPv4 protocol. Compute + Engine currently does not support IPv6. However, Google is a major + advocate of IPv6 and it is an important future direction. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + - !ruby/object:Api::Type::String + name: 'gateway_ipv4' + description: | + A gateway address for default routing to other networks. This value is + read only and is selected by the Google Compute Engine, typically as + the first usable address in the IPv4Range. + field: 'gatewayIPv4' + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'ipv4_range' + description: | + The range of internal addresses that are legal on this network. This + range is a CIDR specification, for example: 192.168.0.0/16. Provided + by the client when the network is created. + field: 'IPv4Range' + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + - !ruby/object:Api::Type::Array + # TODO: Change subnetworks to ResourceRef + name: 'subnetworks' + description: | + Server-defined fully-qualified URLs for all subnetworks in this + network. + item_type: Api::Type::String + output: true + - !ruby/object:Api::Type::Boolean + name: 'autoCreateSubnetworks' + description: | + When set to true, the network is created in "auto subnet mode". When + set to false, the network is in "custom subnet mode". + + In "auto subnet mode", a newly created network is assigned the default + CIDR of 10.128.0.0/9 and it automatically creates one subnetwork per + region. + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Resource + name: 'Region' + kind: 'compute#region' + base_url: projects/{{project}}/regions + exports: + - name + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + virtual: true + description: | + Represents a Region resource. A region is a specific geographical + location where you can run your resources. Each region has one or more + zones + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::Time + name: 'deprecated.deleted' + description: | + An optional RFC3339 timestamp on or after which the deprecation state + of this resource will be changed to DELETED. + output: true + - !ruby/object:Api::Type::Time + name: 'deprecated.deprecated' + description: | + An optional RFC3339 timestamp on or after which the deprecation state + of this resource will be changed to DEPRECATED. + output: true + - !ruby/object:Api::Type::Time + name: 'deprecated.obsolete' + description: | + An optional RFC3339 timestamp on or after which the deprecation state + of this resource will be changed to OBSOLETE. + output: true + - !ruby/object:Api::Type::String + name: 'deprecated.replacement' + description: | + The URL of the suggested replacement for a deprecated resource. The + suggested replacement resource must be the same kind of resource as + the deprecated resource. + output: true + - !ruby/object:Api::Type::Enum + name: 'deprecated.state' + description: | + The deprecation state of this resource. This can be DEPRECATED, + OBSOLETE, or DELETED. Operations which create a new resource using a + DEPRECATED resource will return successfully, but with a warning + indicating the deprecated resource and recommending its replacement. + Operations which use OBSOLETE or DELETED resources will be rejected + and result in an error. + values: + - :DEPRECATED + - :OBSOLETE + - :DELETED + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + output: true + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: 'Name of the resource.' + - !ruby/object:Api::Type::Array + name: 'zones' + description: 'List of zones within the region' + item_type: Api::Type::String + output: true + - !ruby/object:Api::Resource + name: 'Route' + kind: 'compute#route' + base_url: projects/{{project}}/global/routes + description: | + Represents a Route resource. + + A route is a rule that specifies how certain packets should be handled by + the virtual network. Routes are associated with virtual machines by tag, + and the set of routes for a particular virtual machine is called its + routing table. For each packet leaving a virtual machine, the system + searches that virtual machine's routing table for a single best matching + route. + + Routes match packets by destination IP address, preferring smaller or more + specific ranges over larger ones. If there is a tie, the system selects + the route with the smallest priority value. If there is still a tie, it + uses the layer three and four packet headers to select just one of the + remaining matching routes. The packet is then forwarded as specified by + the next_hop field of the winning route -- either to another virtual + machine destination, a virtual machine gateway or a Compute + Engine-operated gateway. Packets that do not match any route in the + sending virtual machine's routing table will be dropped. + + A Routes resources must have exactly one specification of either + nextHopGateway, nextHopInstance, nextHopIp, or nextHopVpnTunnel. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::String + name: 'destRange' + description: | + The destination range of outgoing packets that this route applies to. + Only IPv4 is supported. + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and + match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + the first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the + last character, which cannot be a dash. + - !ruby/object:Api::Type::ResourceRef + name: 'network' + resource: 'Network' + imports: 'selfLink' + description: 'The network that this route applies to.' + input: true + - !ruby/object:Api::Type::Integer + name: 'priority' + description: | + The priority of this route. Priority is used to break ties in cases + where there is more than one matching route of equal prefix length. + + In the case of two routes with equal prefix length, the one with the + lowest-numbered priority value wins. + + Default value is 1000. Valid range is 0 through 65535. + input: true + - !ruby/object:Api::Type::Array + name: 'tags' + description: 'A list of instance tags to which this route applies.' + item_type: Api::Type::String + input: true + # TODO(nelsonjr): Make this a ResourceRef when Gateway is submitted. + - !ruby/object:Api::Type::String + name: 'nextHopGateway' + description: | + URL to a gateway that should handle matching packets. + + Currently, you can only specify the internet gateway, using a full or + partial valid URL: + + * https://www.googleapis.com/compute/v1/projects/project/ + global/gateways/default-internet-gateway + * projects/project/global/gateways/default-internet-gateway + * global/gateways/default-internet-gateway + input: true + # TODO(nelsonjr): Make this a ResourceRef when Instance is submitted. + - !ruby/object:Api::Type::String + name: 'nextHopInstance' + description: | + URL to an instance that should handle matching packets. + You can specify this as a full or partial URL. For example: + + * https://www.googleapis.com/compute/v1/projects/project/zones/zone/ + instances/instance + * projects/project/zones/zone/instances/instance + * zones/zone/instances/instance + input: true + - !ruby/object:Api::Type::String + name: 'nextHopIp' + description: | + Network IP address of an instance that should handle matching packets. + input: true + # TODO(nelsonjr): Make this a ResourceRef when VpnTunnel is submitted. + - !ruby/object:Api::Type::String + name: 'nextHopVpnTunnel' + description: | + URL to a VpnTunnel that should handle matching packets. + input: true + - !ruby/object:Api::Resource + name: 'Snapshot' + kind: 'compute#snapshot' + base_url: projects/{{project}}/global/snapshots + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + Represents a Persistent Disk Snapshot resource. + + Use snapshots to back up data from your persistent disks. Snapshots are + different from public images and custom images, which are used primarily + to create instances or configure instance templates. Snapshots are useful + for periodic backup of the data on your persistent disks. You can create + snapshots from persistent disks even while they are attached to running + instances. + + Snapshots are incremental, so you can create regular snapshots on a + persistent disk faster and at a much lower cost than if you regularly + created a full image of the disk. + # 'createSnapshot' is a zonal operation while 'snapshot.delete' is a global + # operation. we'll leave the object as global operation and use the disk's + # zonal operation for the create action. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'source' + resource: 'Disk' + imports: 'name' + description: 'A reference to the disk used to create this snapshot.' + input: true + - !ruby/object:Api::Type::ResourceRef + name: 'zone' + resource: 'Zone' + imports: 'name' + description: 'A reference to the zone where the disk is hosted.' + input: true + - !ruby/object:Api::Type::NestedObject + name: 'snapshotEncryptionKey' + description: | + The customer-supplied encryption key of the snapshot. Required if the + source snapshot is protected by a customer-supplied encryption key. + properties: + - !ruby/object:Api::Type::String + name: 'rawKey' + description: | + Specifies a 256-bit customer-supplied encryption key, encoded in + RFC 4648 base64 to either encrypt or decrypt this resource. + - !ruby/object:Api::Type::String + name: 'sha256' + description: | + The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied + encryption key that protects this resource. + output: true + input: true + - !ruby/object:Api::Type::NestedObject + name: 'sourceDiskEncryptionKey' + description: | + The customer-supplied encryption key of the source snapshot. Required + if the source snapshot is protected by a customer-supplied encryption + key. + properties: + - !ruby/object:Api::Type::String + name: 'rawKey' + description: | + Specifies a 256-bit customer-supplied encryption key, encoded in + RFC 4648 base64 to either encrypt or decrypt this resource. + - !ruby/object:Api::Type::String + name: 'sha256' + description: | + The RFC 4648 base64 encoded SHA-256 hash of the customer-supplied + encryption key that protects this resource. + output: true + input: true + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + # 'status' not useful for object convergence. + - !ruby/object:Api::Type::Integer + name: 'diskSizeGb' + description: 'Size of the snapshot, specified in GB.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource; provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + required: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + # 'sourceDiskId' not useful for object convergence. + - !ruby/object:Api::Type::Integer + name: 'storageBytes' + description: | + A size of the the storage used by the snapshot. As snapshots share + storage, this number is expected to change with snapshot + creation/deletion. + output: true + # 'storageBytesStatus' not useful for object convergence. + - !ruby/object:Api::Type::Array + name: 'licenses' + description: | + A list of public visible licenses that apply to this snapshot. This + can be because the original image had licenses attached (such as a + Windows image). snapshotEncryptionKey nested object Encrypts the + snapshot using a customer-supplied encryption key. + item_type: !ruby/object:Api::Type::ResourceRef + name: 'license' + resource: 'License' + imports: 'selfLink' + description: 'A reference to a license associated with this snapshot' + # 'labelFingerprint' is used internally on updates + - !ruby/object:Api::Type::Array + name: 'labels' + description: 'Labels to apply to this snapshot.' + item_type: Api::Type::String + - !ruby/object:Api::Resource + name: 'SslCertificate' + kind: 'compute#sslCertificate' + base_url: projects/{{project}}/global/sslCertificates + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + An SslCertificate resource. This resource provides a mechanism to upload + an SSL key and certificate to the load balancer to serve secure + connections from the user. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::String + name: 'certificate' + description: | + The certificate in PEM format. + The certificate chain must be no greater than 5 certs long. + The chain must include at least one intermediate cert. + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + - !ruby/object:Api::Type::String + name: 'privateKey' + description: 'The private key in PEM format.' + input: true + - !ruby/object:Api::Resource + name: 'Subnetwork' + kind: 'compute#subnetwork' + base_url: projects/{{project}}/regions/{{region}}/subnetworks + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + A VPC network is a virtual version of the traditional physical networks + that exist within and between physical data centers. A VPC network + provides connectivity for your Compute Engine virtual machine (VM) + instances, Container Engine containers, App Engine Flex services, and + other network-related resources. + + Each GCP project contains one or more VPC networks. Each VPC network is a + global entity spanning all GCP regions. This global VPC network allows VM + instances and other resources to communicate with each other via internal, + private IP addresses. + + Each VPC network is subdivided into subnets, and each subnet is contained + within a single region. You can have more than one subnet in a region for + a given VPC network. Each subnet has a contiguous private RFC1918 IP + space. You create instances, containers, and the like in these subnets. + When you create an instance, you must create it in a subnet, and the + instance draws its internal IP address from that subnet. + + Virtual machine (VM) instances in a VPC network can communicate with + instances in all other subnets of the same VPC network, regardless of + region, using their RFC1918 private IP addresses. You can isolate portions + of the network, even entire subnets, using firewall rules. +<%= indent(compile('templates/regional_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. This field can be set only at resource + creation time. + # TODO(nelsonjr): Update 'gatewayAddress' input:true vs. output:true + # depending on the resolution of http://b/63113897. + - !ruby/object:Api::Type::String + name: 'gatewayAddress' + description: | + The gateway address for default routes to reach destination addresses + outside this subnetwork. This field can be set only at resource + creation time. + input: true + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'ipCidrRange' + description: | + The range of internal addresses that are owned by this subnetwork. + Provide this property when you create the subnetwork. For example, + 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and + non-overlapping within a network. Only IPv4 is supported. + input: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name of the resource, provided by the client when initially + creating the resource. The name must be 1-63 characters long, and + comply with RFC1035. Specifically, the name must be 1-63 characters + long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which + means the first character must be a lowercase letter, and all + following characters must be a dash, lowercase letter, or digit, + except the last character, which cannot be a dash. + - !ruby/object:Api::Type::ResourceRef + name: 'network' + resource: 'Network' + imports: 'selfLink' + description: | + The network this subnet belongs to. + Only networks that are in the distributed mode can have subnetworks. + input: true + - !ruby/object:Api::Type::Boolean + name: 'privateIpGoogleAccess' + description: | + Whether the VMs in this subnet can access Google services without + assigned external IP addresses. + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'name' + description: | + URL of the region where the regional address resides. + This field is not applicable to global addresses. + required: true + input: true + - !ruby/object:Api::Resource + name: 'TargetHttpProxy' + kind: 'compute#targetHttpProxy' + base_url: projects/{{project}}/global/targetHttpProxies + description: | + Represents a TargetHttpProxy resource, which is used by one or more global + forwarding rule to route incoming HTTP requests to a URL map. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + - !ruby/object:Api::Type::ResourceRef + name: 'urlMap' + resource: 'UrlMap' + imports: 'selfLink' + description: | + A reference to the UrlMap resource that defines the mapping from URL + to the BackendService. + - !ruby/object:Api::Resource + name: 'TargetHttpsProxy' + kind: 'compute#targetHttpsProxy' + base_url: projects/{{project}}/global/targetHttpsProxies + description: | + Represents a TargetHttpsProxy resource, which is used by one or more + global forwarding rule to route incoming HTTPS requests to a URL map. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + - !ruby/object:Api::Type::Array + name: 'sslCertificates' + description: | + A list of SslCertificate resources that are used to authenticate + connections between users and the load balancer. Currently, exactly + one SSL certificate must be specified. + item_type: !ruby/object:Api::Type::ResourceRef + name: 'sslCertificate' + resource: 'SslCertificate' + imports: 'selfLink' + description: 'The SSL certificates used by this TargetHttpsProxy' + - !ruby/object:Api::Type::ResourceRef + name: 'urlMap' + resource: 'UrlMap' + imports: 'selfLink' + description: | + A reference to the UrlMap resource that defines the mapping from URL + to the BackendService. + - !ruby/object:Api::Resource + name: 'TargetPool' + kind: 'compute#targetPool' + base_url: projects/{{project}}/regions/{{region}}/targetPools + description: 'Represents a TargetPool resource, used for Load Balancing.' + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + transport: !ruby/object:Api::Resource::Transport + encoder: encode_request + decoder: decode_request +<%= indent(compile('templates/regional_async.yaml.erb'), 4) %> + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'name' + description: 'The region where the target pool resides.' + required: true + properties: + - !ruby/object:Api::Type::ResourceRef + name: 'backupPool' + resource: 'TargetPool' + imports: 'selfLink' + description: | + This field is applicable only when the containing target pool is + serving a forwarding rule as the primary pool, and its failoverRatio + field is properly set to a value between [0, 1]. + + backupPool and failoverRatio together define the fallback behavior of + the primary target pool: if the ratio of the healthy instances in the + primary pool is at or below failoverRatio, traffic arriving at the + load-balanced IP will be directed to the backup pool. + + In case where failoverRatio and backupPool are not set, or all the + instances in the backup pool are unhealthy, the traffic will be + directed back to the primary pool in the "force" mode, where traffic + will be spread to the healthy instances with the best effort, or to + all instances when no instance is healthy. + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + - !ruby/object:Api::Type::Double + name: 'failoverRatio' + description: | + This field is applicable only when the containing target pool is + serving a forwarding rule as the primary pool (i.e., not as a backup + pool to some other target pool). The value of the field must be in + [0, 1]. + + If set, backupPool must also be set. They together define the fallback + behavior of the primary target pool: if the ratio of the healthy + instances in the primary pool is at or below this number, traffic + arriving at the load-balanced IP will be directed to the backup pool. + + In case where failoverRatio is not set or all the instances in the + backup pool are unhealthy, the traffic will be directed back to the + primary pool in the "force" mode, where traffic will be spread to the + healthy instances with the best effort, or to all instances when no + instance is healthy. + - !ruby/object:Api::Type::ResourceRef + name: 'healthCheck' + resource: 'HttpHealthCheck' + imports: 'selfLink' + description: | + A reference to a HttpHealthCheck resource. + + A member instance in this pool is considered healthy if and only if + the health checks pass. If not specified it means all member instances + will be considered healthy at all times. + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::Array + name: 'instances' + description: | + A list of virtual machine instances serving this pool. + + They must live in zones contained in the same region as this pool. + item_type: !ruby/object:Api::Type::ResourceRef + name: 'instance' + description: 'The instance being served by this pool.' + resource: 'Instance' + imports: 'selfLink' + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + required: true + - !ruby/object:Api::Type::Enum + name: 'sessionAffinity' + description: | + Session affinity option. Must be one of these values: + + - NONE: Connections from the same client IP may go to any instance in + the pool. + - CLIENT_IP: Connections from the same client IP will go to the same + instance in the pool while that instance remains healthy. + - CLIENT_IP_PROTO: Connections from the same client IP with the same + IP protocol will go to the same instance in the pool while that + instance remains healthy. + values: + - :NONE + - :CLIENT_IP + - :CLIENT_IP_PROTO + - !ruby/object:Api::Resource + name: 'TargetSslProxy' + kind: 'compute#targetSslProxy' + base_url: projects/{{project}}/global/targetSslProxies + description: | + Represents a TargetSslProxy resource, which is used by one or more + global forwarding rule to route incoming SSL requests to a backend + service. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + - !ruby/object:Api::Type::Enum + name: 'proxyHeader' + description: | + Specifies the type of proxy header to append before sending data to + the backend, either NONE or PROXY_V1. The default is NONE. + values: + - :NONE + - :PROXY_V1 + - !ruby/object:Api::Type::ResourceRef + name: 'service' + resource: 'BackendService' + imports: 'selfLink' + description: | + A reference to the BackendService resource. + - !ruby/object:Api::Type::Array + name: 'sslCertificates' + description: | + A list of SslCertificate resources that are used to authenticate + connections between users and the load balancer. Currently, exactly + one SSL certificate must be specified. + item_type: !ruby/object:Api::Type::ResourceRef + name: 'sslCertificate' + resource: 'SslCertificate' + imports: 'selfLink' + description: 'The SSL certificates used by this TargetHttpsProxy' + - !ruby/object:Api::Resource + name: 'TargetTcpProxy' + kind: 'compute#targetTcpProxy' + base_url: projects/{{project}}/global/targetTcpProxies + description: | + Represents a TargetTcpProxy resource, which is used by one or more + global forwarding rule to route incoming TCP requests to a Backend + service. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + - !ruby/object:Api::Type::Enum + name: 'proxyHeader' + description: | + Specifies the type of proxy header to append before sending data to + the backend, either NONE or PROXY_V1. The default is NONE. + values: + - :NONE + - :PROXY_V1 + - !ruby/object:Api::Type::ResourceRef + name: 'service' + resource: 'BackendService' + imports: 'selfLink' + description: | + A reference to the BackendService resource. + - !ruby/object:Api::Resource + name: 'UrlMap' + kind: 'compute#urlMap' + base_url: projects/{{project}}/global/urlMaps + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + description: | + UrlMaps are used to route requests to a backend service based on rules + that you define for the host and path of an incoming URL. +<%= indent(compile('templates/global_async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::ResourceRef + name: 'defaultService' + resource: 'BackendService' + imports: 'selfLink' + description: + A reference to BackendService resource if none of the hostRules match. + required: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. + # 'fingerprint' used internally for object consistency. + - !ruby/object:Api::Type::Array + name: 'hostRules' + description: 'The list of HostRules to use against the URL.' + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property + when you create the resource. + - !ruby/object:Api::Type::Array + name: 'hosts' + description: | + The list of host patterns to match. They must be valid + hostnames, except * will match any string of ([a-z0-9-.]*). In + that case, * must be the first character and must be followed in + the pattern by either - or .. + item_type: Api::Type::String + - !ruby/object:Api::Type::String + name: 'pathMatcher' + description: | + The name of the PathMatcher to use to match the path portion of + the URL if the hostRule matches the URL's host portion. + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource. Provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + - !ruby/object:Api::Type::Array + name: 'pathMatchers' + description: 'The list of named PathMatchers to use against the URL.' + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::ResourceRef + name: 'defaultService' + resource: 'BackendService' + imports: 'selfLink' + description: | + A reference to a BackendService resource. This will be used if + none of the pathRules defined by this PathMatcher is matched by + the URL's path portion. + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this resource.' + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name to which this PathMatcher is referred by the HostRule. + - !ruby/object:Api::Type::Array + name: 'pathRules' + description: 'The list of path rules.' + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::Array + name: 'paths' + description: | + The list of path patterns to match. Each must start with / + and the only place a * is allowed is at the end following + a /. The string fed to the path matcher does not include + any text after the first ? or #, and those chars are not + allowed here. + item_type: Api::Type::String + - !ruby/object:Api::Type::ResourceRef + name: 'service' + resource: 'BackendService' + imports: 'selfLink' + description: | + A reference to the BackendService resource if this rule is + matched. + - !ruby/object:Api::Type::Array + name: 'tests' + description: | + The list of expected URL mappings. Request to update this UrlMap will + succeed only if all of the test cases pass. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'description' + description: 'Description of this test case.' + - !ruby/object:Api::Type::String + name: 'host' + description: 'Host portion of the URL.' + - !ruby/object:Api::Type::String + name: 'path' + description: 'Path portion of the URL.' + - !ruby/object:Api::Type::ResourceRef + name: 'service' + resource: 'BackendService' + imports: 'selfLink' + description: + A reference to expected BackendService resource the given URL + should be mapped to. + - !ruby/object:Api::Resource + name: 'Zone' + kind: 'compute#zone' + base_url: projects/{{project}}/zones + exports: + - name + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + virtual: true + description: 'Represents a Zone resource.' + properties: + - !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true + - !ruby/object:Api::Type::NestedObject + name: 'deprecated' + description: 'The deprecation status associated with this machine type.' + properties: + - !ruby/object:Api::Type::Time + name: 'deleted' + description: | + An optional RFC3339 timestamp on or after which the state of this + resource is intended to change to DELETED. This is only + informational and the status will not change unless the client + explicitly changes it. + output: true + - !ruby/object:Api::Type::Time + name: 'deprecated' + description: | + An optional RFC3339 timestamp on or after which the state of this + resource is intended to change to DEPRECATED. This is only + informational and the status will not change unless the client + explicitly changes it. + output: true + - !ruby/object:Api::Type::Time + name: 'obsolete' + description: | + An optional RFC3339 timestamp on or after which the state of this + resource is intended to change to OBSOLETE. This is only + informational and the status will not change unless the client + explicitly changes it. + output: true + - !ruby/object:Api::Type::String + name: 'replacement' + description: | + The URL of the suggested replacement for a deprecated resource. + The suggested replacement resource must be the same kind of + resource as the deprecated resource. + output: true + - !ruby/object:Api::Type::Enum + name: 'state' + description: | + The deprecation state of this resource. This can be DEPRECATED, + OBSOLETE, or DELETED. Operations which create a new resource + using a DEPRECATED resource will return successfully, but with a + warning indicating the deprecated resource and recommending its + replacement. Operations which use OBSOLETE or DELETED resources + will be rejected and result in an error. + values: + - :DEPRECATED + - :OBSOLETE + - :DELETED + output: true + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional textual description of the resource.' + output: true + - !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: 'Name of the resource.' + - !ruby/object:Api::Type::ResourceRef + name: 'region' + resource: 'Region' + imports: 'selfLink' + description: 'The region where the zone is located.' + output: true + - !ruby/object:Api::Type::Enum + name: 'status' + description: 'The status of the zone.' + values: + - :UP + - :DOWN + output: true diff --git a/products/compute/chef-e2e.yaml b/products/compute/chef-e2e.yaml new file mode 100644 index 000000000000..1e5028e7b4a1 --- /dev/null +++ b/products/compute/chef-e2e.yaml @@ -0,0 +1,272 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Chef compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Chef::Tester + product: 'Compute' + tests: + - !ruby/object:Chef::StandardTest + name: 'Address' + module: 'gcompute' + affected_count: 1 + resource_count: 3 + verifiers: + - phase: ALL + command: | + gcloud compute addresses describe + --project=google.com:graphite-playground + --region=us-west1 + chef-e2e-test1 +# TODO(alexstephen): Uncomment when test works +# Nested objects are not properly filtering nil values +# GCP API believes we're passing multiple values when only one is allowed +# This is because all of the values are set to 0/0.0 (nil) +# - !ruby/object:Chef::StandardTest +# name: 'BackendService' +# module: 'gcompute' +# resource_count: 3 +# affected_count: 1 +# verifiers: +# - phase: ALL +# command: | +# gcloud compute backend-services describe +# --project=google.com:graphite-playground +# --global +# chef-e2e-my-app-backend + - !ruby/object:Chef::StandardTest + name: 'Disk' + module: 'gcompute' + resource_count: 3 + verifiers: + - phase: ALL + command: | + gcloud compute disks describe + --project=google.com:graphite-playground + --zone=us-central1-a + chef-e2e-data-disk-1 + - !ruby/object:Chef::VirtualTest + name: 'DiskType' + module: 'gcompute' + resource_count: 3 + - !ruby/object:Chef::StandardTest + name: 'Firewall' + module: 'gcompute' + resource_count: 2 + verifiers: + - phase: ALL + command: | + gcloud compute firewall-rules describe + --project=google.com:graphite-playground + chef-e2e-test-fw-allow-ssh + - !ruby/object:Chef::StandardTest + name: 'GlobalAddress' + module: 'gcompute' + verifiers: + - phase: ALL + command: | + gcloud compute addresses describe + --project=google.com:graphite-playground + --global + chef-e2e-my-app-lb + - !ruby/object:Chef::StandardTest + name: 'HealthCheck' + module: 'gcompute' + verifiers: + - phase: ALL + command: | + gcloud compute health-checks describe + --project=google.com:graphite-playground + chef-e2e-app-health-check + - !ruby/object:Chef::StandardTest + name: 'HttpHealthCheck' + module: 'gcompute' + verifiers: + - phase: ALL + command: | + gcloud compute http-health-checks describe + --project=google.com:graphite-playground + chef-e2e-my-app-http-hc + - !ruby/object:Chef::StandardTest + name: 'HttpsHealthCheck' + module: 'gcompute' + verifiers: + - phase: ALL + command: | + gcloud compute https-health-checks describe + --project=google.com:graphite-playground + chef-e2e-my-app-https-hc + - !ruby/object:Chef::StandardTest + name: 'Image' + module: 'gcompute' + resource_count: 4 + affected_count: 1 + verifiers: + - phase: ALL + command: | + gcloud compute images describe + --project=google.com:graphite-playground + chef-e2e-test-image + - !ruby/object:Chef::StandardTest + name: 'InstanceGroup' + module: 'gcompute' + resource_count: 4 + affected_count: 1 + verifiers: + - phase: ALL + command: | + gcloud compute instance-groups describe + --project=google.com:graphite-playground + --zone=us-central1-a + chef-e2e-my-masters + - !ruby/object:Chef::StandardTest + name: 'Instance' + module: 'gcompute' + resource_count: + create: 8 + delete: 3 + affected_count: + create: 2 # Disk will always be created alongside Instance + delete: 1 # Disk is autodeleted by GCP + verifiers: + - phase: ALL + command: | + gcloud compute instances describe + --project=google.com:graphite-playground + --zone=us-west1-a + chef-e2e-instance-test + - !ruby/object:Chef::StandardTest + name: 'InstanceTemplate' + module: 'gcompute' + resource_count: + create: 5 + delete: 2 + affected_count: 1 + verifiers: + - phase: ALL + command: | + gcloud compute instance-templates describe + --project=google.com:graphite-playground + chef-e2e-instance-template-test + # Auto network + - !ruby/object:Chef::StandardTest + name: 'NetworkAuto' + module: 'gcompute' + resource_name: 'network' + recipes: + create: 'tests~network~auto' + delete: 'tests~delete_network' + env: + network_id: auto-1234 + verifiers: + - phase: ALL + command: | + gcloud compute networks describe + --project=google.com:graphite-playground + chef-e2e-mynetwork-auto-1234 + # Custom Network + - !ruby/object:Chef::StandardTest + name: 'NetworkCustom' + module: 'gcompute' + resource_name: 'network' + recipes: + create: 'tests~network~custom' + delete: 'tests~delete_network' + env: + network_id: custom-1234 + verifiers: + - phase: ALL + command: | + gcloud compute networks describe + --project=google.com:graphite-playground + chef-e2e-mynetwork-custom-1234 + # Convert to Custom Network + - !ruby/object:Chef::StandardTest + name: 'NetworkConvertCustom' + module: 'gcompute' + resource_name: 'network' + recipes: + create: 'tests~network~convert_to_custom' + delete: 'tests~delete_network' + env: + network_id: conv-cust-1234 + verifiers: + - phase: ALL + command: | + gcloud compute networks describe + --project=google.com:graphite-playground + chef-e2e-mynetwork-conv-cust-1234 + # Legacy Network + - !ruby/object:Chef::StandardTest + name: 'NetworkLegacy' + module: 'gcompute' + resource_name: 'network' + recipes: + create: 'tests~network~legacy' + delete: 'tests~delete_network' + env: + network_id: legacy-1234 + verifiers: + - phase: ALL + command: | + gcloud compute networks describe + --project=google.com:graphite-playground + chef-e2e-mynetwork-legacy-1234 + - !ruby/object:Chef::VirtualTest + name: 'Region' + module: 'gcompute' + - !ruby/object:Chef::StandardTest + name: 'Route' + module: 'gcompute' + resource_count: 4 + affected_count: 1 + verifiers: + - phase: ALL + command: | + gcloud compute routes describe + --project=google.com:graphite-playground + chef-e2e-corp-route + - !ruby/object:Chef::StandardTest + name: 'SslCertificate' + module: 'gcompute' + verifiers: + - phase: ALL + command: | + gcloud compute ssl-certificates describe + --project=google.com:graphite-playground + chef-e2e-my-site-ssl-cert + - !ruby/object:Chef::StandardTest + name: 'Subnetwork' + module: 'gcompute' + resource_count: + create: 4 + delete: 3 + affected_count: + create: 1 + delete: 1 + # TODO(alexstephen): Find a way to verify subnets + # TODO(alexstephen): Test can be a little flakey. + post: + - name: 'delete_network' + apply: + - run: 'google-gcompute::tests~delete_network' + env: + network_id: subnetwork + outputs: + - - 'Chef Client finished, 1/2 resources updated' + - !ruby/object:Chef::VirtualTest + name: 'Zone' + module: 'gcompute' diff --git a/products/compute/chef.yaml b/products/compute/chef.yaml new file mode 100644 index 000000000000..3e2b7e481fa5 --- /dev/null +++ b/products/compute/chef.yaml @@ -0,0 +1,490 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(alexstephen): Match all objects w/ Puppet. +# TODO(alexstephen): Match all special behavior Puppet <=> Chef, e.g. cannot +# edit InstanceGroup +--- !ruby/object:Provider::Chef::Config +manifest: !ruby/object:Provider::Chef::Manifest + version: '0.1.1' + source: 'https://github.com/GoogleCloudPlatform/chef-google-compute' + issues: 'https://github.com/GoogleCloudPlatform/chef-google-compute/issues' + summary: 'A Chef cookbook to manage Google Cloud Compute resources' + description: | + This cookbook provides the built-in types and services for Chef to manage + Google Cloud Compute resources, as native Chef types. + depends: + - !ruby/object:Provider::Config::Requirements + name: 'google-gauth' + versions: '< 0.2.0' + operating_systems: +<%= indent(include('provider/chef/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + DiskType: + update: | + message = 'DiskType cannot be edited' + Chef::Log.fatal message + raise message + provider_helpers: + include: + - 'products/compute/helpers/provider_disk_type.rb' + Disk: + update: | + message = 'Disk cannot be edited' + Chef::Log.fatal message + raise message + Instance: + update: | + message = 'Instance cannot be edited' + Chef::Log.fatal message + raise message + Image: + overrides: + deprecated: _deprecated + MachineType: + overrides: + deprecated: _deprecated + Network: + # TODO(alexstephen): Better editing from auto to custom + # This should mimic Puppet. + update: | + message = 'Network cannot be edited' + Chef::Log.fatal message + raise message + provider_helpers: + include: + - 'products/compute/helpers/provider_network.rb' + Region: + update: | + message = 'Region cannot be edited' + Chef::Log.fatal message + raise message + TargetPool: + provider_helpers: + include: + - 'products/compute/helpers/provider_target_pool.rb' + Zone: + overrides: + deprecated: _deprecated +examples: !ruby/object:Api::Resource::HashArray + Address: + - address.rb + - delete_address.rb + BackendBucket: + - backend_bucket.rb + - delete_backend_bucket.rb + BackendService: + - backend_service.rb + - delete_backend_service.rb + Disk: + - delete_disk.rb + - disk.rb + DiskType: + - disk_type.rb + Firewall: + - delete_firewall.rb + - firewall.rb + - firewall~change1.rb + GlobalAddress: + - delete_global_address.rb + - global_address.rb + HealthCheck: + - delete_health_check.rb + - health_check.rb + HttpHealthCheck: + - delete_http_health_check.rb + - http_health_check.rb + HttpsHealthCheck: + - delete_https_health_check.rb + - https_health_check.rb + Image: + - delete_image.rb + - image.rb + Instance: + - delete_instance.rb + - instance.rb + InstanceGroup: + - delete_instance_group.rb + - instance_group.rb + InstanceTemplate: + - delete_instance_template.rb + - instance_template.rb + License: + - license.rb + MachineType: + - machine_type.rb + Network: + - delete_network.rb + - network~auto.rb + - network~convert_to_custom.rb + - network~custom.rb + - network~legacy.rb + Region: + - region.rb + Route: + - delete_route.rb + - route.rb + SslCertificate: + - delete_ssl_certificate.rb + - ssl_certificate.rb + Subnetwork: + - delete_subnetwork.rb + - subnetwork.rb + Zone: + - zone.rb +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/chef/common~copy.yaml'), 4) %> + compile: +<%= indent(include('provider/chef/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/chef/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'Test Data will be automatically generated' +style: + - !ruby/object:Provider::Config::StyleException + name: libraries/google/compute/property/backendservice_cache_key_policy.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: libraries/google/compute/property/backendservice_backends.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: libraries/google/compute/property/instance_disks.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: libraries/google/compute/property/instance_network_interfaces.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: libraries/google/compute/property/instancetemplate_network_interfaces.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: libraries/google/compute/property/instancetemplate_disks.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: libraries/google/compute/property/instancetemplate_properties.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - function: to_s + exceptions: + - Metrics/MethodLength + - Metrics/AbcSize + - !ruby/object:Provider::Config::StyleException + name: libraries/google/compute/property/backend_service_cache_key_policy.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: libraries/google/compute/property/backend_service_backends.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/address.rb + pinpoints: + - class: Google::GCOMPUTE::Address + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/backend_bucket.rb + pinpoints: + - class: Google::GCOMPUTE::BackendBucket + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/backend_service.rb + pinpoints: + - class: Google::GCOMPUTE::BackendService + exceptions: + - Metrics/ClassLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - Metrics/AbcSize + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/disk.rb + pinpoints: + - class: Google::GCOMPUTE::Disk + exceptions: + - Metrics/ClassLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/disk_type.rb + pinpoints: + - class: Google::GCOMPUTE::DiskType + exceptions: + - Metrics/ClassLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/firewall.rb + pinpoints: + - class: Google::GCOMPUTE::Firewall + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/global_address.rb + pinpoints: + - class: Google::GCOMPUTE::GlobalAddress + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/health_check.rb + pinpoints: + - class: Google::GCOMPUTE::HealthCheck + exceptions: + - Metrics/ClassLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/AbcSize + - !ruby/object:Provider::Config::StyleException + name: resources/http_health_check.rb + pinpoints: + - class: Google::GCOMPUTE::HttpHealthCheck + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/https_health_check.rb + pinpoints: + - class: Google::GCOMPUTE::HttpsHealthCheck + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/image.rb + pinpoints: + - class: Google::GCOMPUTE::Image + exceptions: + - Metrics/ClassLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - Metrics/AbcSize + - !ruby/object:Provider::Config::StyleException + name: resources/instance.rb + pinpoints: + - class: Google::GCOMPUTE::Instance + exceptions: + - Metrics/ClassLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/AbcSize + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/instance_group.rb + pinpoints: + - class: Google::GCOMPUTE::InstanceGroup + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/instance_template.rb + pinpoints: + - class: Google::GCOMPUTE::InstanceTemplate + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/license.rb + pinpoints: + - class: Google::GCOMPUTE::License + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/machine_type.rb + pinpoints: + - class: Google::GCOMPUTE::MachineType + exceptions: + - Metrics/ClassLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/network.rb + pinpoints: + - class: Google::GCOMPUTE::Network + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/region.rb + pinpoints: + - class: Google::GCOMPUTE::Region + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/route.rb + pinpoints: + - class: Google::GCOMPUTE::Route + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/ssl_certificate.rb + pinpoints: + - class: Google::GCOMPUTE::SslCertificate + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/subnetwork.rb + pinpoints: + - class: Google::GCOMPUTE::Subnetwork + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/zone.rb + pinpoints: + - class: Google::GCOMPUTE::Zone + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: spec/test_constants.rb + pinpoints: + - module: GoogleTests::Constants + exceptions: + - Metrics/ModuleLength + - !ruby/object:Provider::Config::StyleException + name: spec/gcompute_instance_template + pinpoints: + - test: InstanceTemplate > present > not_exist > success > title_eq_name > before + exceptions: + - Metrics/LineLength + - test: InstanceTemplate > present > not_exist > success > title_and_name > before + exceptions: + - Metrics/LineLength + - !ruby/object:Provider::Config::StyleException + name: spec/gcompute_instance + pinpoints: + - test: Instance > present > not_exist > success > title_eq_name > before + exceptions: + - Metrics/LineLength + - test: Instance > present > not_exist > success > title_and_name > before + exceptions: + - Metrics/LineLength +# TODO(alexstephen): Add changelog. +functions: + # TODO(alexstephen): Test other compute function and merge + # all functions into a single YAML file. + - !ruby/object:Provider::Chef::Function + name: 'gcompute_address_ip' + description: | + Retrieves the IP address associated with a gcompute_address static IP. + arguments: + - !ruby/object:Provider::Config::Function::Argument + name: name + type: Api::Type::String + description: 'the name of the static IP' + - !ruby/object:Provider::Config::Function::Argument + name: region + type: Api::Type::String + description: | + the name of the region that hosts the address + - !ruby/object:Provider::Config::Function::Argument + name: project + type: Api::Type::String + description: | + the name of the project that hosts the address + - !ruby/object:Provider::Config::Function::Argument + name: cred + type: Google::Authorization + description: | + the credential to use to authorize the information request + search_paths: + - !ruby/object:Provider::Chef::SearchPath + name: 'self' + comment: Enable loading cookbook libraries + path: ../../.. + requires: + - google/compute/network/get + - resources/address + code: | + uri = Google::GCOMPUTE::Address.action_class.self_link(name: name, + region: region, + project: project) + get_request = ::Google::Compute::Network::Get.new(uri, cred) + response = JSON.parse(get_request.send.body) + response['address'] + examples: + - gcompute_address_ip('my-server', 'us-central1', 'myproject', fn_auth) + notes: | + The credential parameter should be allocated with a + `gauth_credential_*_for_function` call. + - !ruby/object:Provider::Chef::Function + name: 'gcompute_image_family' + description: | + Builds the family resource identifier required to uniquely identify the + family, e.g. to create virtual machines based on it. You can use this + function as `source_image` of a `gcompute_instance` resource. + arguments: + - !ruby/object:Provider::Config::Function::Argument + name: family_name + type: Api::Type::String + description: 'the name of the family, e.g. ubuntu-1604-lts' + - !ruby/object:Provider::Config::Function::Argument + name: project_name + type: Api::Type::String + description: | + the name of the project that hosts the family, + e.g. ubuntu-os-cloud + code: '"projects/#{project_name}/global/images/family/#{family_name}"' + examples: + - gcompute_image_family('ubuntu-1604-lts', 'ubuntu-os-cloud') + - gcompute_image_family('my-web-server', 'my-project') + notes: | + Note: In the case of private images, your credentials will need to have + the proper permissions to access the image. + + To get a list of supported families you can use the gcloud utility: + + gcloud compute images list +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.1.1' + date: 2017-10-16T18:30:00-0700 + features: + - Added `gcompute_address_ip` client function. + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-10-04T10:00:00-0700 + general: 'Initial release' diff --git a/products/compute/files/examples~address.pp b/products/compute/files/examples~address.pp new file mode 100644 index 000000000000..fc9b03006a98 --- /dev/null +++ b/products/compute/files/examples~address.pp @@ -0,0 +1,34 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_region { <%= example_resource_name('some-region') -%>: + name => 'us-west1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_address { <%= example_resource_name('test1') -%>: + ensure => present, + region => <%= example_resource_name('some-region') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~backend_bucket.pp b/products/compute/files/examples~backend_bucket.pp new file mode 100644 index 000000000000..ca235aad5d38 --- /dev/null +++ b/products/compute/files/examples~backend_bucket.pp @@ -0,0 +1,39 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% + # TODO(nelsonjr): http://b/63088154 Google Cloud Platform API is returning + # access denied if we use a more restricted scope such as + # https://www.googleapis.com/auth/compute. For the time being use an all + # mighty scope instead: https://www.googleapis.com/auth/cloud-platform. + original_scopes = data[:scopes] + data[:scopes] = ['https://www.googleapis.com/auth/cloud-platform'] +-%> +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> +<% data[:scopes] = original_scopes # restore the scopes -%> + +<% end # name == README.md -%> +gcompute_backend_bucket { <%= example_resource_name('be-bucket-connection') -%>: + ensure => present, + bucket_name => <%= example_resource_name('backend-bucket-test') -%>, + description => 'A BackendBucket to connect LNB w/ Storage Bucket', + enable_cdn => true, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~backend_service.pp b/products/compute/files/examples~backend_service.pp new file mode 100644 index 000000000000..923a0e6c827d --- /dev/null +++ b/products/compute/files/examples~backend_service.pp @@ -0,0 +1,53 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +<% if name == "README.md" -%> +# Backend Service requires various other services to be setup beforehand. Please +# make sure they are defined as well: +# - gcompute_instance_group { ... } +# - Health check +<% else # name == README.md -%> +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_instance_group { <%= example_resource_name('my-puppet-masters') -%>: + ensure => present, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_backend_service { <%= example_resource_name('my-app-backend') -%>: + ensure => present, + backends => [ + { group => <%= example_resource_name('my-puppet-masters') -%> }, + ], + enable_cdn => true, + health_checks => [ + gcompute_health_check_ref('another-hc', 'google.com:graphite-playground'), + ], + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~cookbook~address.rb b/products/compute/files/examples~cookbook~address.rb new file mode 100644 index 000000000000..3a9792280c1d --- /dev/null +++ b/products/compute/files/examples~cookbook~address.rb @@ -0,0 +1,36 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_region <%= example_resource_name('some-region') -%> do + action :create + r_label 'us-west1' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_address <%= example_resource_name('test1') -%> do + action :create + region <%= example_resource_name('some-region') %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~backend_bucket.rb b/products/compute/files/examples~cookbook~backend_bucket.rb new file mode 100644 index 000000000000..09f64c5a29f1 --- /dev/null +++ b/products/compute/files/examples~cookbook~backend_bucket.rb @@ -0,0 +1,46 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% + # TODO(nelsonjr): http://b/63088154 Google Cloud Platform API is returning + # access denied if we use a more restricted scope such as + # https://www.googleapis.com/auth/compute. For the time being use an all + # mighty scope instead: https://www.googleapis.com/auth/cloud-platform. + original_scopes = data[:scopes] + data[:scopes] = ['https://www.googleapis.com/auth/cloud-platform'] +-%> +<%= compile 'templates/chef/example~auth.rb.erb' -%> +<% data[:scopes] = original_scopes # restore the scopes -%> + +<% end -%> +# *** WARNING *** +# TODO(nelsonjr): http://b/63088154 Google Cloud Platform API is returning +# access denied if we use a more restricted scope such as +# https://www.googleapis.com/auth/compute. For the time being use an all mighty +# scope instead: https://www.googleapis.com/auth/cloud-platform. + +gcompute_backend_bucket <%= example_resource_name('be-bucket-connection') -%> do + action :create + bucket_name <%= example_resource_name('backend-bucket-test') %> + description 'A BackendBucket to connect LNB w/ Storage Bucket' + enable_cdn true + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~backend_service.rb b/products/compute/files/examples~cookbook~backend_service.rb new file mode 100644 index 000000000000..270090c0495e --- /dev/null +++ b/products/compute/files/examples~cookbook~backend_service.rb @@ -0,0 +1,49 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +<% if name == "README.md" -%> +# Backend Service requires various other services to be setup beforehand. Please +# make sure they are defined as well: +# - gcompute_instance_group 'my-masters' do ... end +# - Health check +<% else # name == README.md -%> +gcompute_instance_group <%= example_resource_name('my-masters') -%> do + action :create + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end # name == README.md -%> +gcompute_backend_service <%= example_resource_name('my-app-backend') -%> do + action :create + backends [ + { group: <%= example_resource_name('my-masters') -%> } + ] + enable_cdn true + health_checks [ + gcompute_health_check_ref('another-hc', 'google.com:graphite-playground') + ] + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_address.rb b/products/compute/files/examples~cookbook~delete_address.rb new file mode 100644 index 000000000000..bb395b06f898 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_address.rb @@ -0,0 +1,36 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_region <%= example_resource_name('some-region') -%> do + action :create + r_label 'us-west1' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_address <%= example_resource_name('test1') -%> do + action :delete + region <%= example_resource_name('some-region') %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_backend_bucket.rb b/products/compute/files/examples~cookbook~delete_backend_bucket.rb new file mode 100644 index 000000000000..5e61a954ac71 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_backend_bucket.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_backend_bucket <%= example_resource_name('be-bucket-connection') -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_backend_service.rb b/products/compute/files/examples~cookbook~delete_backend_service.rb new file mode 100644 index 000000000000..ab3853dff803 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_backend_service.rb @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +<% if name == "README.md" -%> +# Backend Service requires various other services to be setup beforehand. Please +# make sure they are defined as well: +# - gcompute_instance_group 'my-masters' do ... end +# - Health check +<% else # name == README.md -%> +gcompute_instance_group <%= example_resource_name('my-masters') -%> do + action :create + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end # name == README.md -%> +<% res_name = 'my-app-backend' -%> +gcompute_backend_service <%= example_resource_name(res_name) -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_disk.rb b/products/compute/files/examples~cookbook~delete_disk.rb new file mode 100644 index 000000000000..bc3c80468c3f --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_disk.rb @@ -0,0 +1,34 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-central1-a' do + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end -%> +gcompute_disk <%= example_resource_name('data-disk-1') -%> do + action :delete + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_firewall.rb b/products/compute/files/examples~cookbook~delete_firewall.rb new file mode 100644 index 000000000000..88044d49f9a6 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_firewall.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_firewall <%= example_resource_name('test-fw-allow-ssh') -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_global_address.rb b/products/compute/files/examples~cookbook~delete_global_address.rb new file mode 100644 index 000000000000..1a3b53a7dcd6 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_global_address.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_global_address <%= example_resource_name('my-app-lb') -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_health_check.rb b/products/compute/files/examples~cookbook~delete_health_check.rb new file mode 100644 index 000000000000..bcc61d51fa8c --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_health_check.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_health_check <%= example_resource_name('app-health-check') -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_http_health_check.rb b/products/compute/files/examples~cookbook~delete_http_health_check.rb new file mode 100644 index 000000000000..e35262cf9904 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_http_health_check.rb @@ -0,0 +1,29 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_http_health_check <%= example_resource_name('app-health-check') -%> do + action :delete + hhc_label <%= example_resource_name('my-app-http-hc') %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_https_health_check.rb b/products/compute/files/examples~cookbook~delete_https_health_check.rb new file mode 100644 index 000000000000..337228be9a86 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_https_health_check.rb @@ -0,0 +1,29 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_https_health_check <%= example_resource_name('app-health-check') -%> do + action :delete + hhc_label <%= example_resource_name('my-app-https-hc') %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_image.rb b/products/compute/files/examples~cookbook~delete_image.rb new file mode 100644 index 000000000000..15f31962c3d9 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_image.rb @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-central1-a' do + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_disk <%= example_resource_name('data-disk-1') -%> do + action :create + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Tip: Be sure to include a valid gcompute_disk object +<% end # name == README.md -%> +gcompute_image <%= example_resource_name('test-image') -%> do + action :delete + source_disk <%= example_resource_name('data-disk-1') %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_instance.rb b/products/compute/files/examples~cookbook~delete_instance.rb new file mode 100644 index 000000000000..a2d4485205ca --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_instance.rb @@ -0,0 +1,37 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-west1-a' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Tip: Remember to define gcompute_zone to match the 'zone' property. +<% end -%> +gcompute_instance <%= example_resource_name('instance-test') -%> do + action :delete + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_instance_group.rb b/products/compute/files/examples~cookbook~delete_instance_group.rb new file mode 100644 index 000000000000..4c75fc1d7059 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_instance_group.rb @@ -0,0 +1,45 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +<% if name == "README.md" -%> +# Instance group requires a network and a region, so define them in your recipe: +# - gcompute_network 'my-network' do ... end +<% else # name == README.md -%> +gcompute_network <%= example_resource_name('my-network') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_zone 'us-central1-a' do + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end # name == README.md -%> +gcompute_instance_group <%= example_resource_name('my-masters') -%> do + action :delete + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_instance_template.rb b/products/compute/files/examples~cookbook~delete_instance_template.rb new file mode 100644 index 000000000000..1c4e8eed0229 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_instance_template.rb @@ -0,0 +1,29 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +<% res_name = example_resource_name('instance-template-test') -%> +gcompute_instance_template <%= res_name -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_network.rb b/products/compute/files/examples~cookbook~delete_network.rb new file mode 100644 index 000000000000..06a380f8102f --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_network.rb @@ -0,0 +1,32 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +raise "Missing parameter 'network_id'. Please read docs at #{__FILE__}" \ + unless ENV.key?('network_id') + +<% res_name = 'mynetwork-#{ENV[\'network_id\']}' -%> +gcompute_network <%= example_resource_name(res_name) -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_route.rb b/products/compute/files/examples~cookbook~delete_route.rb new file mode 100644 index 000000000000..077f2c3def92 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_route.rb @@ -0,0 +1,47 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +<% if name == "README.md" -%> +# Subnetwork requires a network and a region, so define them in your recipe: +# - gcompute_network 'my-network' do ... end +# - gcompute_region 'some-region' do ... end +<% else # name == README.md -%> +gcompute_network <%= example_resource_name('my-network') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_region <%= example_resource_name('some-region') -%> do + action :create + r_label 'us-west1' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end # name == README.md -%> +gcompute_route <%= example_resource_name('corp-route') -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_ssl_certificate.rb b/products/compute/files/examples~cookbook~delete_ssl_certificate.rb new file mode 100644 index 000000000000..f0c21288fae9 --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_ssl_certificate.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_ssl_certificate <%= example_resource_name('my-site-ssl-cert') -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~delete_subnetwork.rb b/products/compute/files/examples~cookbook~delete_subnetwork.rb new file mode 100644 index 000000000000..b29512fa150c --- /dev/null +++ b/products/compute/files/examples~cookbook~delete_subnetwork.rb @@ -0,0 +1,39 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_region <%= example_resource_name('some-region') -%> do + action :create + r_label 'us-west1' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else # name == README.md -%> +# Subnetwork requires a network and a region, so define them in your recipe: +# - gcompute_region 'some-region' do ... end +<% end # name == README.md -%> +gcompute_subnetwork <%= example_resource_name('servers') -%> do + action :delete + region <%= example_resource_name('some-region') %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~disk.rb b/products/compute/files/examples~cookbook~disk.rb new file mode 100644 index 000000000000..52ae5109f4a8 --- /dev/null +++ b/products/compute/files/examples~cookbook~disk.rb @@ -0,0 +1,38 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-central1-a' do + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end -%> +gcompute_disk <%= example_resource_name('data-disk-1') -%> do + action :create + size_gb 50 + disk_encryption_key({ + raw_key: 'SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=' + }) + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~disk_type.rb b/products/compute/files/examples~cookbook~disk_type.rb new file mode 100644 index 000000000000..680a4f9f0f30 --- /dev/null +++ b/products/compute/files/examples~cookbook~disk_type.rb @@ -0,0 +1,34 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-central1-a' do + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end -%> +gcompute_disk_type 'pd-standard' do + action :create + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~firewall.rb b/products/compute/files/examples~cookbook~firewall.rb new file mode 100644 index 000000000000..9edef1d72bda --- /dev/null +++ b/products/compute/files/examples~cookbook~firewall.rb @@ -0,0 +1,38 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_firewall <%= example_resource_name('test-fw-allow-ssh') -%> do + action :create + allowed [ + { + ip_protocol: 'tcp', + ports: ['22'] + } + ] + target_tags [ + 'test-ssh-server', + 'staging-ssh-server' + ] + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~firewall~change1.rb b/products/compute/files/examples~cookbook~firewall~change1.rb new file mode 100644 index 000000000000..cded083fdf36 --- /dev/null +++ b/products/compute/files/examples~cookbook~firewall~change1.rb @@ -0,0 +1,38 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_firewall <%= example_resource_name('test-fw-allow-ssh') -%> do + action :create + allowed [ + { + ip_protocol: 'tcp', + ports: ['22'] + }, + { + ip_protocol: 'tcp', + ports: ['2222'] + } + ] + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~global_address.rb b/products/compute/files/examples~cookbook~global_address.rb new file mode 100644 index 000000000000..af308d4e80ed --- /dev/null +++ b/products/compute/files/examples~cookbook~global_address.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_global_address <%= example_resource_name('my-app-lb') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~health_check.rb b/products/compute/files/examples~cookbook~health_check.rb new file mode 100644 index 000000000000..e5df156a9244 --- /dev/null +++ b/products/compute/files/examples~cookbook~health_check.rb @@ -0,0 +1,37 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_health_check <%= example_resource_name('app-health-check') -%> do + action :create + type 'TCP' + tcp_health_check( + port: 6123, + request: 'ping', + response: 'pong' + ) + healthy_threshold 10 + timeout_sec 2 + unhealthy_threshold 5 + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~http_health_check.rb b/products/compute/files/examples~cookbook~http_health_check.rb new file mode 100644 index 000000000000..894ccba51860 --- /dev/null +++ b/products/compute/files/examples~cookbook~http_health_check.rb @@ -0,0 +1,33 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_http_health_check <%= example_resource_name('app-health-check') -%> do + action :create + hhc_label <%= example_resource_name('my-app-http-hc') %> + healthy_threshold 10 + port 8080 + timeout_sec 2 + unhealthy_threshold 5 + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~https_health_check.rb b/products/compute/files/examples~cookbook~https_health_check.rb new file mode 100644 index 000000000000..9e78bde2e62a --- /dev/null +++ b/products/compute/files/examples~cookbook~https_health_check.rb @@ -0,0 +1,33 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_https_health_check <%= example_resource_name('app-health-check') -%> do + action :create + hhc_label <%= example_resource_name('my-app-https-hc') %> + healthy_threshold 10 + port 8080 + timeout_sec 2 + unhealthy_threshold 5 + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~image.rb b/products/compute/files/examples~cookbook~image.rb new file mode 100644 index 000000000000..ddc6b95a5118 --- /dev/null +++ b/products/compute/files/examples~cookbook~image.rb @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-central1-a' do + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_disk <%= example_resource_name('data-disk-1') -%> do + action :create + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Tip: Be sure to include a valid gcompute_disk object +<% end # name == README.md -%> +gcompute_image <%= example_resource_name('test-image') -%> do + action :create + source_disk <%= example_resource_name('data-disk-1') %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~instance.rb b/products/compute/files/examples~cookbook~instance.rb new file mode 100644 index 000000000000..fb6a7ccfa6f4 --- /dev/null +++ b/products/compute/files/examples~cookbook~instance.rb @@ -0,0 +1,110 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-west1-a' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +# Google::Functions must be included at runtime to ensure that the +# gcompute_image_family function can be used in gcompute_disk blocks. +::Chef::Resource.send(:include, Google::Functions) + +gcompute_disk <%= example_resource_name('instance-test-os-1') -%> do + action :create + source_image gcompute_image_family('ubuntu-1604-lts', 'ubuntu-os-cloud') + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_network <%= example_resource_name('mynetwork-test') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_region 'us-west1' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_address <%= example_resource_name('instance-test-ip') -%> do + action :create + region 'us-west1' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_machine_type 'n1-standard-1' do + action :create + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Power Tips: +# 1) Remember to define the resources needed to allocate the VM: +# a) gcompute_disk (to be used in 'disks' property) +# b) gcompute_network (to be used in 'network' property) +# c) gcompute_address (to be used in 'access_configs', if your machine +# needs external ingress access) +# d) gcompute_zone (to determine where the VM will be allocated) +# e) gcompute_machine_type (to determine the kind of machine to be created) +# 2) Don't forget to define a source_image for the OS of the boot disk +# a) You can use the provided gcompute_image_family function to specify the +# latest version of an operating system of a given family +# e.g. Ubuntu 16.04 +<% end -%> +gcompute_instance <%= example_resource_name('instance-test') -%> do + action :create + machine_type 'n1-standard-1' + disks [ + { + boot: true, + auto_delete: true, + source: <%= example_resource_name('instance-test-os-1') %> + } + ] + metadata ( + 'startup-script-url' => 'gs://graphite-playground/bootstrap.sh', + 'cost-center' => '12345' + ) + network_interfaces [ + { + network: <%= example_resource_name('mynetwork-test') %>, + access_configs: [ + { + name: 'External NAT', + nat_ip: <%= example_resource_name('instance-test-ip') -%>, + type: 'ONE_TO_ONE_NAT' + } + ] + } + ] + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~instance_group.rb b/products/compute/files/examples~cookbook~instance_group.rb new file mode 100644 index 000000000000..03e593ea9280 --- /dev/null +++ b/products/compute/files/examples~cookbook~instance_group.rb @@ -0,0 +1,53 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +<% if name == "README.md" -%> +# Instance group requires a network and a region, so define them in your recipe: +# - gcompute_network 'my-network' do ... end +# - gcompute_zone 'my-zone' do ... end +<% else # name == README.md -%> +gcompute_network <%= example_resource_name('my-network') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_zone 'us-central1-a' do + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end # name == README.md -%> +gcompute_instance_group <%= example_resource_name('my-masters') -%> do + action :create + named_ports [ + { + name: 'test-port', + port: 8141 + } + ] + network <%= example_resource_name('my-network') %> + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~instance_template.rb b/products/compute/files/examples~cookbook~instance_template.rb new file mode 100644 index 000000000000..19f032fb1476 --- /dev/null +++ b/products/compute/files/examples~cookbook~instance_template.rb @@ -0,0 +1,100 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-west1-a' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_machine_type 'n1-standard-1' do + action :create + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +# TODO(nelsonjr): Reactiveate example based on disk once http://b/66871792 is +# resolved. +#gcompute_disk <%= example_resource_name('os-disk-1') -%> do +# action :create +# zone 'us-west1-a' +# source_image 'projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts' +# project 'google.com:graphite-playground' +# credential 'mycred' +#end + +# Google::Functions must be included at runtime to ensure that the +# gcompute_image_family function can be used in gcompute_disk blocks. +::Chef::Resource.send(:include, Google::Functions) + +gcompute_network <%= example_resource_name('mynetwork-test') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Power Tips: +# 1) Remember to define the resources needed to allocate the VM: +# a) gcompute_disk_type (to be used in 'diskType' property) +# b) gcompute_machine_type (to be used in 'machine_type' property) +# c) gcompute_network (to be used in 'network_interfaces' property) +# d) gcompute_subnetwork (to be used in the 'subnetwork' property) +# e) gcompute_disk (to be used in the 'sourceDisk' property) +# 2) Don't forget to define a source_image for the OS of the boot disk +<% end -%> +<% res_name = example_resource_name('instance-template-test') -%> +gcompute_instance_template <%= res_name -%> do + action :create + properties( + machine_type: 'n1-standard-1', + disks: [ + { + # Tip: Auto delete will prevent disks from being left behind on + # deletion. + auto_delete: true, + boot: true, + initialize_params: { + disk_size_gb: 100, + source_image: + gcompute_image_family('ubuntu-1604-lts', 'ubuntu-os-cloud') + } + } + ], + metadata: { + 'startup-script-url' => 'gs://graphite-playground/bootstrap.sh', + 'cost-center' => '12345' + }, + network_interfaces: [ + { + access_configs: { + name: 'test-config', + type: 'ONE_TO_ONE_NAT', + }, + network: <%= example_resource_name('mynetwork-test') %> + } + ] + ) + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~license.rb b/products/compute/files/examples~cookbook~license.rb new file mode 100644 index 000000000000..cd38efefbb3e --- /dev/null +++ b/products/compute/files/examples~cookbook~license.rb @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_license <%= example_resource_name('test-license') -%> do + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~machine_type.rb b/products/compute/files/examples~cookbook~machine_type.rb new file mode 100644 index 000000000000..fa6cb6f158b4 --- /dev/null +++ b/products/compute/files/examples~cookbook~machine_type.rb @@ -0,0 +1,35 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-west1-a' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end -%> +gcompute_machine_type 'n1-standard-1' do + action :create + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~network.rb b/products/compute/files/examples~cookbook~network.rb new file mode 100644 index 000000000000..9632fd5ccb3c --- /dev/null +++ b/products/compute/files/examples~cookbook~network.rb @@ -0,0 +1,29 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_network <%= example_resource_name('mynetwork') -%> do + action :create + auto_create_subnetworks true + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~network~auto.rb b/products/compute/files/examples~cookbook~network~auto.rb new file mode 100644 index 000000000000..99fa01107620 --- /dev/null +++ b/products/compute/files/examples~cookbook~network~auto.rb @@ -0,0 +1,50 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise "Missing parameter 'network_id'. Please read docs at #{__FILE__}" \ + unless ENV.key?('network_id') +<% end -%> +# TODO(alexstephen): Create a test case to verify that errors are properly +# displayed, such as a block like the one below, which will fail because you +# cannot specify auto_create_subnetworks and ipv4Range at the same time: +# | gcompute_network { "mynetwork-#{ENV['network_id']}" do +# | auto_create_subnetworks true +# | ipv4_range '192.168.0.0/16' +# | gateway_ipv4 '192.168.0.1' +# | project 'google.com:graphite-playground' +# | credential 'mycred' +# | end + +# The environment variable 'network_id' defines a suffix for a network name when +# using this example. If running from the command line, you can pass this suffix +# in via the command line: +# +# network_id="some_suffix" chef-client -z --runlist \ +# "recipe[gcompute::examples~network~auto]" +puts 'Creating network with automatically assigned subnetworks' +<% res_name = 'mynetwork-#{ENV[\'network_id\']}' -%> +gcompute_network <%= example_resource_name(res_name) -%> do + action :create + auto_create_subnetworks true + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~network~convert_to_custom.rb b/products/compute/files/examples~cookbook~network~convert_to_custom.rb new file mode 100644 index 000000000000..a620c098dca7 --- /dev/null +++ b/products/compute/files/examples~cookbook~network~convert_to_custom.rb @@ -0,0 +1,40 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise "Missing parameter 'network_id'. Please read docs at #{__FILE__}" \ + unless ENV.key?('network_id') + +<% end -%> +# The environment variable 'network_id' defines a suffix for a network name when +# using this example. If running from the command line, you can pass this suffix +# in via the command line: +# +# network_id="some_suffix" chef-client -z --runlist \ +# "recipe[gcompute::examples~network~auto]" +puts 'Converting network to Custom' +<% res_name = 'mynetwork-#{ENV[\'network_id\']}' -%> +gcompute_network <%= example_resource_name(res_name) -%> do + action :create + auto_create_subnetworks false + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~network~custom.rb b/products/compute/files/examples~cookbook~network~custom.rb new file mode 100644 index 000000000000..dd999efe2aaa --- /dev/null +++ b/products/compute/files/examples~cookbook~network~custom.rb @@ -0,0 +1,39 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise "Missing parameter 'network_id'. Please read docs at #{__FILE__}" \ + unless ENV.key?('network_id') +<% end -%> +# The environment variable 'network_id' defines a suffix for a network name when +# using this example. If running from the command line, you can pass this suffix +# in via the command line: +# +# network_id="some_suffix" chef-client -z --runlist \ +# "recipe[gcompute::examples~network~auto]" +puts 'Creating network without automatically assigned subnetworks' +<% res_name = 'mynetwork-#{ENV[\'network_id\']}' -%> +gcompute_network <%= example_resource_name(res_name) -%> do + action :create + auto_create_subnetworks false + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~network~legacy.rb b/products/compute/files/examples~cookbook~network~legacy.rb new file mode 100644 index 000000000000..0899345c7b12 --- /dev/null +++ b/products/compute/files/examples~cookbook~network~legacy.rb @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise "Missing parameter 'network_id'. Please read docs at #{__FILE__}" \ + unless ENV.key?('network_id') +<% end -%> +# The environment variable 'network_id' defines a suffix for a network name when +# using this example. If running from the command line, you can pass this suffix +# in via the command line: +# +# network_id="some_suffix" chef-client -z --runlist \ +# "recipe[gcompute::examples~network~auto]" +puts 'Creating network in Legacy mode' +<% res_name = 'mynetwork-#{ENV[\'network_id\']}' -%> +gcompute_network <%= example_resource_name(res_name) -%> do + # On a legacy network you cannot specify the auto_create_subnetworks + # parameter. + # | auto_create_subnetworks => false, + action :create + ipv4_range '192.168.0.0/16' + gateway_ipv4 '192.168.0.1' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~readme.rb b/products/compute/files/examples~cookbook~readme.rb new file mode 100644 index 000000000000..90cc47351615 --- /dev/null +++ b/products/compute/files/examples~cookbook~readme.rb @@ -0,0 +1,89 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% end -%> +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-west1-a' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_disk 'instance-test-os-1' do + action :create + source_image 'projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts' + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_network 'mynetwork-test' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_region 'us-west1' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_address 'instance-test-ip' do + action :create + region 'us-west1' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_machine_type 'n1-standard-1' do + action :create + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_instance 'instance-test' do + action :create + machine_type 'n1-standard-1' + disks [ + { + boot: true, + auto_delete: true, + source: 'instance-test-os-1' + } + ] + network_interfaces [ + { + network: 'mynetwork-test', + access_configs: [ + { + name: 'External NAT', + nat_ip: 'instance-test-ip', + type: 'ONE_TO_ONE_NAT' + } + ] + } + ] + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~region.rb b/products/compute/files/examples~cookbook~region.rb new file mode 100644 index 000000000000..a488084309d4 --- /dev/null +++ b/products/compute/files/examples~cookbook~region.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_region 'us-west1' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~route.rb b/products/compute/files/examples~cookbook~route.rb new file mode 100644 index 000000000000..e92f20034190 --- /dev/null +++ b/products/compute/files/examples~cookbook~route.rb @@ -0,0 +1,51 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +<% if name == "README.md" -%> +# Subnetwork requires a network and a region, so define them in your recipe: +# - gcompute_network 'my-network' do ... end +# - gcompute_region 'some-region' do ... end +<% else # name == README.md -%> +gcompute_network <%= example_resource_name('my-network') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_region <%= example_resource_name('some-region') -%> do + action :create + r_label 'us-west1' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end # name == README.md -%> +gcompute_route <%= example_resource_name('corp-route') -%> do + action :create + dest_range '192.168.6.0/24' + next_hop_gateway 'global/gateways/default-internet-gateway' + tags %w[backends databases] # %w[] best for single words. use ['.'] w/ spaces + network <%= example_resource_name('my-network') %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~ssl_certificate.rb b/products/compute/files/examples~cookbook~ssl_certificate.rb new file mode 100644 index 000000000000..655b2388f6fc --- /dev/null +++ b/products/compute/files/examples~cookbook~ssl_certificate.rb @@ -0,0 +1,67 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +# ******* +# WARNING: This recipe is for example purposes only. It is *not* advisable to +# have the key embedded like this because if you check this file into source +# control you are publishing the private key to whomever can access the source +# code. +# ******* + +gcompute_ssl_certificate <%= example_resource_name('my-site-ssl-cert') -%> do + action :create + certificate( + <<-CERTIFICATE + -----BEGIN CERTIFICATE----- + MIICqjCCAk+gAwIBAgIJAIuJ+0352Kq4MAoGCCqGSM49BAMCMIGwMQswCQYDVQQG + EwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxFTAT + BgNVBAoMDEdvb2dsZSwgSW5jLjEeMBwGA1UECwwVR29vZ2xlIENsb3VkIFBsYXRm + b3JtMR8wHQYDVQQDDBZ3d3cubXktc2VjdXJlLXNpdGUuY29tMSEwHwYJKoZIhvcN + AQkBFhJuZWxzb25hQGdvb2dsZS5jb20wHhcNMTcwNjI4MDQ1NjI2WhcNMjcwNjI2 + MDQ1NjI2WjCBsDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xETAP + BgNVBAcMCEtpcmtsYW5kMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xHjAcBgNVBAsM + FUdvb2dsZSBDbG91ZCBQbGF0Zm9ybTEfMB0GA1UEAwwWd3d3Lm15LXNlY3VyZS1z + aXRlLmNvbTEhMB8GCSqGSIb3DQEJARYSbmVsc29uYUBnb29nbGUuY29tMFkwEwYH + KoZIzj0CAQYIKoZIzj0DAQcDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ + 4mzkzTv0dXyB750fOGN02HtkpBOZzzvUARTR10JQoSe2/5PIwaNQME4wHQYDVR0O + BBYEFKIQC3A2SDpxcdfn0YLKineDNq/BMB8GA1UdIwQYMBaAFKIQC3A2SDpxcdfn + 0YLKineDNq/BMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhALs4vy+O + M3jcqgA4fSW/oKw6UJxp+M6a+nGMX+UJR3YgAiEAvvl39QRVAiv84hdoCuyON0lJ + zqGNhIPGq2ULqXKK8BY= + -----END CERTIFICATE----- + CERTIFICATE + .split("\n").map(&:strip).join("\n") + ) + private_key( + <<-PRIVATE_KEY + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIObtRo8tkUqoMjeHhsOh2ouPpXCgBcP+EDxZCB/tws15oAoGCCqGSM49 + AwEHoUQDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ4mzkzTv0dXyB750f + OGN02HtkpBOZzzvUARTR10JQoSe2/5PIwQ== + -----END EC PRIVATE KEY----- + PRIVATE_KEY + .split("\n").map(&:strip).join("\n") + ) + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~subnetwork.rb b/products/compute/files/examples~cookbook~subnetwork.rb new file mode 100644 index 000000000000..acf4ae535711 --- /dev/null +++ b/products/compute/files/examples~cookbook~subnetwork.rb @@ -0,0 +1,51 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +<% if name == "README.md" -%> +# Subnetwork requires a network and a region, so define them in your recipe: +# - gcompute_network 'my-network' do ... end +# - gcompute_region 'some-region' do ... end +<% else # name == README.md -%> +gcompute_network <%= example_resource_name('mynetwork-subnetwork') -%> do + action :create + auto_create_subnetworks false + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_region <%= example_resource_name('some-region') -%> do + action :create + r_label 'us-west1' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end # name == README.md -%> +gcompute_subnetwork <%= example_resource_name('servers') -%> do + action :create + ip_cidr_range '172.16.0.0/16' + network <%= example_resource_name('mynetwork-subnetwork') %> + region <%= example_resource_name('some-region') %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~cookbook~target_pool.rb b/products/compute/files/examples~cookbook~target_pool.rb new file mode 100644 index 000000000000..d6367ca420fc --- /dev/null +++ b/products/compute/files/examples~cookbook~target_pool.rb @@ -0,0 +1,23 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> diff --git a/products/compute/files/examples~cookbook~zone.rb b/products/compute/files/examples~cookbook~zone.rb new file mode 100644 index 000000000000..2809d8a9a148 --- /dev/null +++ b/products/compute/files/examples~cookbook~zone.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gcompute_zone 'us-west1-a' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/compute/files/examples~delete_address.pp b/products/compute/files/examples~delete_address.pp new file mode 100644 index 000000000000..4243ac659c2a --- /dev/null +++ b/products/compute/files/examples~delete_address.pp @@ -0,0 +1,34 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_region { <%= example_resource_name('some-region') -%>: + name => 'us-west1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_address { <%= example_resource_name('test1') -%>: + ensure => absent, + region => <%= example_resource_name('some-region') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_backend_bucket.pp b/products/compute/files/examples~delete_backend_bucket.pp new file mode 100644 index 000000000000..9b6b0d9010d3 --- /dev/null +++ b/products/compute/files/examples~delete_backend_bucket.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_backend_bucket { <%= example_resource_name('be-bucket-connection') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_backend_service.pp b/products/compute/files/examples~delete_backend_service.pp new file mode 100644 index 000000000000..861b38011d55 --- /dev/null +++ b/products/compute/files/examples~delete_backend_service.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_backend_service { <%= example_resource_name('my-app-backend') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_disk.pp b/products/compute/files/examples~delete_disk.pp new file mode 100644 index 000000000000..5ffa186e41aa --- /dev/null +++ b/products/compute/files/examples~delete_disk.pp @@ -0,0 +1,33 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_disk { <%= example_resource_name('data-disk-1') -%>: + ensure => absent, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_firewall.pp b/products/compute/files/examples~delete_firewall.pp new file mode 100644 index 000000000000..f95ef6057056 --- /dev/null +++ b/products/compute/files/examples~delete_firewall.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_firewall { <%= example_resource_name('test-fw-allow-ssh') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_forwarding_rule.pp b/products/compute/files/examples~delete_forwarding_rule.pp new file mode 100644 index 000000000000..e5db23993832 --- /dev/null +++ b/products/compute/files/examples~delete_forwarding_rule.pp @@ -0,0 +1,34 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_region { <%= example_resource_name('some-region') -%>: + name => 'us-west1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_forwarding_rule { <%= example_resource_name('test1') -%>: + ensure => absent, + region => <%= example_resource_name('some-region') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_global_address.pp b/products/compute/files/examples~delete_global_address.pp new file mode 100644 index 000000000000..f1c76efeff33 --- /dev/null +++ b/products/compute/files/examples~delete_global_address.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_global_address { <%= example_resource_name('my-app-lb-address') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_global_forwarding_rule.pp b/products/compute/files/examples~delete_global_forwarding_rule.pp new file mode 100644 index 000000000000..703edeebc074 --- /dev/null +++ b/products/compute/files/examples~delete_global_forwarding_rule.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_global_forwarding_rule { <%= example_resource_name('test1') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_health_check.pp b/products/compute/files/examples~delete_health_check.pp new file mode 100644 index 000000000000..7fad64337e34 --- /dev/null +++ b/products/compute/files/examples~delete_health_check.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_health_check { <%= example_resource_name('my-app-tcp-hc') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_http_health_check.pp b/products/compute/files/examples~delete_http_health_check.pp new file mode 100644 index 000000000000..fb53c8cc6ad0 --- /dev/null +++ b/products/compute/files/examples~delete_http_health_check.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_http_health_check { <%= example_resource_name('my-app-http-hc') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_https_health_check.pp b/products/compute/files/examples~delete_https_health_check.pp new file mode 100644 index 000000000000..871b2bb5122a --- /dev/null +++ b/products/compute/files/examples~delete_https_health_check.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_https_health_check { <%= example_resource_name('my-app-https-hc') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_image.pp b/products/compute/files/examples~delete_image.pp new file mode 100644 index 000000000000..f0956aebbc29 --- /dev/null +++ b/products/compute/files/examples~delete_image.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_image { <%= example_resource_name('test-image') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred' +} diff --git a/products/compute/files/examples~delete_instance.pp b/products/compute/files/examples~delete_instance.pp new file mode 100644 index 000000000000..c0585a3b2f6e --- /dev/null +++ b/products/compute/files/examples~delete_instance.pp @@ -0,0 +1,33 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_instance { <%= example_resource_name('instance-test') -%>: + ensure => absent, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_instance_group.pp b/products/compute/files/examples~delete_instance_group.pp new file mode 100644 index 000000000000..aae3ee35d1db --- /dev/null +++ b/products/compute/files/examples~delete_instance_group.pp @@ -0,0 +1,33 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_instance_group { <%= example_resource_name('my-puppet-masters') -%>: + ensure => absent, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_instance_group_manager.pp b/products/compute/files/examples~delete_instance_group_manager.pp new file mode 100644 index 000000000000..47fcead1dbd2 --- /dev/null +++ b/products/compute/files/examples~delete_instance_group_manager.pp @@ -0,0 +1,33 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-west1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_instance_group_manager { <%= example_resource_name('test1') -%>: + ensure => absent, + zone => 'us-west1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_instance_template.pp b/products/compute/files/examples~delete_instance_template.pp new file mode 100644 index 000000000000..d4d1a38cf256 --- /dev/null +++ b/products/compute/files/examples~delete_instance_template.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_instance_template { <%= example_resource_name('instance-template') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_network.pp b/products/compute/files/examples~delete_network.pp new file mode 100644 index 000000000000..ef33262848f9 --- /dev/null +++ b/products/compute/files/examples~delete_network.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_network { <%= example_resource_name('mynetwork-${network_id}') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_route.pp b/products/compute/files/examples~delete_route.pp new file mode 100644 index 000000000000..c2e39510d726 --- /dev/null +++ b/products/compute/files/examples~delete_route.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_route { <%= example_resource_name('corp-route') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_snapshot.pp b/products/compute/files/examples~delete_snapshot.pp new file mode 100644 index 000000000000..e9fcb2d71341 --- /dev/null +++ b/products/compute/files/examples~delete_snapshot.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_snapshot { <%= example_resource_name('data-disk-snapshot-1') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_ssl_certificate.pp b/products/compute/files/examples~delete_ssl_certificate.pp new file mode 100644 index 000000000000..94cfa584bab8 --- /dev/null +++ b/products/compute/files/examples~delete_ssl_certificate.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_ssl_certificate { <%= example_resource_name('sample-certificate') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_subnetwork.pp b/products/compute/files/examples~delete_subnetwork.pp new file mode 100644 index 000000000000..5a96e57102cd --- /dev/null +++ b/products/compute/files/examples~delete_subnetwork.pp @@ -0,0 +1,37 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_region { <%= example_resource_name('some-region') -%>: + name => 'us-west1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else # name == README.md -%> +# Subnetwork requires a network and a region, so define them in your manifest: +# - gcompute_region { 'some-region': ... } +<% end # name == README.md -%> +gcompute_subnetwork { <%= example_resource_name('servers') -%>: + ensure => absent, + region => <%= example_resource_name('some-region') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_target_http_proxy.pp b/products/compute/files/examples~delete_target_http_proxy.pp new file mode 100644 index 000000000000..c9bb47ca4ae5 --- /dev/null +++ b/products/compute/files/examples~delete_target_http_proxy.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_target_http_proxy { <%= example_resource_name('my-http-proxy') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_target_https_proxy.pp b/products/compute/files/examples~delete_target_https_proxy.pp new file mode 100644 index 000000000000..85a919340fb8 --- /dev/null +++ b/products/compute/files/examples~delete_target_https_proxy.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_target_https_proxy { <%= example_resource_name('my-https-proxy') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_target_pool.pp b/products/compute/files/examples~delete_target_pool.pp new file mode 100644 index 000000000000..e87e621ab3f6 --- /dev/null +++ b/products/compute/files/examples~delete_target_pool.pp @@ -0,0 +1,34 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_region { <%= example_resource_name('some-region') -%>: + name => 'us-west1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_target_pool { <%= example_resource_name('test1') -%>: + ensure => absent, + region => <%= example_resource_name('some-region') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_target_ssl_proxy.pp b/products/compute/files/examples~delete_target_ssl_proxy.pp new file mode 100644 index 000000000000..621c89406c4a --- /dev/null +++ b/products/compute/files/examples~delete_target_ssl_proxy.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_target_ssl_proxy { <%= example_resource_name('my-ssl-proxy') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_target_tcp_proxy.pp b/products/compute/files/examples~delete_target_tcp_proxy.pp new file mode 100644 index 000000000000..c7a105537fe7 --- /dev/null +++ b/products/compute/files/examples~delete_target_tcp_proxy.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_target_tcp_proxy { <%= example_resource_name('my-tcp-proxy') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~delete_url_map.pp b/products/compute/files/examples~delete_url_map.pp new file mode 100644 index 000000000000..a385ef3d5752 --- /dev/null +++ b/products/compute/files/examples~delete_url_map.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_url_map { <%= example_resource_name('my-url-map') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~disk.pp b/products/compute/files/examples~disk.pp new file mode 100644 index 000000000000..dd5f4982e32d --- /dev/null +++ b/products/compute/files/examples~disk.pp @@ -0,0 +1,37 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_disk { <%= example_resource_name('data-disk-1') -%>: + ensure => present, + size_gb => 50, + disk_encryption_key => { + raw_key => 'SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=', + }, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~disk_type.pp b/products/compute/files/examples~disk_type.pp new file mode 100644 index 000000000000..c99d850e2ab1 --- /dev/null +++ b/products/compute/files/examples~disk_type.pp @@ -0,0 +1,34 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_disk_type { 'pd-standard': + default_disk_size_gb => 500, + deprecated_deleted => undef, # undef = not deprecated + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~firewall.pp b/products/compute/files/examples~firewall.pp new file mode 100644 index 000000000000..1b8e6629df6f --- /dev/null +++ b/products/compute/files/examples~firewall.pp @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_firewall { <%= example_resource_name('test-fw-allow-ssh') -%>: + ensure => present, + allowed => [ + { + ip_protocol => 'tcp', + ports => [ + '22', + ], + }, + ], + target_tags => [ + 'test-ssh-server', + 'staging-ssh-server', + ], + source_tags => [ + 'test-ssh-clients', + ], + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~firewall~change1.pp b/products/compute/files/examples~firewall~change1.pp new file mode 100644 index 000000000000..6f4cc41d4196 --- /dev/null +++ b/products/compute/files/examples~firewall~change1.pp @@ -0,0 +1,41 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_firewall { 'test-firewall-allow-ssh': + ensure => present, + allowed => [ + { + ip_protocol => 'tcp', + ports => [ + '22', + ], + }, + { + ip_protocol => 'tcp', + ports => [ + '2222', + ], + }, + ], + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~forwarding_rule.pp b/products/compute/files/examples~forwarding_rule.pp new file mode 100644 index 000000000000..8172d2ff2aa6 --- /dev/null +++ b/products/compute/files/examples~forwarding_rule.pp @@ -0,0 +1,55 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_region { <%= example_resource_name('some-region') -%>: + name => 'us-west1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_address { <%= example_resource_name('some-address') -%>: + ensure => present, + region => <%= example_resource_name('some-region') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_target_pool { <%= example_resource_name('target-pool') -%>: + ensure => present, + region => <%= example_resource_name('some-region') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_forwarding_rule { <%= example_resource_name('test1') -%>: + ensure => present, + ip_address => gcompute_address_ref( + <%= example_resource_name('some-address') -%>, + 'us-west1', 'google.com:graphite-playground' + ), + ip_protocol => 'TCP', + port_range => '80', + target => <%= example_resource_name('target-pool') -%>, + region => <%= example_resource_name('some-region') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~global_address.pp b/products/compute/files/examples~global_address.pp new file mode 100644 index 000000000000..1c65903d93de --- /dev/null +++ b/products/compute/files/examples~global_address.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_global_address { <%= example_resource_name('my-app-lb-address') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~global_forwarding_rule.pp b/products/compute/files/examples~global_forwarding_rule.pp new file mode 100644 index 000000000000..f91e909485ac --- /dev/null +++ b/products/compute/files/examples~global_forwarding_rule.pp @@ -0,0 +1,82 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_global_address { <%= example_resource_name('my-app-lb-address') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_instance_group { <%= example_resource_name('my-puppet-masters') -%>: + ensure => present, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_backend_service { <%= example_resource_name('my-app-backend') -%>: + ensure => present, + backends => [ + { group => <%= example_resource_name('my-puppet-masters') -%> }, + ], + enable_cdn => true, + health_checks => [ + gcompute_health_check_ref('another-hc', 'google.com:graphite-playground'), + ], + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_url_map { <%= example_resource_name('my-url-map') -%>: + ensure => present, + default_service => <%= example_resource_name('my-app-backend') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_target_http_proxy { <%= example_resource_name('my-http-proxy') -%>: + ensure => present, + url_map => <%= example_resource_name('my-url-map') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_global_forwarding_rule { <%= example_resource_name('test1') -%>: + ensure => present, + ip_address => gcompute_global_address_ref( + <%= example_resource_name('my-app-lb-address') -%>, + 'google.com:graphite-playground' + ), + ip_protocol => 'TCP', + port_range => '80', + target => gcompute_target_http_proxy_ref( + <%= example_resource_name('my-http-proxy') -%>, + 'google.com:graphite-playground' + ), + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~health_check.pp b/products/compute/files/examples~health_check.pp new file mode 100644 index 000000000000..dff073a104a2 --- /dev/null +++ b/products/compute/files/examples~health_check.pp @@ -0,0 +1,36 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_health_check { <%= example_resource_name('my-app-tcp-hc') -%>: + ensure => present, + type => 'TCP', + tcp_health_check => { + port_name => 'service-health', + request => 'ping', + response => 'pong', + }, + healthy_threshold => 10, + timeout_sec => 2, + unhealthy_threshold => 5, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~http_health_check.pp b/products/compute/files/examples~http_health_check.pp new file mode 100644 index 000000000000..cd0bab89e8a6 --- /dev/null +++ b/products/compute/files/examples~http_health_check.pp @@ -0,0 +1,31 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_http_health_check { <%= example_resource_name('my-app-http-hc') -%>: + ensure => present, + healthy_threshold => 10, + port => 8080, + timeout_sec => 2, + unhealthy_threshold => 5, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~https_health_check.pp b/products/compute/files/examples~https_health_check.pp new file mode 100644 index 000000000000..04d5d475730a --- /dev/null +++ b/products/compute/files/examples~https_health_check.pp @@ -0,0 +1,31 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_https_health_check { <%= example_resource_name('my-app-https-hc') -%>: + ensure => present, + healthy_threshold => 10, + port => 8080, + timeout_sec => 2, + unhealthy_threshold => 5, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~image.pp b/products/compute/files/examples~image.pp new file mode 100644 index 000000000000..9e00452dadb1 --- /dev/null +++ b/products/compute/files/examples~image.pp @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_disk { <%= example_resource_name('data-disk-1') -%>: + ensure => present, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else -%> +# Tip: Be sure to include a valid gcompute_disk object +<% end # name == README.md -%> +gcompute_image { <%= example_resource_name('test-image') -%>: + ensure => present, + source_disk => <%= example_resource_name('data-disk-1') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred' +} diff --git a/products/compute/files/examples~instance.pp b/products/compute/files/examples~instance.pp new file mode 100644 index 000000000000..cc4c6fe35c6d --- /dev/null +++ b/products/compute/files/examples~instance.pp @@ -0,0 +1,114 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_disk { <%= example_resource_name('instance-test-os-1') -%>: + ensure => present, + size_gb => 50, + source_image => + 'projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts', + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +# Tips +# 1) You can use network 'default' if do not use VLAN or other traffic +# seggregation on your project. +# 2) Don't forget to define the firewall rules if you specify a custom +# network to ensure the traffic can reach your machine +gcompute_network { <%= example_resource_name('default') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_region { 'us-central1': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +# Defines the machine type to be used by the VM. This definition is required +# only once per catalog as it is shared to any objects that use the +# 'n1-standard-1' defined below. +gcompute_machine_type { 'n1-standard-1': + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +# Ensures the 'instance-test-ip' external IP address exists. If it does not +# exist it will allocate an ephemeral one. +gcompute_address { <%= example_resource_name('instance-test-ip') -%>: + region => 'us-central1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else -%> +# Power Tips: +# 1) Remember to define the resources needed to allocate the VM: +# a) gcompute_disk_type (to be used in 'diskType' property) +# b) gcompute_machine_type (to be used in 'machine_type' property) +# c) gcompute_network (to be used in 'network_interfaces' property) +# d) gcompute_subnetwork (to be used in the 'subnetwork' property) +# e) gcompute_disk (to be used in the 'sourceDisk' property) +# f) gcompute_address (to be used in 'access_configs', if your machine +# needs external ingress access) +# 2) Don't forget to define a source_image for the OS of the boot disk +# a) You can use the provided gcompute_image_family function to specify the +# latest version of an operating system of a given family +# e.g. Ubuntu 16.04 +<% end # name == README.md -%> +gcompute_instance { <%= example_resource_name('instance-test') -%>: + ensure => present, + machine_type => 'n1-standard-1', + disks => [ + { + auto_delete => true, + boot => true, + source => <%= example_resource_name('instance-test-os-1') %> + } + ], + metadata => { + startup-script-url => 'gs://graphite-playground/bootstrap.sh', + cost-center => '12345', + }, + network_interfaces => [ + { + network => <%= example_resource_name('default') %>, + access_configs => [ + { + name => 'External NAT', + nat_ip => <%= example_resource_name('instance-test-ip') -%>, + type => 'ONE_TO_ONE_NAT', + }, + ], + } + ], + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~instance_group.pp b/products/compute/files/examples~instance_group.pp new file mode 100644 index 000000000000..09c0b7aabc9c --- /dev/null +++ b/products/compute/files/examples~instance_group.pp @@ -0,0 +1,51 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +<% if name == "README.md" -%> +# Instance group requires a network, so define them in your manifest: +# - gcompute_network { 'my-network': ensure => present } +<% else # name == README.md -%> +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_network { <%= example_resource_name('my-network') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_instance_group { <%= example_resource_name('my-puppet-masters') -%>: + ensure => present, + named_ports => [ + { + name => 'puppet', + port => 8140, + }, + ], + network => <%= example_resource_name('my-network') -%>, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~instance_group_manager.pp b/products/compute/files/examples~instance_group_manager.pp new file mode 100644 index 000000000000..3828a200e95f --- /dev/null +++ b/products/compute/files/examples~instance_group_manager.pp @@ -0,0 +1,80 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-west1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_machine_type { 'n1-standard-1': + project => 'google.com:graphite-playground', + zone => 'us-west1-a', + credential => 'mycred', +} + +gcompute_network { <%= example_resource_name('mynetwork-test') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_instance_template { <%= example_resource_name('instance-template') -%>: + ensure => present, + properties => { + machine_type => 'n1-standard-1', + disks => [ + { + # Tip: Auto delete will prevent disks from being left behind on + # deletion. + auto_delete => true, + boot => true, + initialize_params => { + source_image => + gcompute_image_family('ubuntu-1604-lts', 'ubuntu-os-cloud'), + } + } + ], + network_interfaces => [ + { + access_configs => [ + { + name => 'External NAT', + type => 'ONE_TO_ONE_NAT', + }, + ], + network => <%= example_resource_name('mynetwork-test') %>, + } + ] + }, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_instance_group_manager { <%= example_resource_name('test1') -%>: + ensure => present, + base_instance_name => <%= example_resource_name('test1-child') -%>, + instance_template => <%= example_resource_name('instance-template') -%>, + target_size => 3, + zone => 'us-west1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~instance_template.pp b/products/compute/files/examples~instance_template.pp new file mode 100644 index 000000000000..046f0b593842 --- /dev/null +++ b/products/compute/files/examples~instance_template.pp @@ -0,0 +1,95 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_machine_type { 'n1-standard-1': + project => 'google.com:graphite-playground', + zone => 'us-central1-a', + credential => 'mycred', +} + +# TODO(nelsonjr): Reactivate example based on disk once http://b/66871792 is +# resolved. +# | gcompute_disk { <%= example_resource_name('os-disk-1') -%>: +# | ensure => present, +# | zone => 'us-central1-a', +# | source_image => +# | gcompute_image_family('ubuntu-1604-lts', 'ubuntu-os-cloud'), +# | project => 'google.com:graphite-playground', +# | credential => 'mycred', +# | } + +gcompute_network { <%= example_resource_name('mynetwork-test') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else -%> +# Power Tips: +# 1) Remember to define the resources needed to allocate the VM: +# a) gcompute_disk_type (to be used in 'diskType' property) +# b) gcompute_machine_type (to be used in 'machine_type' property) +# c) gcompute_network (to be used in 'network_interfaces' property) +# d) gcompute_subnetwork (to be used in the 'subnetwork' property) +# e) gcompute_disk (to be used in the 'sourceDisk' property) +# 2) Don't forget to define a source_image for the OS of the boot disk +# a) You can use the provided gcompute_image_family function to specify the +# latest version of an operating system of a given family +# e.g. Ubuntu 16.04 +<% end # name == README.md -%> +gcompute_instance_template { <%= example_resource_name('instance-template') -%>: + ensure => present, + properties => { + machine_type => 'n1-standard-1', + disks => [ + { + # Tip: Auto delete will prevent disks from being left behind on + # deletion. + auto_delete => true, + boot => true, + initialize_params => { + source_image => + gcompute_image_family('ubuntu-1604-lts', 'ubuntu-os-cloud'), + } + } + ], + metadata => { + 'startup-script-url' => 'gs://graphite-playground/bootstrap.sh', + 'cost-center' => '12345', + }, + network_interfaces => [ + { + access_configs => { + name => 'test-config', + type => 'ONE_TO_ONE_NAT', + }, + network => <%= example_resource_name('mynetwork-test') %>, + } + ] + }, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~license.pp b/products/compute/files/examples~license.pp new file mode 100644 index 000000000000..dedda8169360 --- /dev/null +++ b/products/compute/files/examples~license.pp @@ -0,0 +1,26 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_license { <%= example_resource_name('test-license') -%>: + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~machine_type.pp b/products/compute/files/examples~machine_type.pp new file mode 100644 index 000000000000..96cf3b3d538a --- /dev/null +++ b/products/compute/files/examples~machine_type.pp @@ -0,0 +1,32 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_machine_type { 'n1-standard-1': + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~network.pp b/products/compute/files/examples~network.pp new file mode 100644 index 000000000000..9531a707aa03 --- /dev/null +++ b/products/compute/files/examples~network.pp @@ -0,0 +1,25 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +# Automatically allocated network +<%= compile('products/compute/files/examples~network~auto.pp') -%> + +# Manually allocated network +<%= compile('products/compute/files/examples~network~custom.pp') -%> + +# Legacy network +<%= compile('products/compute/files/examples~network~legacy.pp') -%> + +# Converting automatic to custom network +<%= compile('products/compute/files/examples~network~convert_to_custom.pp') -%> diff --git a/products/compute/files/examples~network~auto.pp b/products/compute/files/examples~network~auto.pp new file mode 100644 index 000000000000..a71413d548c2 --- /dev/null +++ b/products/compute/files/examples~network~auto.pp @@ -0,0 +1,39 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# TODO(nelsonjr): Create a test case to verify that errors are properly +# displayed, such as a block like the one below, which will fail because you +# cannot specify auto_create_subnetworks and ipv4Range at the same time: +# | gcompute_network { "mynetwork-${network_id}": +# | auto_create_subnetworks => true, +# | ipv4_range => '192.168.0.0/16', +# | gateway_ipv4 => '192.168.0.1', +# | project => 'google.com:graphite-playground', +# | credential => 'mycred', +# | } + +notice('Creating network with automatically assigned subnetworks') +<% end # name == README.md -%> +gcompute_network { <%= example_resource_name('mynetwork-${network_id}') -%>: + auto_create_subnetworks => true, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~network~convert_to_custom.pp b/products/compute/files/examples~network~convert_to_custom.pp new file mode 100644 index 000000000000..48bf1fcee956 --- /dev/null +++ b/products/compute/files/examples~network~convert_to_custom.pp @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +notice('Converting network to Custom') +<% end # name == README.md -%> +gcompute_network { <%= example_resource_name('mynetwork-${network_id}') -%>: + auto_create_subnetworks => false, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~network~custom.pp b/products/compute/files/examples~network~custom.pp new file mode 100644 index 000000000000..d298d6b9b45e --- /dev/null +++ b/products/compute/files/examples~network~custom.pp @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +notice('Creating network without automatically assigned subnetworks') +<% end # name == README.md -%> +gcompute_network { <%= example_resource_name('mynetwork-${network_id}') -%>: + auto_create_subnetworks => false, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~network~legacy.pp b/products/compute/files/examples~network~legacy.pp new file mode 100644 index 000000000000..eefd4317f2e4 --- /dev/null +++ b/products/compute/files/examples~network~legacy.pp @@ -0,0 +1,32 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +notice('Creating network in Legacy mode') +<% end # name == README.md -%> +gcompute_network { <%= example_resource_name('mynetwork-${network_id}') -%>: + # On a legacy network you cannot specify the auto_create_subnetworks + # parameter. + # | auto_create_subnetworks => false, + ipv4_range => '192.168.0.0/16', + gateway_ipv4 => '192.168.0.1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~region.pp b/products/compute/files/examples~region.pp new file mode 100644 index 000000000000..65dbd4ee21e4 --- /dev/null +++ b/products/compute/files/examples~region.pp @@ -0,0 +1,26 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_region { 'us-west1': + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~route.pp b/products/compute/files/examples~route.pp new file mode 100644 index 000000000000..1e3233e7744f --- /dev/null +++ b/products/compute/files/examples~route.pp @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +<% if name == "README.md" -%> +# Route requires a network, so define them in your manifest: +# - gcompute_network { 'my-network': ensure => presnet } +<% else # name == README.md -%> +gcompute_network { <%= example_resource_name('my-network') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_route { <%= example_resource_name('corp-route') -%>: + ensure => present, + dest_range => '192.168.6.0/24', + next_hop_gateway => 'global/gateways/default-internet-gateway', + network => <%= example_resource_name('my-network') -%>, + tags => ['backends', 'databases'], + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~snapshot.pp b/products/compute/files/examples~snapshot.pp new file mode 100644 index 000000000000..366f90ce4541 --- /dev/null +++ b/products/compute/files/examples~snapshot.pp @@ -0,0 +1,51 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_disk { <%= example_resource_name('data-disk-1') -%>: + ensure => present, + size_gb => 50, + disk_encryption_key => { + raw_key => 'SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=', + }, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_snapshot { <%= example_resource_name('data-disk-snapshot-1') -%>: + ensure => present, + snapshot_encryption_key => { + raw_key => 'VGhpcyBpcyBhbiBlbmNyeXB0ZWQgc25hcHNob3QhISE=', + }, + source_disk_encryption_key => { + raw_key => 'SGVsbG8gZnJvbSBHb29nbGUgQ2xvdWQgUGxhdGZvcm0=', + }, + source => <%= example_resource_name('data-disk-1') -%>, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~ssl_certificate.pp b/products/compute/files/examples~ssl_certificate.pp new file mode 100644 index 000000000000..654cf9e4ffe2 --- /dev/null +++ b/products/compute/files/examples~ssl_certificate.pp @@ -0,0 +1,64 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +# ******* +# WARNING: This manifest is for example purposes only. It is *not* advisable to +# have the key embedded like this because if you check this file into source +# control you are publishing the private key to whomever can access the source +# code. Instead you should protect the key, and for example, use the file() +# function to read it from disk without writing it verbatim to the manifest: +# +# gcompute_ssl_certificate { ... +# ... +# private_key => file('/path/to/my/private/key.pem'), +# ... +# } +# ******* + +gcompute_ssl_certificate { <%= example_resource_name('sample-certificate') -%>: + ensure => present, + description => 'A certificate for test purposes only.', + project => 'google.com:graphite-playground', + credential => 'mycred', + certificate => '-----BEGIN CERTIFICATE----- +MIICqjCCAk+gAwIBAgIJAIuJ+0352Kq4MAoGCCqGSM49BAMCMIGwMQswCQYDVQQG +EwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxFTAT +BgNVBAoMDEdvb2dsZSwgSW5jLjEeMBwGA1UECwwVR29vZ2xlIENsb3VkIFBsYXRm +b3JtMR8wHQYDVQQDDBZ3d3cubXktc2VjdXJlLXNpdGUuY29tMSEwHwYJKoZIhvcN +AQkBFhJuZWxzb25hQGdvb2dsZS5jb20wHhcNMTcwNjI4MDQ1NjI2WhcNMjcwNjI2 +MDQ1NjI2WjCBsDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xETAP +BgNVBAcMCEtpcmtsYW5kMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xHjAcBgNVBAsM +FUdvb2dsZSBDbG91ZCBQbGF0Zm9ybTEfMB0GA1UEAwwWd3d3Lm15LXNlY3VyZS1z +aXRlLmNvbTEhMB8GCSqGSIb3DQEJARYSbmVsc29uYUBnb29nbGUuY29tMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ +4mzkzTv0dXyB750fOGN02HtkpBOZzzvUARTR10JQoSe2/5PIwaNQME4wHQYDVR0O +BBYEFKIQC3A2SDpxcdfn0YLKineDNq/BMB8GA1UdIwQYMBaAFKIQC3A2SDpxcdfn +0YLKineDNq/BMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhALs4vy+O +M3jcqgA4fSW/oKw6UJxp+M6a+nGMX+UJR3YgAiEAvvl39QRVAiv84hdoCuyON0lJ +zqGNhIPGq2ULqXKK8BY= +-----END CERTIFICATE-----', + private_key => '-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIObtRo8tkUqoMjeHhsOh2ouPpXCgBcP+EDxZCB/tws15oAoGCCqGSM49 +AwEHoUQDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ4mzkzTv0dXyB750f +OGN02HtkpBOZzzvUARTR10JQoSe2/5PIwQ== +-----END EC PRIVATE KEY-----', +} diff --git a/products/compute/files/examples~subnetwork.pp b/products/compute/files/examples~subnetwork.pp new file mode 100644 index 000000000000..f4879629e6f9 --- /dev/null +++ b/products/compute/files/examples~subnetwork.pp @@ -0,0 +1,49 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +<% if name == "README.md" -%> +# Subnetwork requires a network and a region, so define them in your manifest: +# - gcompute_network { 'my-network': ensure => present, ... } +# - gcompute_region { 'some-region': ... } +<% else # name == README.md -%> +gcompute_network { <%= example_resource_name('mynetwork-subnetwork') -%>: + ensure => present, + auto_create_subnetworks => false, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_region { <%= example_resource_name('some-region') -%>: + name => 'us-west1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_subnetwork { <%= example_resource_name('servers') -%>: + ensure => present, + ip_cidr_range => '172.16.0.0/16', + network => <%= example_resource_name('mynetwork-subnetwork') -%>, + region => <%= example_resource_name('some-region') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~target_http_proxy.pp b/products/compute/files/examples~target_http_proxy.pp new file mode 100644 index 000000000000..b5c9e2571fa7 --- /dev/null +++ b/products/compute/files/examples~target_http_proxy.pp @@ -0,0 +1,60 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_instance_group { <%= example_resource_name('my-puppet-masters') -%>: + ensure => present, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_backend_service { <%= example_resource_name('my-app-backend') -%>: + ensure => present, + backends => [ + { group => <%= example_resource_name('my-puppet-masters') -%> }, + ], + enable_cdn => true, + health_checks => [ + gcompute_health_check_ref('another-hc', 'google.com:graphite-playground'), + ], + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_url_map { <%= example_resource_name('my-url-map') -%>: + ensure => present, + default_service => <%= example_resource_name('my-app-backend') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_target_http_proxy { <%= example_resource_name('my-http-proxy') -%>: + ensure => present, + url_map => <%= example_resource_name('my-url-map') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~target_https_proxy.pp b/products/compute/files/examples~target_https_proxy.pp new file mode 100644 index 000000000000..b178db73eaf9 --- /dev/null +++ b/products/compute/files/examples~target_https_proxy.pp @@ -0,0 +1,106 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_instance_group { <%= example_resource_name('my-puppet-masters') -%>: + ensure => present, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_backend_service { <%= example_resource_name('my-app-backend') -%>: + ensure => present, + backends => [ + { group => <%= example_resource_name('my-puppet-masters') -%> }, + ], + enable_cdn => true, + health_checks => [ + gcompute_health_check_ref('another-hc', 'google.com:graphite-playground'), + ], + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_url_map { <%= example_resource_name('my-url-map') -%>: + ensure => present, + default_service => <%= example_resource_name('my-app-backend') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +# ******* +# WARNING: This manifest is for example purposes only. It is *not* advisable to +# have the key embedded like this because if you check this file into source +# control you are publishing the private key to whomever can access the source +# code. Instead you should protect the key, and for example, use the file() +# function to read it from disk without writing it verbatim to the manifest: +# +# gcompute_ssl_certificate { ... +# ... +# private_key => file('/path/to/my/private/key.pem'), +# ... +# } +# ******* + +gcompute_ssl_certificate { <%= example_resource_name('sample-certificate') -%>: + ensure => present, + description => 'A certificate for test purposes only.', + project => 'google.com:graphite-playground', + credential => 'mycred', + certificate => '-----BEGIN CERTIFICATE----- +MIICqjCCAk+gAwIBAgIJAIuJ+0352Kq4MAoGCCqGSM49BAMCMIGwMQswCQYDVQQG +EwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxFTAT +BgNVBAoMDEdvb2dsZSwgSW5jLjEeMBwGA1UECwwVR29vZ2xlIENsb3VkIFBsYXRm +b3JtMR8wHQYDVQQDDBZ3d3cubXktc2VjdXJlLXNpdGUuY29tMSEwHwYJKoZIhvcN +AQkBFhJuZWxzb25hQGdvb2dsZS5jb20wHhcNMTcwNjI4MDQ1NjI2WhcNMjcwNjI2 +MDQ1NjI2WjCBsDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xETAP +BgNVBAcMCEtpcmtsYW5kMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xHjAcBgNVBAsM +FUdvb2dsZSBDbG91ZCBQbGF0Zm9ybTEfMB0GA1UEAwwWd3d3Lm15LXNlY3VyZS1z +aXRlLmNvbTEhMB8GCSqGSIb3DQEJARYSbmVsc29uYUBnb29nbGUuY29tMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ +4mzkzTv0dXyB750fOGN02HtkpBOZzzvUARTR10JQoSe2/5PIwaNQME4wHQYDVR0O +BBYEFKIQC3A2SDpxcdfn0YLKineDNq/BMB8GA1UdIwQYMBaAFKIQC3A2SDpxcdfn +0YLKineDNq/BMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhALs4vy+O +M3jcqgA4fSW/oKw6UJxp+M6a+nGMX+UJR3YgAiEAvvl39QRVAiv84hdoCuyON0lJ +zqGNhIPGq2ULqXKK8BY= +-----END CERTIFICATE-----', + private_key => '-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIObtRo8tkUqoMjeHhsOh2ouPpXCgBcP+EDxZCB/tws15oAoGCCqGSM49 +AwEHoUQDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ4mzkzTv0dXyB750f +OGN02HtkpBOZzzvUARTR10JQoSe2/5PIwQ== +-----END EC PRIVATE KEY-----', +} + +<% end # name == README.md -%> +gcompute_target_https_proxy { <%= example_resource_name('my-https-proxy') -%>: + ensure => present, + ssl_certificates => [ + <%= example_resource_name('sample-certificate') -%>, + ], + url_map => <%= example_resource_name('my-url-map') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~target_pool.pp b/products/compute/files/examples~target_pool.pp new file mode 100644 index 000000000000..a46392d21fa3 --- /dev/null +++ b/products/compute/files/examples~target_pool.pp @@ -0,0 +1,34 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_region { <%= example_resource_name('some-region') -%>: + name => 'us-west1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_target_pool { <%= example_resource_name('test1') -%>: + ensure => present, + region => <%= example_resource_name('some-region') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~target_ssl_proxy.pp b/products/compute/files/examples~target_ssl_proxy.pp new file mode 100644 index 000000000000..32e0e58b67c1 --- /dev/null +++ b/products/compute/files/examples~target_ssl_proxy.pp @@ -0,0 +1,100 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_instance_group { <%= example_resource_name('my-puppet-masters') -%>: + ensure => present, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_backend_service { <%= example_resource_name('my-ssl-backend') -%>: + ensure => present, + backends => [ + { group => <%= example_resource_name('my-puppet-masters') -%> }, + ], + health_checks => [ + gcompute_health_check_ref('another-hc', 'google.com:graphite-playground'), + ], + protocol => 'SSL', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +# ******* +# WARNING: This manifest is for example purposes only. It is *not* advisable to +# have the key embedded like this because if you check this file into source +# control you are publishing the private key to whomever can access the source +# code. Instead you should protect the key, and for example, use the file() +# function to read it from disk without writing it verbatim to the manifest: +# +# gcompute_ssl_certificate { ... +# ... +# private_key => file('/path/to/my/private/key.pem'), +# ... +# } +# ******* + +gcompute_ssl_certificate { <%= example_resource_name('sample-certificate') -%>: + ensure => present, + description => 'A certificate for test purposes only.', + project => 'google.com:graphite-playground', + credential => 'mycred', + certificate => '-----BEGIN CERTIFICATE----- +MIICqjCCAk+gAwIBAgIJAIuJ+0352Kq4MAoGCCqGSM49BAMCMIGwMQswCQYDVQQG +EwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjERMA8GA1UEBwwIS2lya2xhbmQxFTAT +BgNVBAoMDEdvb2dsZSwgSW5jLjEeMBwGA1UECwwVR29vZ2xlIENsb3VkIFBsYXRm +b3JtMR8wHQYDVQQDDBZ3d3cubXktc2VjdXJlLXNpdGUuY29tMSEwHwYJKoZIhvcN +AQkBFhJuZWxzb25hQGdvb2dsZS5jb20wHhcNMTcwNjI4MDQ1NjI2WhcNMjcwNjI2 +MDQ1NjI2WjCBsDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xETAP +BgNVBAcMCEtpcmtsYW5kMRUwEwYDVQQKDAxHb29nbGUsIEluYy4xHjAcBgNVBAsM +FUdvb2dsZSBDbG91ZCBQbGF0Zm9ybTEfMB0GA1UEAwwWd3d3Lm15LXNlY3VyZS1z +aXRlLmNvbTEhMB8GCSqGSIb3DQEJARYSbmVsc29uYUBnb29nbGUuY29tMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ +4mzkzTv0dXyB750fOGN02HtkpBOZzzvUARTR10JQoSe2/5PIwaNQME4wHQYDVR0O +BBYEFKIQC3A2SDpxcdfn0YLKineDNq/BMB8GA1UdIwQYMBaAFKIQC3A2SDpxcdfn +0YLKineDNq/BMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhALs4vy+O +M3jcqgA4fSW/oKw6UJxp+M6a+nGMX+UJR3YgAiEAvvl39QRVAiv84hdoCuyON0lJ +zqGNhIPGq2ULqXKK8BY= +-----END CERTIFICATE-----', + private_key => '-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIObtRo8tkUqoMjeHhsOh2ouPpXCgBcP+EDxZCB/tws15oAoGCCqGSM49 +AwEHoUQDQgAEHGzpcRJ4XzfBJCCPMQeXQpTXwlblimODQCuQ4mzkzTv0dXyB750f +OGN02HtkpBOZzzvUARTR10JQoSe2/5PIwQ== +-----END EC PRIVATE KEY-----', +} + +<% end # name == README.md -%> +gcompute_target_ssl_proxy { <%= example_resource_name('my-ssl-proxy') -%>: + ensure => present, + proxy_header => 'PROXY_V1', + service => <%= example_resource_name('my-ssl-backend') -%>, + ssl_certificates => [ + <%= example_resource_name('sample-certificate') -%>, + ], + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~target_tcp_proxy.pp b/products/compute/files/examples~target_tcp_proxy.pp new file mode 100644 index 000000000000..309e0a30c5e9 --- /dev/null +++ b/products/compute/files/examples~target_tcp_proxy.pp @@ -0,0 +1,54 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_instance_group { <%= example_resource_name('my-puppet-masters') -%>: + ensure => present, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_backend_service { <%= example_resource_name('my-tcp-backend') -%>: + ensure => present, + backends => [ + { group => <%= example_resource_name('my-puppet-masters') -%> }, + ], + health_checks => [ + gcompute_health_check_ref('another-hc', 'google.com:graphite-playground'), + ], + protocol => 'TCP', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_target_tcp_proxy { <%= example_resource_name('my-tcp-proxy') -%>: + ensure => present, + proxy_header => 'PROXY_V1', + service => <%= example_resource_name('my-tcp-backend') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~url_map.pp b/products/compute/files/examples~url_map.pp new file mode 100644 index 000000000000..93d39e677d00 --- /dev/null +++ b/products/compute/files/examples~url_map.pp @@ -0,0 +1,53 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_instance_group { <%= example_resource_name('my-puppet-masters') -%>: + ensure => present, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gcompute_backend_service { <%= example_resource_name('my-app-backend') -%>: + ensure => present, + backends => [ + { group => <%= example_resource_name('my-puppet-masters') -%> }, + ], + enable_cdn => true, + health_checks => [ + gcompute_health_check_ref('another-hc', 'google.com:graphite-playground'), + ], + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcompute_url_map { <%= example_resource_name('my-url-map') -%>: + ensure => present, + default_service => <%= example_resource_name('my-app-backend') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/examples~zone.pp b/products/compute/files/examples~zone.pp new file mode 100644 index 000000000000..2ce7be853f86 --- /dev/null +++ b/products/compute/files/examples~zone.pp @@ -0,0 +1,26 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gcompute_zone { 'us-central1-a': + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/compute/files/spec~instance_template~success1~name.yaml b/products/compute/files/spec~instance_template~success1~name.yaml new file mode 100644 index 000000000000..7ce55bec4d0d --- /dev/null +++ b/products/compute/files/spec~instance_template~success1~name.yaml @@ -0,0 +1,179 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instanceTemplate +name: test name#0 data +id: 2149500871 +creationTimestamp: '2045-05-23T12:08:10+00:00' +description: test description#0 data +project: "'test project#0 data'" +properties: + canIpForward: true + description: test description#0 data + disks: + - autoDelete: true + boot: true + deviceName: test device_name#0 data + diskEncryptionKey: + rawKey: test raw_key#0 data + rsaEncryptedKey: test rsa_encrypted_key#0 data + sha256: test sha256#0 data + index: 1443881260 + initializeParams: + diskName: test disk_name#0 data + diskSizeGb: 450092159 + diskType: selflink(resource(disk_type,0)) + sourceImage: test source_image#0 data + sourceImageEncryptionKey: + rawKey: test raw_key#0 data + sha256: test sha256#0 data + interface: SCSI + mode: READ_WRITE + source: test name#0 data + type: SCRATCH + - autoDelete: false + boot: false + deviceName: test device_name#1 data + diskEncryptionKey: + rawKey: test raw_key#1 data + rsaEncryptedKey: test rsa_encrypted_key#1 data + sha256: test sha256#1 data + index: 2887762520 + initializeParams: + diskName: test disk_name#1 data + diskSizeGb: 900184319 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#1 data + sourceImageEncryptionKey: + rawKey: test raw_key#1 data + sha256: test sha256#1 data + interface: NVME + mode: READ_ONLY + source: test name#1 data + type: PERSISTENT + machineType: test name#0 data + metadata: + test metadata#1 data: test metadata#1 data + test metadata#2 data: 2666715473 + test metadata#3 data: test metadata#3 data + guestAccelerators: + - acceleratorCount: 2697554557 + acceleratorType: test accelerator_type#0 data + - acceleratorCount: 5395109114 + acceleratorType: test accelerator_type#1 data + - acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data + - acceleratorCount: 10790218229 + acceleratorType: test accelerator_type#3 data + networkInterfaces: + - accessConfigs: + - name: test name#0 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#0 data + subnetworkRangeName: test subnetwork_range_name#0 data + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#0 data + network: selflink(resource(network,0)) + networkIP: test network_ip#0 data + subnetwork: selflink(resource(subnetwork,0)) + - accessConfigs: + - name: test name#1 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#1 data + network: selflink(resource(network,1)) + networkIP: test network_ip#1 data + subnetwork: selflink(resource(subnetwork,1)) + - accessConfigs: + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#2 data + network: selflink(resource(network,2)) + networkIP: test network_ip#2 data + subnetwork: selflink(resource(subnetwork,2)) + scheduling: + automaticRestart: true + onHostMaintenance: test on_host_maintenance#0 data + preemptible: true + serviceAccounts: + - email: true + scopes: + - rr + - ss + - tt + - uu + - vv + - email: false + scopes: + - ll + - mm + - nn + - oo + - pp + - email: true + scopes: + - ee + - ff + - gg + - hh + tags: + fingerprint: test fingerprint#0 data + items: + - hh + - ii + - jj +selfLink: selflink(resource(instance_template,0)) diff --git a/products/compute/files/spec~instance_template~success1~title.yaml b/products/compute/files/spec~instance_template~success1~title.yaml new file mode 100644 index 000000000000..c7ad96e8e818 --- /dev/null +++ b/products/compute/files/spec~instance_template~success1~title.yaml @@ -0,0 +1,179 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instanceTemplate +name: title0 +id: 2149500871 +creationTimestamp: '2045-05-23T12:08:10+00:00' +description: test description#0 data +project: "'test project#0 data'" +properties: + canIpForward: true + description: test description#0 data + disks: + - autoDelete: true + boot: true + deviceName: test device_name#0 data + diskEncryptionKey: + rawKey: test raw_key#0 data + rsaEncryptedKey: test rsa_encrypted_key#0 data + sha256: test sha256#0 data + index: 1443881260 + initializeParams: + diskName: test disk_name#0 data + diskSizeGb: 450092159 + diskType: selflink(resource(disk_type,0)) + sourceImage: test source_image#0 data + sourceImageEncryptionKey: + rawKey: test raw_key#0 data + sha256: test sha256#0 data + interface: SCSI + mode: READ_WRITE + source: test name#0 data + type: SCRATCH + - autoDelete: false + boot: false + deviceName: test device_name#1 data + diskEncryptionKey: + rawKey: test raw_key#1 data + rsaEncryptedKey: test rsa_encrypted_key#1 data + sha256: test sha256#1 data + index: 2887762520 + initializeParams: + diskName: test disk_name#1 data + diskSizeGb: 900184319 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#1 data + sourceImageEncryptionKey: + rawKey: test raw_key#1 data + sha256: test sha256#1 data + interface: NVME + mode: READ_ONLY + source: test name#1 data + type: PERSISTENT + machineType: test name#0 data + metadata: + test metadata#1 data: test metadata#1 data + test metadata#2 data: 2666715473 + test metadata#3 data: test metadata#3 data + guestAccelerators: + - acceleratorCount: 2697554557 + acceleratorType: test accelerator_type#0 data + - acceleratorCount: 5395109114 + acceleratorType: test accelerator_type#1 data + - acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data + - acceleratorCount: 10790218229 + acceleratorType: test accelerator_type#3 data + networkInterfaces: + - accessConfigs: + - name: test name#0 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#0 data + subnetworkRangeName: test subnetwork_range_name#0 data + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#0 data + network: selflink(resource(network,0)) + networkIP: test network_ip#0 data + subnetwork: selflink(resource(subnetwork,0)) + - accessConfigs: + - name: test name#1 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#1 data + network: selflink(resource(network,1)) + networkIP: test network_ip#1 data + subnetwork: selflink(resource(subnetwork,1)) + - accessConfigs: + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#2 data + network: selflink(resource(network,2)) + networkIP: test network_ip#2 data + subnetwork: selflink(resource(subnetwork,2)) + scheduling: + automaticRestart: true + onHostMaintenance: test on_host_maintenance#0 data + preemptible: true + serviceAccounts: + - email: true + scopes: + - rr + - ss + - tt + - uu + - vv + - email: false + scopes: + - ll + - mm + - nn + - oo + - pp + - email: true + scopes: + - ee + - ff + - gg + - hh + tags: + fingerprint: test fingerprint#0 data + items: + - hh + - ii + - jj +selfLink: selflink(resource(instance_template,0)) diff --git a/products/compute/files/spec~instance_template~success2~name.yaml b/products/compute/files/spec~instance_template~success2~name.yaml new file mode 100644 index 000000000000..e503468fb272 --- /dev/null +++ b/products/compute/files/spec~instance_template~success2~name.yaml @@ -0,0 +1,131 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instanceTemplate +name: test name#1 data +id: 4299001743 +creationTimestamp: '2120-10-14T00:16:21+00:00' +description: test description#1 data +project: "'test project#1 data'" +properties: + canIpForward: false + description: test description#1 data + disks: + - autoDelete: false + boot: false + deviceName: test device_name#1 data + diskEncryptionKey: + rawKey: test raw_key#1 data + rsaEncryptedKey: test rsa_encrypted_key#1 data + sha256: test sha256#1 data + index: 2887762520 + initializeParams: + diskName: test disk_name#1 data + diskSizeGb: 900184319 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#1 data + sourceImageEncryptionKey: + rawKey: test raw_key#1 data + sha256: test sha256#1 data + interface: NVME + mode: READ_ONLY + source: test name#1 data + type: PERSISTENT + machineType: test name#1 data + metadata: + test metadata#2 data: test metadata#2 data + test metadata#3 data: 3555620630 + test metadata#4 data: test metadata#4 data + test metadata#5 data: 5333430946 + test metadata#6 data: test metadata#6 data + guestAccelerators: + - acceleratorCount: 5395109114 + acceleratorType: test accelerator_type#1 data + - acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data + networkInterfaces: + - accessConfigs: + - name: test name#1 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#1 data + network: selflink(resource(network,1)) + networkIP: test network_ip#1 data + subnetwork: selflink(resource(subnetwork,1)) + scheduling: + automaticRestart: false + onHostMaintenance: test on_host_maintenance#1 data + preemptible: false + serviceAccounts: + - email: false + scopes: + - ll + - mm + - nn + - oo + - pp + - email: true + scopes: + - ee + - ff + - gg + - hh + - email: false + scopes: + - ww + - xx + - yy + - zz + - email: true + scopes: + - rr + - ss + - tt + - email: false + scopes: + - ll + - mm + - nn + tags: + fingerprint: test fingerprint#1 data + items: + - qq + - rr + - ss + - tt +selfLink: selflink(resource(instance_template,1)) diff --git a/products/compute/files/spec~instance_template~success2~title.yaml b/products/compute/files/spec~instance_template~success2~title.yaml new file mode 100644 index 000000000000..2071d1cc638c --- /dev/null +++ b/products/compute/files/spec~instance_template~success2~title.yaml @@ -0,0 +1,131 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instanceTemplate +name: title1 +id: 4299001743 +creationTimestamp: '2120-10-14T00:16:21+00:00' +description: test description#1 data +project: "'test project#1 data'" +properties: + canIpForward: false + description: test description#1 data + disks: + - autoDelete: false + boot: false + deviceName: test device_name#1 data + diskEncryptionKey: + rawKey: test raw_key#1 data + rsaEncryptedKey: test rsa_encrypted_key#1 data + sha256: test sha256#1 data + index: 2887762520 + initializeParams: + diskName: test disk_name#1 data + diskSizeGb: 900184319 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#1 data + sourceImageEncryptionKey: + rawKey: test raw_key#1 data + sha256: test sha256#1 data + interface: NVME + mode: READ_ONLY + source: test name#1 data + type: PERSISTENT + machineType: test name#1 data + metadata: + test metadata#2 data: test metadata#2 data + test metadata#3 data: 3555620630 + test metadata#4 data: test metadata#4 data + test metadata#5 data: 5333430946 + test metadata#6 data: test metadata#6 data + guestAccelerators: + - acceleratorCount: 5395109114 + acceleratorType: test accelerator_type#1 data + - acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data + networkInterfaces: + - accessConfigs: + - name: test name#1 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#1 data + network: selflink(resource(network,1)) + networkIP: test network_ip#1 data + subnetwork: selflink(resource(subnetwork,1)) + scheduling: + automaticRestart: false + onHostMaintenance: test on_host_maintenance#1 data + preemptible: false + serviceAccounts: + - email: false + scopes: + - ll + - mm + - nn + - oo + - pp + - email: true + scopes: + - ee + - ff + - gg + - hh + - email: false + scopes: + - ww + - xx + - yy + - zz + - email: true + scopes: + - rr + - ss + - tt + - email: false + scopes: + - ll + - mm + - nn + tags: + fingerprint: test fingerprint#1 data + items: + - qq + - rr + - ss + - tt +selfLink: selflink(resource(instance_template,1)) diff --git a/products/compute/files/spec~instance_template~success3~name.yaml b/products/compute/files/spec~instance_template~success3~name.yaml new file mode 100644 index 000000000000..95a281391587 --- /dev/null +++ b/products/compute/files/spec~instance_template~success3~name.yaml @@ -0,0 +1,185 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instanceTemplate +name: test name#2 data +id: 6448502614 +creationTimestamp: '2196-03-05T12:24:32+00:00' +description: test description#2 data +project: "'test project#2 data'" +properties: + canIpForward: true + description: test description#2 data + disks: + - autoDelete: true + boot: true + deviceName: test device_name#2 data + diskEncryptionKey: + rawKey: test raw_key#2 data + rsaEncryptedKey: test rsa_encrypted_key#2 data + sha256: test sha256#2 data + index: 4331643780 + initializeParams: + diskName: test disk_name#2 data + diskSizeGb: 1350276479 + diskType: selflink(resource(disk_type,2)) + sourceImage: test source_image#2 data + sourceImageEncryptionKey: + rawKey: test raw_key#2 data + sha256: test sha256#2 data + interface: SCSI + mode: READ_WRITE + source: test name#2 data + type: SCRATCH + - autoDelete: false + boot: false + deviceName: test device_name#3 data + diskEncryptionKey: + rawKey: test raw_key#3 data + rsaEncryptedKey: test rsa_encrypted_key#3 data + sha256: test sha256#3 data + index: 5775525040 + initializeParams: + diskName: test disk_name#3 data + diskSizeGb: 1800368639 + diskType: selflink(resource(disk_type,0)) + sourceImage: test source_image#3 data + sourceImageEncryptionKey: + rawKey: test raw_key#3 data + sha256: test sha256#3 data + interface: NVME + mode: READ_ONLY + source: test name#0 data + type: PERSISTENT + - autoDelete: true + boot: true + deviceName: test device_name#4 data + diskEncryptionKey: + rawKey: test raw_key#4 data + rsaEncryptedKey: test rsa_encrypted_key#4 data + sha256: test sha256#4 data + index: 7219406300 + initializeParams: + diskName: test disk_name#4 data + diskSizeGb: 2250460799 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#4 data + sourceImageEncryptionKey: + rawKey: test raw_key#4 data + sha256: test sha256#4 data + interface: SCSI + mode: READ_WRITE + source: test name#1 data + type: SCRATCH + machineType: test name#2 data + metadata: + test metadata#3 data: test metadata#3 data + test metadata#4 data: 4444525788 + test metadata#5 data: test metadata#5 data + guestAccelerators: + - acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data + - acceleratorCount: 10790218229 + acceleratorType: test accelerator_type#3 data + - acceleratorCount: 13487772787 + acceleratorType: test accelerator_type#4 data + - acceleratorCount: 16185327344 + acceleratorType: test accelerator_type#5 data + networkInterfaces: + - accessConfigs: + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#2 data + network: selflink(resource(network,2)) + networkIP: test network_ip#2 data + subnetwork: selflink(resource(subnetwork,2)) + - accessConfigs: + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#5 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + - ipCidrRange: test ip_cidr_range#4 data + subnetworkRangeName: test subnetwork_range_name#4 data + - ipCidrRange: test ip_cidr_range#5 data + subnetworkRangeName: test subnetwork_range_name#5 data + - ipCidrRange: test ip_cidr_range#6 data + subnetworkRangeName: test subnetwork_range_name#6 data + - ipCidrRange: test ip_cidr_range#7 data + subnetworkRangeName: test subnetwork_range_name#7 data + name: test name#3 data + network: selflink(resource(network,0)) + networkIP: test network_ip#3 data + subnetwork: selflink(resource(subnetwork,0)) + scheduling: + automaticRestart: true + onHostMaintenance: test on_host_maintenance#2 data + preemptible: true + serviceAccounts: + - email: true + scopes: + - ee + - ff + - gg + - hh + - email: false + scopes: + - ww + - xx + - yy + - zz + - email: true + scopes: + - rr + - ss + - tt + tags: + fingerprint: test fingerprint#2 data + items: + - yy + - zz +selfLink: selflink(resource(instance_template,2)) diff --git a/products/compute/files/spec~instance_template~success3~title.yaml b/products/compute/files/spec~instance_template~success3~title.yaml new file mode 100644 index 000000000000..59d00a5723e5 --- /dev/null +++ b/products/compute/files/spec~instance_template~success3~title.yaml @@ -0,0 +1,185 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instanceTemplate +name: title2 +id: 6448502614 +creationTimestamp: '2196-03-05T12:24:32+00:00' +description: test description#2 data +project: "'test project#2 data'" +properties: + canIpForward: true + description: test description#2 data + disks: + - autoDelete: true + boot: true + deviceName: test device_name#2 data + diskEncryptionKey: + rawKey: test raw_key#2 data + rsaEncryptedKey: test rsa_encrypted_key#2 data + sha256: test sha256#2 data + index: 4331643780 + initializeParams: + diskName: test disk_name#2 data + diskSizeGb: 1350276479 + diskType: selflink(resource(disk_type,2)) + sourceImage: test source_image#2 data + sourceImageEncryptionKey: + rawKey: test raw_key#2 data + sha256: test sha256#2 data + interface: SCSI + mode: READ_WRITE + source: test name#2 data + type: SCRATCH + - autoDelete: false + boot: false + deviceName: test device_name#3 data + diskEncryptionKey: + rawKey: test raw_key#3 data + rsaEncryptedKey: test rsa_encrypted_key#3 data + sha256: test sha256#3 data + index: 5775525040 + initializeParams: + diskName: test disk_name#3 data + diskSizeGb: 1800368639 + diskType: selflink(resource(disk_type,0)) + sourceImage: test source_image#3 data + sourceImageEncryptionKey: + rawKey: test raw_key#3 data + sha256: test sha256#3 data + interface: NVME + mode: READ_ONLY + source: test name#0 data + type: PERSISTENT + - autoDelete: true + boot: true + deviceName: test device_name#4 data + diskEncryptionKey: + rawKey: test raw_key#4 data + rsaEncryptedKey: test rsa_encrypted_key#4 data + sha256: test sha256#4 data + index: 7219406300 + initializeParams: + diskName: test disk_name#4 data + diskSizeGb: 2250460799 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#4 data + sourceImageEncryptionKey: + rawKey: test raw_key#4 data + sha256: test sha256#4 data + interface: SCSI + mode: READ_WRITE + source: test name#1 data + type: SCRATCH + machineType: test name#2 data + metadata: + test metadata#3 data: test metadata#3 data + test metadata#4 data: 4444525788 + test metadata#5 data: test metadata#5 data + guestAccelerators: + - acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data + - acceleratorCount: 10790218229 + acceleratorType: test accelerator_type#3 data + - acceleratorCount: 13487772787 + acceleratorType: test accelerator_type#4 data + - acceleratorCount: 16185327344 + acceleratorType: test accelerator_type#5 data + networkInterfaces: + - accessConfigs: + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#2 data + network: selflink(resource(network,2)) + networkIP: test network_ip#2 data + subnetwork: selflink(resource(subnetwork,2)) + - accessConfigs: + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#5 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + - ipCidrRange: test ip_cidr_range#4 data + subnetworkRangeName: test subnetwork_range_name#4 data + - ipCidrRange: test ip_cidr_range#5 data + subnetworkRangeName: test subnetwork_range_name#5 data + - ipCidrRange: test ip_cidr_range#6 data + subnetworkRangeName: test subnetwork_range_name#6 data + - ipCidrRange: test ip_cidr_range#7 data + subnetworkRangeName: test subnetwork_range_name#7 data + name: test name#3 data + network: selflink(resource(network,0)) + networkIP: test network_ip#3 data + subnetwork: selflink(resource(subnetwork,0)) + scheduling: + automaticRestart: true + onHostMaintenance: test on_host_maintenance#2 data + preemptible: true + serviceAccounts: + - email: true + scopes: + - ee + - ff + - gg + - hh + - email: false + scopes: + - ww + - xx + - yy + - zz + - email: true + scopes: + - rr + - ss + - tt + tags: + fingerprint: test fingerprint#2 data + items: + - yy + - zz +selfLink: selflink(resource(instance_template,2)) diff --git a/products/compute/files/spec~instance~success1~name.yaml b/products/compute/files/spec~instance~success1~name.yaml new file mode 100644 index 000000000000..2b03eacd84ee --- /dev/null +++ b/products/compute/files/spec~instance~success1~name.yaml @@ -0,0 +1,186 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instance +name: test name#0 data +id: 2149500871 +canIpForward: true +cpuPlatform: test cpu_platform#0 data +creationTimestamp: test creation_timestamp#0 data +disks: +- autoDelete: true + boot: true + deviceName: test device_name#0 data + diskEncryptionKey: + rawKey: test raw_key#0 data + rsaEncryptedKey: test rsa_encrypted_key#0 data + sha256: test sha256#0 data + index: 1443881260 + initializeParams: + diskName: test disk_name#0 data + diskSizeGb: 450092159 + diskType: selflink(resource(disk_type,0)) + sourceImage: test source_image#0 data + sourceImageEncryptionKey: + rawKey: test raw_key#0 data + sha256: test sha256#0 data + interface: SCSI + mode: READ_WRITE + source: selflink(resource(disk,0)) + type: SCRATCH +- autoDelete: false + boot: false + deviceName: test device_name#1 data + diskEncryptionKey: + rawKey: test raw_key#1 data + rsaEncryptedKey: test rsa_encrypted_key#1 data + sha256: test sha256#1 data + index: 2887762520 + initializeParams: + diskName: test disk_name#1 data + diskSizeGb: 900184319 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#1 data + sourceImageEncryptionKey: + rawKey: test raw_key#1 data + sha256: test sha256#1 data + interface: NVME + mode: READ_ONLY + source: selflink(resource(disk,1)) + type: PERSISTENT +guestAccelerators: +- acceleratorCount: 2697554557 + acceleratorType: test accelerator_type#0 data +- acceleratorCount: 5395109114 + acceleratorType: test accelerator_type#1 data +- acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data +- acceleratorCount: 10790218229 + acceleratorType: test accelerator_type#3 data +labelFingerprint: test label_fingerprint#0 data +machineType: selflink(resource(machine_type,0)) +metadata: + items: + - key: test metadata#1 data + value: test metadata#1 data + - key: test metadata#2 data + value: 2666715473 + - key: test metadata#3 data + value: test metadata#3 data +minCpuPlatform: test min_cpu_platform#0 data +networkInterfaces: +- accessConfigs: + - name: test name#0 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#0 data + subnetworkRangeName: test subnetwork_range_name#0 data + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#0 data + network: selflink(resource(network,0)) + networkIP: test network_ip#0 data + subnetwork: selflink(resource(subnetwork,0)) +- accessConfigs: + - name: test name#1 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#1 data + network: selflink(resource(network,1)) + networkIP: test network_ip#1 data + subnetwork: selflink(resource(subnetwork,1)) +- accessConfigs: + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#2 data + network: selflink(resource(network,2)) + networkIP: test network_ip#2 data + subnetwork: selflink(resource(subnetwork,2)) +project: "'test project#0 data'" +scheduling: + automaticRestart: true + onHostMaintenance: test on_host_maintenance#0 data + preemptible: true +serviceAccounts: +- email: true + scopes: + - rr + - ss + - tt + - uu + - vv +- email: false + scopes: + - ll + - mm + - nn + - oo + - pp +- email: true + scopes: + - ee + - ff + - gg + - hh +status: test status#0 data +statusMessage: test status_message#0 data +tags: + fingerprint: test fingerprint#0 data + items: + - hh + - ii + - jj +zone: test name#0 data +selfLink: selflink(resource(instance,0)) diff --git a/products/compute/files/spec~instance~success1~title.yaml b/products/compute/files/spec~instance~success1~title.yaml new file mode 100644 index 000000000000..0597ff0c7593 --- /dev/null +++ b/products/compute/files/spec~instance~success1~title.yaml @@ -0,0 +1,186 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instance +name: title0 +id: 2149500871 +canIpForward: true +cpuPlatform: test cpu_platform#0 data +creationTimestamp: test creation_timestamp#0 data +disks: +- autoDelete: true + boot: true + deviceName: test device_name#0 data + diskEncryptionKey: + rawKey: test raw_key#0 data + rsaEncryptedKey: test rsa_encrypted_key#0 data + sha256: test sha256#0 data + index: 1443881260 + initializeParams: + diskName: test disk_name#0 data + diskSizeGb: 450092159 + diskType: selflink(resource(disk_type,0)) + sourceImage: test source_image#0 data + sourceImageEncryptionKey: + rawKey: test raw_key#0 data + sha256: test sha256#0 data + interface: SCSI + mode: READ_WRITE + source: selflink(resource(disk,0)) + type: SCRATCH +- autoDelete: false + boot: false + deviceName: test device_name#1 data + diskEncryptionKey: + rawKey: test raw_key#1 data + rsaEncryptedKey: test rsa_encrypted_key#1 data + sha256: test sha256#1 data + index: 2887762520 + initializeParams: + diskName: test disk_name#1 data + diskSizeGb: 900184319 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#1 data + sourceImageEncryptionKey: + rawKey: test raw_key#1 data + sha256: test sha256#1 data + interface: NVME + mode: READ_ONLY + source: selflink(resource(disk,1)) + type: PERSISTENT +guestAccelerators: +- acceleratorCount: 2697554557 + acceleratorType: test accelerator_type#0 data +- acceleratorCount: 5395109114 + acceleratorType: test accelerator_type#1 data +- acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data +- acceleratorCount: 10790218229 + acceleratorType: test accelerator_type#3 data +labelFingerprint: test label_fingerprint#0 data +machineType: selflink(resource(machine_type,0)) +metadata: + items: + - key: test metadata#1 data + value: test metadata#1 data + - key: test metadata#2 data + value: 2666715473 + - key: test metadata#3 data + value: test metadata#3 data +minCpuPlatform: test min_cpu_platform#0 data +networkInterfaces: +- accessConfigs: + - name: test name#0 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#0 data + subnetworkRangeName: test subnetwork_range_name#0 data + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#0 data + network: selflink(resource(network,0)) + networkIP: test network_ip#0 data + subnetwork: selflink(resource(subnetwork,0)) +- accessConfigs: + - name: test name#1 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#1 data + network: selflink(resource(network,1)) + networkIP: test network_ip#1 data + subnetwork: selflink(resource(subnetwork,1)) +- accessConfigs: + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#2 data + network: selflink(resource(network,2)) + networkIP: test network_ip#2 data + subnetwork: selflink(resource(subnetwork,2)) +project: "'test project#0 data'" +scheduling: + automaticRestart: true + onHostMaintenance: test on_host_maintenance#0 data + preemptible: true +serviceAccounts: +- email: true + scopes: + - rr + - ss + - tt + - uu + - vv +- email: false + scopes: + - ll + - mm + - nn + - oo + - pp +- email: true + scopes: + - ee + - ff + - gg + - hh +status: test status#0 data +statusMessage: test status_message#0 data +tags: + fingerprint: test fingerprint#0 data + items: + - hh + - ii + - jj +zone: test name#0 data +selfLink: selflink(resource(instance,0)) diff --git a/products/compute/files/spec~instance~success2~name.yaml b/products/compute/files/spec~instance~success2~name.yaml new file mode 100644 index 000000000000..e87f43029df3 --- /dev/null +++ b/products/compute/files/spec~instance~success2~name.yaml @@ -0,0 +1,126 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +kind: compute#instance +name: test name#1 data +id: 4299001743 +canIpForward: false +cpuPlatform: test cpu_platform#1 data +creationTimestamp: test creation_timestamp#1 data +disks: +- autoDelete: false + boot: false + deviceName: test device_name#1 data + diskEncryptionKey: + rawKey: test raw_key#1 data + rsaEncryptedKey: test rsa_encrypted_key#1 data + sha256: test sha256#1 data + index: 2887762520 + initializeParams: + diskName: test disk_name#1 data + diskSizeGb: 900184319 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#1 data + sourceImageEncryptionKey: + rawKey: test raw_key#1 data + sha256: test sha256#1 data + interface: NVME + mode: READ_ONLY + source: selflink(resource(disk,1)) + type: PERSISTENT +guestAccelerators: +- acceleratorCount: 5395109114 + acceleratorType: test accelerator_type#1 data +- acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data +labelFingerprint: test label_fingerprint#1 data +machineType: selflink(resource(machine_type,1)) +metadata: + items: + - key: test metadata#2 data + value: test metadata#2 data + - key: test metadata#3 data + value: 3555620630 + - key: test metadata#4 data + value: test metadata#4 data + - key: test metadata#5 data + value: 5333430946 + - key: test metadata#6 data + value: test metadata#6 data +minCpuPlatform: test min_cpu_platform#1 data +networkInterfaces: +- accessConfigs: + - name: test name#1 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#1 data + network: selflink(resource(network,1)) + networkIP: test network_ip#1 data + subnetwork: selflink(resource(subnetwork,1)) +project: "'test project#1 data'" +scheduling: + automaticRestart: false + onHostMaintenance: test on_host_maintenance#1 data + preemptible: false +serviceAccounts: +- email: false + scopes: + - ll + - mm + - nn + - oo + - pp +- email: true + scopes: + - ee + - ff + - gg + - hh +- email: false + scopes: + - ww + - xx + - yy + - zz +- email: true + scopes: + - rr + - ss + - tt +- email: false + scopes: + - ll + - mm + - nn +status: test status#1 data +statusMessage: test status_message#1 data +tags: + fingerprint: test fingerprint#1 data + items: + - qq + - rr + - ss + - tt +zone: test name#1 data +selfLink: selflink(resource(instance,1)) diff --git a/products/compute/files/spec~instance~success2~title.yaml b/products/compute/files/spec~instance~success2~title.yaml new file mode 100644 index 000000000000..a72b164527c6 --- /dev/null +++ b/products/compute/files/spec~instance~success2~title.yaml @@ -0,0 +1,140 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instance +name: title1 +id: 4299001743 +canIpForward: false +cpuPlatform: test cpu_platform#1 data +creationTimestamp: test creation_timestamp#1 data +disks: +- autoDelete: false + boot: false + deviceName: test device_name#1 data + diskEncryptionKey: + rawKey: test raw_key#1 data + rsaEncryptedKey: test rsa_encrypted_key#1 data + sha256: test sha256#1 data + index: 2887762520 + initializeParams: + diskName: test disk_name#1 data + diskSizeGb: 900184319 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#1 data + sourceImageEncryptionKey: + rawKey: test raw_key#1 data + sha256: test sha256#1 data + interface: NVME + mode: READ_ONLY + source: selflink(resource(disk,1)) + type: PERSISTENT +guestAccelerators: +- acceleratorCount: 5395109114 + acceleratorType: test accelerator_type#1 data +- acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data +labelFingerprint: test label_fingerprint#1 data +machineType: selflink(resource(machine_type,1)) +metadata: + items: + - key: test metadata#2 data + value: test metadata#2 data + - key: test metadata#3 data + value: 3555620630 + - key: test metadata#4 data + value: test metadata#4 data + - key: test metadata#5 data + value: 5333430946 + - key: test metadata#6 data + value: test metadata#6 data +minCpuPlatform: test min_cpu_platform#1 data +networkInterfaces: +- accessConfigs: + - name: test name#1 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#1 data + subnetworkRangeName: test subnetwork_range_name#1 data + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#1 data + network: selflink(resource(network,1)) + networkIP: test network_ip#1 data + subnetwork: selflink(resource(subnetwork,1)) +project: "'test project#1 data'" +scheduling: + automaticRestart: false + onHostMaintenance: test on_host_maintenance#1 data + preemptible: false +serviceAccounts: +- email: false + scopes: + - ll + - mm + - nn + - oo + - pp +- email: true + scopes: + - ee + - ff + - gg + - hh +- email: false + scopes: + - ww + - xx + - yy + - zz +- email: true + scopes: + - rr + - ss + - tt +- email: false + scopes: + - ll + - mm + - nn +status: test status#1 data +statusMessage: test status_message#1 data +tags: + fingerprint: test fingerprint#1 data + items: + - qq + - rr + - ss + - tt +zone: test name#1 data +selfLink: selflink(resource(instance,1)) diff --git a/products/compute/files/spec~instance~success3~name.yaml b/products/compute/files/spec~instance~success3~name.yaml new file mode 100644 index 000000000000..caec1fa62b28 --- /dev/null +++ b/products/compute/files/spec~instance~success3~name.yaml @@ -0,0 +1,192 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instance +name: test name#2 data +id: 6448502614 +canIpForward: true +cpuPlatform: test cpu_platform#2 data +creationTimestamp: test creation_timestamp#2 data +disks: +- autoDelete: true + boot: true + deviceName: test device_name#2 data + diskEncryptionKey: + rawKey: test raw_key#2 data + rsaEncryptedKey: test rsa_encrypted_key#2 data + sha256: test sha256#2 data + index: 4331643780 + initializeParams: + diskName: test disk_name#2 data + diskSizeGb: 1350276479 + diskType: selflink(resource(disk_type,2)) + sourceImage: test source_image#2 data + sourceImageEncryptionKey: + rawKey: test raw_key#2 data + sha256: test sha256#2 data + interface: SCSI + mode: READ_WRITE + source: selflink(resource(disk,2)) + type: SCRATCH +- autoDelete: false + boot: false + deviceName: test device_name#3 data + diskEncryptionKey: + rawKey: test raw_key#3 data + rsaEncryptedKey: test rsa_encrypted_key#3 data + sha256: test sha256#3 data + index: 5775525040 + initializeParams: + diskName: test disk_name#3 data + diskSizeGb: 1800368639 + diskType: selflink(resource(disk_type,0)) + sourceImage: test source_image#3 data + sourceImageEncryptionKey: + rawKey: test raw_key#3 data + sha256: test sha256#3 data + interface: NVME + mode: READ_ONLY + source: selflink(resource(disk,0)) + type: PERSISTENT +- autoDelete: true + boot: true + deviceName: test device_name#4 data + diskEncryptionKey: + rawKey: test raw_key#4 data + rsaEncryptedKey: test rsa_encrypted_key#4 data + sha256: test sha256#4 data + index: 7219406300 + initializeParams: + diskName: test disk_name#4 data + diskSizeGb: 2250460799 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#4 data + sourceImageEncryptionKey: + rawKey: test raw_key#4 data + sha256: test sha256#4 data + interface: SCSI + mode: READ_WRITE + source: selflink(resource(disk,1)) + type: SCRATCH +guestAccelerators: +- acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data +- acceleratorCount: 10790218229 + acceleratorType: test accelerator_type#3 data +- acceleratorCount: 13487772787 + acceleratorType: test accelerator_type#4 data +- acceleratorCount: 16185327344 + acceleratorType: test accelerator_type#5 data +labelFingerprint: test label_fingerprint#2 data +machineType: selflink(resource(machine_type,2)) +metadata: + items: + - key: test metadata#3 data + value: test metadata#3 data + - key: test metadata#4 data + value: 4444525788 + - key: test metadata#5 data + value: test metadata#5 data +minCpuPlatform: test min_cpu_platform#2 data +networkInterfaces: +- accessConfigs: + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#2 data + network: selflink(resource(network,2)) + networkIP: test network_ip#2 data + subnetwork: selflink(resource(subnetwork,2)) +- accessConfigs: + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#5 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + - ipCidrRange: test ip_cidr_range#4 data + subnetworkRangeName: test subnetwork_range_name#4 data + - ipCidrRange: test ip_cidr_range#5 data + subnetworkRangeName: test subnetwork_range_name#5 data + - ipCidrRange: test ip_cidr_range#6 data + subnetworkRangeName: test subnetwork_range_name#6 data + - ipCidrRange: test ip_cidr_range#7 data + subnetworkRangeName: test subnetwork_range_name#7 data + name: test name#3 data + network: selflink(resource(network,0)) + networkIP: test network_ip#3 data + subnetwork: selflink(resource(subnetwork,0)) +project: "'test project#2 data'" +scheduling: + automaticRestart: true + onHostMaintenance: test on_host_maintenance#2 data + preemptible: true +serviceAccounts: +- email: true + scopes: + - ee + - ff + - gg + - hh +- email: false + scopes: + - ww + - xx + - yy + - zz +- email: true + scopes: + - rr + - ss + - tt +status: test status#2 data +statusMessage: test status_message#2 data +tags: + fingerprint: test fingerprint#2 data + items: + - yy + - zz +zone: test name#2 data +selfLink: selflink(resource(instance,2)) diff --git a/products/compute/files/spec~instance~success3~title.yaml b/products/compute/files/spec~instance~success3~title.yaml new file mode 100644 index 000000000000..cf40010e6f67 --- /dev/null +++ b/products/compute/files/spec~instance~success3~title.yaml @@ -0,0 +1,192 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +kind: compute#instance +name: title2 +id: 6448502614 +canIpForward: true +cpuPlatform: test cpu_platform#2 data +creationTimestamp: test creation_timestamp#2 data +disks: +- autoDelete: true + boot: true + deviceName: test device_name#2 data + diskEncryptionKey: + rawKey: test raw_key#2 data + rsaEncryptedKey: test rsa_encrypted_key#2 data + sha256: test sha256#2 data + index: 4331643780 + initializeParams: + diskName: test disk_name#2 data + diskSizeGb: 1350276479 + diskType: selflink(resource(disk_type,2)) + sourceImage: test source_image#2 data + sourceImageEncryptionKey: + rawKey: test raw_key#2 data + sha256: test sha256#2 data + interface: SCSI + mode: READ_WRITE + source: selflink(resource(disk,2)) + type: SCRATCH +- autoDelete: false + boot: false + deviceName: test device_name#3 data + diskEncryptionKey: + rawKey: test raw_key#3 data + rsaEncryptedKey: test rsa_encrypted_key#3 data + sha256: test sha256#3 data + index: 5775525040 + initializeParams: + diskName: test disk_name#3 data + diskSizeGb: 1800368639 + diskType: selflink(resource(disk_type,0)) + sourceImage: test source_image#3 data + sourceImageEncryptionKey: + rawKey: test raw_key#3 data + sha256: test sha256#3 data + interface: NVME + mode: READ_ONLY + source: selflink(resource(disk,0)) + type: PERSISTENT +- autoDelete: true + boot: true + deviceName: test device_name#4 data + diskEncryptionKey: + rawKey: test raw_key#4 data + rsaEncryptedKey: test rsa_encrypted_key#4 data + sha256: test sha256#4 data + index: 7219406300 + initializeParams: + diskName: test disk_name#4 data + diskSizeGb: 2250460799 + diskType: selflink(resource(disk_type,1)) + sourceImage: test source_image#4 data + sourceImageEncryptionKey: + rawKey: test raw_key#4 data + sha256: test sha256#4 data + interface: SCSI + mode: READ_WRITE + source: selflink(resource(disk,1)) + type: SCRATCH +guestAccelerators: +- acceleratorCount: 8092663672 + acceleratorType: test accelerator_type#2 data +- acceleratorCount: 10790218229 + acceleratorType: test accelerator_type#3 data +- acceleratorCount: 13487772787 + acceleratorType: test accelerator_type#4 data +- acceleratorCount: 16185327344 + acceleratorType: test accelerator_type#5 data +labelFingerprint: test label_fingerprint#2 data +machineType: selflink(resource(machine_type,2)) +metadata: + items: + - key: test metadata#3 data + value: test metadata#3 data + - key: test metadata#4 data + value: 4444525788 + - key: test metadata#5 data + value: test metadata#5 data +minCpuPlatform: test min_cpu_platform#2 data +networkInterfaces: +- accessConfigs: + - name: test name#2 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#2 data + subnetworkRangeName: test subnetwork_range_name#2 data + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + name: test name#2 data + network: selflink(resource(network,2)) + networkIP: test network_ip#2 data + subnetwork: selflink(resource(subnetwork,2)) +- accessConfigs: + - name: test name#3 data + natIP: test address#0 data + type: ONE_TO_ONE_NAT + - name: test name#4 data + natIP: test address#1 data + type: ONE_TO_ONE_NAT + - name: test name#5 data + natIP: test address#2 data + type: ONE_TO_ONE_NAT + aliasIpRanges: + - ipCidrRange: test ip_cidr_range#3 data + subnetworkRangeName: test subnetwork_range_name#3 data + - ipCidrRange: test ip_cidr_range#4 data + subnetworkRangeName: test subnetwork_range_name#4 data + - ipCidrRange: test ip_cidr_range#5 data + subnetworkRangeName: test subnetwork_range_name#5 data + - ipCidrRange: test ip_cidr_range#6 data + subnetworkRangeName: test subnetwork_range_name#6 data + - ipCidrRange: test ip_cidr_range#7 data + subnetworkRangeName: test subnetwork_range_name#7 data + name: test name#3 data + network: selflink(resource(network,0)) + networkIP: test network_ip#3 data + subnetwork: selflink(resource(subnetwork,0)) +project: "'test project#2 data'" +scheduling: + automaticRestart: true + onHostMaintenance: test on_host_maintenance#2 data + preemptible: true +serviceAccounts: +- email: true + scopes: + - ee + - ff + - gg + - hh +- email: false + scopes: + - ww + - xx + - yy + - zz +- email: true + scopes: + - rr + - ss + - tt +status: test status#2 data +statusMessage: test status_message#2 data +tags: + fingerprint: test fingerprint#2 data + items: + - yy + - zz +zone: test name#2 data +selfLink: selflink(resource(instance,2)) diff --git a/products/compute/files/tasks~instance.pp b/products/compute/files/tasks~instance.pp new file mode 100644 index 000000000000..272dd0cd17ca --- /dev/null +++ b/products/compute/files/tasks~instance.pp @@ -0,0 +1,133 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +$vm = $_name +$image = split($image_family, ':') # input: : + +# Convert provided zone to a region +$region_parts = split($zone, '-') +$region = "${region_parts[0]}-${region_parts[1]}" + +# Convenience because title is only used by Puppet, not GCP +$cred = 'bolt-credential' + +gauth_credential { $cred: + provider => serviceaccount, + path => $credential, + scopes => ['https://www.googleapis.com/auth/cloud-platform'], +} + +gcompute_network { $network_name: + project => $project, + credential => $cred, +} + +gcompute_region { $region: + project => $project, + credential => $cred, +} + +gcompute_zone { $zone: + project => $project, + credential => $cred, +} + +gcompute_machine_type { $machine_type: + zone => $zone, + project => $project, + credential => $cred, +} + +if $ensure == absent { + Gcompute_instance[$vm] -> Gcompute_disk[$vm] + if $allocate_static_ip { + Gcompute_instance[$vm] -> Gcompute_address[$vm] + } +} + +gcompute_disk { $vm: + ensure => $ensure, + size_gb => $size_gb, + source_image => gcompute_image_family($image[0], $image[1]), + zone => $zone, + project => $project, + credential => $cred, +} + +if $allocate_static_ip { + gcompute_address { $vm: + ensure => $ensure, + region => $region, + project => $project, + credential => $cred, + } + + gcompute_instance { $vm: + ensure => $ensure, + machine_type => $machine_type, + disks => [ + { + boot => true, + source => $vm, + auto_delete => true, + } + ], + network_interfaces => [ + { + network => $network_name, + access_configs => [ + { + name => 'External NAT', + nat_ip => $vm, + type => 'ONE_TO_ONE_NAT', + } + ], + } + ], + zone => $zone, + project => $project, + credential => $cred, + } +} else { + gcompute_instance { $vm: + ensure => $ensure, + machine_type => $machine_type, + disks => [ + { + boot => true, + source => $vm, + auto_delete => true, + } + ], + network_interfaces => [ + { + network => $network_name, + access_configs => [ + { + name => 'External NAT', + type => 'ONE_TO_ONE_NAT', + } + ], + } + ], + zone => $zone, + project => $project, + credential => $cred, + } +} + +notify { 'task:success': + message => "$vm", + require => Gcompute_instance[$vm], +} diff --git a/products/compute/forwarding_rule_properties.yaml b/products/compute/forwarding_rule_properties.yaml new file mode 100644 index 000000000000..b8ce0730f464 --- /dev/null +++ b/products/compute/forwarding_rule_properties.yaml @@ -0,0 +1,181 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- !ruby/object:Api::Type::Time + name: 'creationTimestamp' + description: 'Creation timestamp in RFC3339 text format.' + output: true +- !ruby/object:Api::Type::String + name: 'description' + description: | + An optional description of this resource. Provide this property when + you create the resource. +- !ruby/object:Api::Type::Integer + name: 'id' + description: 'The unique identifier for the resource.' + output: true +# TODO(nelsonjr): Make IPAddress work as a resource reference that works +# for both Address and GlobalAddress. (and deprecate gcompute_address_ref +# function) +- !ruby/object:Api::Type::String + name: 'IPAddress' + description: | + The IP address that this forwarding rule is serving on behalf of. + + Addresses are restricted based on the forwarding rule's load balancing + scheme (EXTERNAL or INTERNAL) and scope (global or regional). + + When the load balancing scheme is EXTERNAL, for global forwarding + rules, the address must be a global IP, and for regional forwarding + rules, the address must live in the same region as the forwarding + rule. If this field is empty, an ephemeral IPv4 address from the same + scope (global or regional) will be assigned. A regional forwarding + rule supports IPv4 only. A global forwarding rule supports either IPv4 + or IPv6. + + When the load balancing scheme is INTERNAL, this can only be an RFC + 1918 IP address belonging to the network/subnet configured for the + forwarding rule. By default, if this field is empty, an ephemeral + internal IP address will be automatically allocated from the IP range + of the subnet or network configured for this forwarding rule. + + An address can be specified either by a literal IP address or a URL + reference to an existing Address resource. The following examples are + all valid: + + * 100.1.2.3 + * https://www.googleapis.com/compute/v1/projects/project + /regions/region/addresses/address + * projects/project/regions/region/addresses/address + * regions/region/addresses/address + * global/addresses/address + * address +- !ruby/object:Api::Type::Enum + name: 'IPProtocol' + description: | + The IP protocol to which this rule applies. Valid options are TCP, + UDP, ESP, AH, SCTP or ICMP. + + When the load balancing scheme is INTERNAL, only TCP and UDP are + valid. + values: + - :TCP + - :UDP + - :ESP + - :AH + - :SCTP + - :ICMP +- !ruby/object:Api::Type::ResourceRef + name: 'backendService' + resource: 'BackendService' + imports: 'selfLink' + description: | + A reference to a BackendService to receive the matched traffic. + + This is used for internal load balancing. + (not used for external load balancing) +- !ruby/object:Api::Type::Enum + name: 'ipVersion' + description: | + The IP Version that will be used by this forwarding rule. Valid + options are IPV4 or IPV6. This can only be specified for a global + forwarding rule. + values: + - :IPV4 + - :IPV6 +- !ruby/object:Api::Type::Enum + name: 'loadBalancingScheme' + description: | + This signifies what the ForwardingRule will be used for and can only + take the following values: INTERNAL, EXTERNAL The value of INTERNAL + means that this will be used for Internal Network Load Balancing (TCP, + UDP). The value of EXTERNAL means that this will be used for External + Load Balancing (HTTP(S) LB, External TCP/UDP LB, SSL Proxy) + values: + - :INTERNAL + - :EXTERNAL +- !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the resource; provided by the client when the resource is + created. The name must be 1-63 characters long, and comply with + RFC1035. Specifically, the name must be 1-63 characters long and match + the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the + first character must be a lowercase letter, and all following + characters must be a dash, lowercase letter, or digit, except the last + character, which cannot be a dash. + required: true +- !ruby/object:Api::Type::ResourceRef + name: 'network' + resource: 'Network' + imports: 'selfLink' + description: | + For internal load balancing, this field identifies the network that + the load balanced IP should belong to for this Forwarding Rule. If + this field is not specified, the default network will be used. + + This field is not used for external load balancing. +# TODO(nelsonjr): When implementing new types enable convering the +# manifest input from a single value to a range of form NN-NN. The API +# accepts a single value, e.g. '80', but the API stores and returns +# '80-80'. This causes idempotency false positive. +- !ruby/object:Api::Type::String + name: 'portRange' + description: | + This field is used along with the target field for TargetHttpProxy, + TargetHttpsProxy, TargetSslProxy, TargetTcpProxy, TargetVpnGateway, + TargetPool, TargetInstance. + + Applicable only when IPProtocol is TCP, UDP, or SCTP, only packets + addressed to ports in the specified range will be forwarded to target. + Forwarding rules with the same [IPAddress, IPProtocol] pair must have + disjoint port ranges. + + Some types of forwarding target have constraints on the acceptable + ports: + + * TargetHttpProxy: 80, 8080 + * TargetHttpsProxy: 443 + * TargetTcpProxy: 25, 43, 110, 143, 195, 443, 465, 587, 700, 993, 995, + 1883, 5222 + * TargetSslProxy: 25, 43, 110, 143, 195, 443, 465, 587, 700, 993, 995, + 1883, 5222 + * TargetVpnGateway: 500, 4500 +- !ruby/object:Api::Type::Array + name: 'ports' + description: | + This field is used along with the backend_service field for internal + load balancing. + + When the load balancing scheme is INTERNAL, a single port or a comma + separated list of ports can be configured. Only packets addressed to + these ports will be forwarded to the backends configured with this + forwarding rule. + + You may specify a maximum of up to 5 ports. + item_type: Api::Type::String +- !ruby/object:Api::Type::ResourceRef + name: 'subnetwork' + resource: 'Subnetwork' + imports: 'selfLink' + description: | + A reference to a subnetwork. + + For internal load balancing, this field identifies the subnetwork that + the load balanced IP should belong to for this Forwarding Rule. + + If the network specified is in auto subnet mode, this field is + optional. However, if the network is in custom subnet mode, a + subnetwork must be specified. + + This field is not used for external load balancing. diff --git a/products/compute/healthcheck_protocol_props.yaml.erb b/products/compute/healthcheck_protocol_props.yaml.erb new file mode 100644 index 000000000000..59ccc3b2d6a7 --- /dev/null +++ b/products/compute/healthcheck_protocol_props.yaml.erb @@ -0,0 +1,49 @@ +<% if ['HTTP', 'HTTPS'].include?(protocol) -%> +- !ruby/object:Api::Type::String + name: 'host' + description: | + The value of the host header in the <%= protocol -%> health check request. + If left empty (default value), the public IP on behalf of which this health + check is performed will be used. +- !ruby/object:Api::Type::String + name: 'requestPath' + description: | + The request path of the <%= protocol -%> health check request. + The default value is /. +<% elsif ['SSL', 'TCP'].include?(protocol) -%> +- !ruby/object:Api::Type::String + name: 'request' + description: | + The application data to send once the <%= protocol -%> connection has been + established (default value is empty). If both request and response are + empty, the connection establishment alone will indicate health. The request + data can only be ASCII. +- !ruby/object:Api::Type::String + name: 'response' + description: | + The bytes to match against the beginning of the response data. If left empty + (the default value), any response will indicate health. The response data + can only be ASCII. +<% + else + raise "Unknown protocol: #{protocol}" + end +-%> +- !ruby/object:Api::Type::Integer + name: 'port' + description: | + The TCP port number for the <%= protocol -%> health check request. + The default value is <%= port -%>. +- !ruby/object:Api::Type::String + name: 'portName' + description: | + Port name as defined in InstanceGroup#NamedPort#name. If both port and + port_name are defined, port takes precedence. +- !ruby/object:Api::Type::Enum + name: 'proxyHeader' + description: | + Specifies the type of proxy header to append before sending data to the + backend, either NONE or PROXY_V1. The default is NONE. + values: + - :NONE + - :PROXY_V1 diff --git a/products/compute/helpers/api_gcompute_disk.rb b/products/compute/helpers/api_gcompute_disk.rb new file mode 100644 index 000000000000..8e0bcd4e1265 --- /dev/null +++ b/products/compute/helpers/api_gcompute_disk.rb @@ -0,0 +1,57 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/compute/network/post' + +module Google + module Compute + module Api + # A helper class to provide access to (some) Google Compute Engine API. + class Disk + def initialize(name, zone, project, cred) + @name = name + @zone = zone + @project = project + @cred = cred + end + + # TODO(nelsonjr): Implement this as gcompute_disk_snapshot { } + # TODO(nelsonjr): Make this function wait for the operation to complete + def snapshot(target, properties = {}) + snapshot_request = ::Google::Compute::Network::Post.new( + gcompute_disk_snapshot, @cred, 'application/json', + # Ordering of 'kind' must be moved so that testing + # expectations do not fail. + { kind: properties[:kind], name: target }.merge(properties).to_json + ) + response = JSON.parse(snapshot_request.send.body) + raise Puppet::Error, response['error']['errors'][0]['message'] \ + if response['error'] + end + + private + + def gcompute_disk_snapshot + URI.parse( + format( + '%s/%s', + Puppet::Type.type(:gcompute_disk).provider(:google).self_link( + name: @name, zone: @zone, project: @project + ), 'createSnapshot' + ) + ) + end + end + end + end +end diff --git a/products/compute/helpers/api_gcompute_instance.rb b/products/compute/helpers/api_gcompute_instance.rb new file mode 100644 index 000000000000..5f12c1bb4be7 --- /dev/null +++ b/products/compute/helpers/api_gcompute_instance.rb @@ -0,0 +1,53 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/compute/network/post' + +module Google + module Compute + module Api + # A helper class to provide access to (some) Google Compute Engine API. + class Instance + def initialize(name, zone, project, cred) + @name = name + @zone = zone + @project = project + @cred = cred + end + + # TODO(nelsonjr): Make this function wait for the operation to complete + def reset + reset_request = ::Google::Compute::Network::Post.new( + gcompute_instance_reset, @cred, 'application/json', {}.to_json + ) + response = JSON.parse(reset_request.send.body) + raise Puppet::Error, response['error']['errors'][0]['message'] \ + if response['error'] + end + + private + + def gcompute_instance_reset + URI.parse( + format( + '%s/%s', + Puppet::Type.type(:gcompute_instance).provider(:google).self_link( + name: @name, zone: @zone, project: @project + ), 'reset' + ) + ) + end + end + end + end +end diff --git a/products/compute/helpers/expect_snapshot.rb.erb b/products/compute/helpers/expect_snapshot.rb.erb new file mode 100644 index 000000000000..0f4eed3f56ad --- /dev/null +++ b/products/compute/helpers/expect_snapshot.rb.erb @@ -0,0 +1,50 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def expect_network_create(id, expected_body, data = {}) + default_props = { + zone: 'test name#0 data', + project: 'test project#0 data' + } + + merged_uri = uri_data(id).merge(default_props).merge(data) + body = { kind: 'compute#operation', + status: 'DONE', targetLink: self_link(merged_uri) }.to_json + # Remove refs that are also part of the body + expected_body = Hash[expected_body.map do |k, v| + [k.is_a?(Symbol) ? k.id2name : k, v] + end] + + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + + debug_network "!! POST #{collection(merged_uri)}" + expect(Google::Compute::Network::Post).to receive(:new) + .with(gcompute_disk_snapshot_uri(merged_uri), + instance_of(Google::FakeAuthorization), + 'application/json', expected_body.to_json) do |args| + debug_network ">> POST #{args} = body(#{body})" + request + end +end + +def gcompute_disk_snapshot_uri(data) + URI.parse( + format( + '%s/%s', + Puppet::Type.type(:gcompute_disk).provider(:google).self_link( + name: data[:name], zone: data[:zone], project: data[:project] + ), 'createSnapshot' + ) + ) +end diff --git a/products/compute/helpers/instance_metadata.rb b/products/compute/helpers/instance_metadata.rb new file mode 100644 index 000000000000..c90a261413ee --- /dev/null +++ b/products/compute/helpers/instance_metadata.rb @@ -0,0 +1,46 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Mask the fact healthChecks array is actually a single object of type +# HttpHealthCheck. + +# TODO(nelsonjr): Implement updating metadata on exsiting resources. + +# Expose instance 'metadata' as a simple name/value pair hash. However the API +# defines metadata as a NestedObject with the following layout: +# +# metadata { +# fingerprint: 'hash-of-last-metadata' +# items: [ +# { +# key: 'metadata1-key' +# value: 'metadata1-value' +# }, +# ... +# ] +# } +# +# Fingerpint is an optimistic locking mechanism for updates, which requires +# adding the 'fingerprint' of the last metadata to allow update. +def self.metadata_encoder(metadata) + items = metadata.map { |k, v| { key: k, value: v } } + metadata.clear + metadata[:items] = items +end + +# Map metadata.items[]{key:,value:} => metadata[key]=value +def self.metadata_decoder(metadata) + metadata_items = metadata['items'] + metadata.clear + metadata.merge!(Hash[metadata_items.map { |i| [i['key'], i['value']] }]) \ + unless metadata_items.nil? +end diff --git a/products/compute/helpers/provider_disk_type.rb b/products/compute/helpers/provider_disk_type.rb new file mode 100644 index 000000000000..df5ebaf4955e --- /dev/null +++ b/products/compute/helpers/provider_disk_type.rb @@ -0,0 +1,19 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def dirty_display_formatted + @dirty.map do |name, change| + ["#{name.id2name} expected #{change[:to]},", + "but platform reports #{change[:from]}"].join(' ') + end.join('. Also ') +end diff --git a/products/compute/helpers/provider_instance.rb b/products/compute/helpers/provider_instance.rb new file mode 100644 index 000000000000..eaff2fecb222 --- /dev/null +++ b/products/compute/helpers/provider_instance.rb @@ -0,0 +1,31 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Mask the fact healthChecks array is actually a single object of type +# HttpHealthCheck. + +def self.encode_request(request) + metadata_encoder(request[:metadata]) unless request[:metadata].nil? + request +end + +def encode_request(resource_request) + self.class.encode_request(resource_request) +end + +def self.decode_response(response, kind) + response = JSON.parse(response.body) + return response unless kind == 'compute#instance' + + metadata_decoder(response['metadata']) unless response['metadata'].nil? + response +end diff --git a/products/compute/helpers/provider_instance_template.rb b/products/compute/helpers/provider_instance_template.rb new file mode 100644 index 000000000000..f55ee6f8a73e --- /dev/null +++ b/products/compute/helpers/provider_instance_template.rb @@ -0,0 +1,35 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# Mask the fact healthChecks array is actually a single object of type +# HttpHealthCheck. + +def self.encode_request(request) + metadata_encoder(request[:properties].metadata) \ + unless request[:properties].nil? || request[:properties].metadata.nil? + request +end + +def encode_request(resource_request) + self.class.encode_request(resource_request) +end + +def self.decode_response(response, kind) + response = JSON.parse(response.body) + return response unless kind == 'compute#instanceTemplate' + + properties = response['properties'] + metadata_decoder(properties['metadata']) \ + unless properties.nil? || properties['metadata'].nil? + + response +end diff --git a/products/compute/helpers/provider_network.rb b/products/compute/helpers/provider_network.rb new file mode 100644 index 000000000000..a0bcb3025457 --- /dev/null +++ b/products/compute/helpers/provider_network.rb @@ -0,0 +1,29 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def handle_auto_to_custom_change + # We allow changing the auto_create_subnetworks from true => false + # (which will make the network going from Auto to Custom) + auto_change = @dirty[:auto_create_subnetworks] + raise 'Cannot convert a network from Custom back to Auto' \ + if auto_change[:from] == false && auto_change[:to] == true + # TODO(nelsonjr): Enable converting from Auto => Custom via call to + # special method URL. See tracking work item: + # https://bugzilla.graphite.cloudnativeapp.com/show_bug.cgi?id=174 + raise [ + 'Conversion from Auto to Custom not implemented yet.', + 'See', ['https://bugzilla.graphite.cloudnativeapp.com', + 'show_bug.cgi?id=174'].join('/'), + 'for more details' + ].join(' ') +end diff --git a/products/compute/helpers/provider_target_pool.rb b/products/compute/helpers/provider_target_pool.rb new file mode 100644 index 000000000000..2924e52ea62f --- /dev/null +++ b/products/compute/helpers/provider_target_pool.rb @@ -0,0 +1,48 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Mask the fact healthChecks array is actually a single object of type +# HttpHealthCheck. +# +# Google Compute Engine API defines healthChecks as a list but it can only +# take [0, 1] elements. To make it simpler to declare we'll map that to a +# single object and encode/decode as appropriate. +def self.encode_request(request) + unless request[:healthCheck].nil? + # Map one allowed health check into array + request[:healthChecks] = [request[:healthCheck]] + request.delete(:healthCheck) + end + request +end + +def encode_request(resource_request) + self.class.encode_request(resource_request) +end + +# Mask healthChecks into a single element. +# @see self.encode_request for details +def self.decode_request(response, kind) + response = JSON.parse(response.body) + + return response unless kind == 'compute#targetPool' + + # Map healthChecks[0] => healthCheck + unless response['healthChecks'].nil? + response['healthCheck'] = response['healthChecks'][0] \ + unless response['healthChecks'].empty? + response.delete('healthChecks') + end + + response +end diff --git a/products/compute/instance_disks.yaml b/products/compute/instance_disks.yaml new file mode 100644 index 000000000000..1290aa2db57f --- /dev/null +++ b/products/compute/instance_disks.yaml @@ -0,0 +1,201 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- !ruby/object:Api::Type::Array + name: 'disks' + description: | + An array of disks that are associated with the instances that are + created from this template. +<% if type == 'Instance' -%> + input: true +<% end -%> + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::Boolean + name: 'autoDelete' + description: | + Specifies whether the disk will be auto-deleted when the + instance is deleted (but not when the disk is detached from + the instance). + + Tip: Disks should be set to autoDelete=true + so that leftover disks are not left behind on machine + deletion. + - !ruby/object:Api::Type::Boolean + name: 'boot' + description: | + Indicates that this is a boot disk. The virtual machine will + use the first partition of the disk for its root filesystem. + - !ruby/object:Api::Type::String + name: 'deviceName' + description: | + Specifies a unique device name of your choice that is + reflected into the /dev/disk/by-id/google-* tree of a Linux + operating system running within the instance. This name can + be used to reference the device for mounting, resizing, and + so on, from within the instance. + - !ruby/object:Api::Type::NestedObject + name: 'diskEncryptionKey' + description: | + Encrypts or decrypts a disk using a customer-supplied + encryption key. + properties: + - !ruby/object:Api::Type::String + name: 'rawKey' + description: | + Specifies a 256-bit customer-supplied encryption key, + encoded in RFC 4648 base64 to either encrypt or decrypt + this resource. + - !ruby/object:Api::Type::String + name: 'rsaEncryptedKey' + description: | + Specifies an RFC 4648 base64 encoded, RSA-wrapped + 2048-bit customer-supplied encryption key to either + encrypt or decrypt this resource. + - !ruby/object:Api::Type::String + name: 'sha256' + description: | + The RFC 4648 base64 encoded SHA-256 hash of the + customer-supplied encryption key that protects this + resource. + output: true + - !ruby/object:Api::Type::Integer + name: 'index' + description: | + Assigns a zero-based index to this disk, where 0 is + reserved for the boot disk. For example, if you have many + disks attached to an instance, each disk would have a + unique index number. If not specified, the server will + choose an appropriate value. + - !ruby/object:Api::Type::NestedObject + name: 'initializeParams' + description: | + Specifies the parameters for a new disk that will be + created alongside the new instance. Use initialization + parameters to create boot disks or local SSDs attached to + the new instance. + input: true + required: true + properties: + - !ruby/object:Api::Type::String + name: 'diskName' + description: | + Specifies the disk name. If not specified, the default + is to use the name of the instance. + - !ruby/object:Api::Type::Integer + name: 'diskSizeGb' + description: Specifies the size of the disk in base-2 GB. + # diskStorageType - deprecated + - !ruby/object:Api::Type::ResourceRef + name: 'diskType' + description: | + Reference to a gcompute_disk_type resource. + Specifies the disk type to use to create the instance. + If not specified, the default is pd-standard. + resource: 'DiskType' + imports: 'selfLink' + - !ruby/object:Api::Type::String + name: 'sourceImage' + description: | + The source image to create this disk. When creating a + new instance, one of initializeParams.sourceImage or + disks.source is required. To create a disk with one of + the public operating system images, specify the image + by its family name. + - !ruby/object:Api::Type::NestedObject + name: 'sourceImageEncryptionKey' + description: | + The customer-supplied encryption key of the source + image. Required if the source image is protected by a + customer-supplied encryption key. + + Instance templates do not store customer-supplied + encryption keys, so you cannot create disks for + instances in a managed instance group if the source + images are encrypted with your own keys. + properties: + - !ruby/object:Api::Type::String + name: 'rawKey' + description: | + Specifies a 256-bit customer-supplied encryption + key, encoded in RFC 4648 base64 to either encrypt + or decrypt this resource. + - !ruby/object:Api::Type::String + name: 'sha256' + description: | + The RFC 4648 base64 encoded SHA-256 hash of the + customer-supplied encryption key that protects this + resource. + output: true + - !ruby/object:Api::Type::Enum + name: 'interface' + description: | + Specifies the disk interface to use for attaching this + disk, which is either SCSI or NVME. The default is SCSI. + Persistent disks must always use SCSI and the request will + fail if you attempt to attach a persistent disk in any + other format than SCSI. + values: + - :SCSI + - :NVME + # Ignoring kind - It's a constant and we don't need it. + # TODO(alexstephen): Place in licenses - it's a Array of + # ResourceRefs + - !ruby/object:Api::Type::Enum + name: 'mode' + description: | + The mode in which to attach this disk, either READ_WRITE or + READ_ONLY. If not specified, the default is to attach the + disk in READ_WRITE mode. + values: + - :READ_WRITE + - :READ_ONLY + # This is the name, not selfLink of a disk. +<% if type == 'InstanceTemplate' -%> + - !ruby/object:Api::Type::ResourceRef + name: 'source' + resource: 'Disk' + imports: 'name' + description: | + Reference to a gcompute_disk resource. When creating a new instance, + one of initializeParams.sourceImage or disks.source is required. + + If desired, you can also attach existing non-root + persistent disks using this property. This field is only + applicable for persistent disks. + + Note that for InstanceTemplate, specify the disk name, not + the URL for the disk. +<% elsif type == 'Instance' -%> + - !ruby/object:Api::Type::ResourceRef + name: 'source' + resource: 'Disk' + imports: 'selfLink' + description: | + Reference to a gcompute_disk resource. When creating a new instance, + one of initializeParams.sourceImage or disks.source is required. + + If desired, you can also attach existing non-root + persistent disks using this property. This field is only + applicable for persistent disks. +<% else -%> +<% raise "type #{type} is invalid for using the disks property" -%> +<% end -%> + - !ruby/object:Api::Type::Enum + name: 'type' + description: | + Specifies the type of the disk, either SCRATCH or + PERSISTENT. If not specified, the default is PERSISTENT. + values: + - :SCRATCH + - :PERSISTENT diff --git a/products/compute/instance_guestaccelerators.yaml b/products/compute/instance_guestaccelerators.yaml new file mode 100644 index 000000000000..444eaff568e9 --- /dev/null +++ b/products/compute/instance_guestaccelerators.yaml @@ -0,0 +1,32 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- !ruby/object:Api::Type::Array + name: 'guestAccelerators' + description: | + List of the type and count of accelerator cards attached to the + instance + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::Integer + name: 'acceleratorCount' + description: | + The number of the guest accelerator cards exposed to this + instance. + # TODO(alexstephen): Change to ResourceRef once AcceleratorType is + # created. + - !ruby/object:Api::Type::String + name: 'acceleratorType' + description: | + Full or partial URL of the accelerator type resource to expose + to this instance. diff --git a/products/compute/instance_metadata.yaml b/products/compute/instance_metadata.yaml new file mode 100644 index 000000000000..197767bc6c5e --- /dev/null +++ b/products/compute/instance_metadata.yaml @@ -0,0 +1,41 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Implement updating metadata *after* resource is created. + +# Expose instance 'metadata' as a simple name/value pair hash. However the API +# defines metadata as a NestedObject with the following layout: +# +# metadata { +# fingerprint: 'hash-of-last-metadata' +# items: [ +# { +# key: 'metadata1-key' +# value: 'metadata1-value' +# }, +# ... +# ] +# } +# +# Fingerpint is an optimistic locking mechanism for updates, which requires +# adding the 'fingerprint' of the last metadata to allow update. +# +# To comply with the API please add an encoder: and decoder: to the provider. +- !ruby/object:Api::Type::NameValues + name: 'metadata' + description: | + The metadata key/value pairs to assign to instances that are + created from this template. These pairs can consist of custom + metadata or predefined keys. + key_type: Api::Type::String + value_type: Api::Type::String diff --git a/products/compute/instance_networkinterfaces.yaml b/products/compute/instance_networkinterfaces.yaml new file mode 100644 index 000000000000..188265641845 --- /dev/null +++ b/products/compute/instance_networkinterfaces.yaml @@ -0,0 +1,121 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- !ruby/object:Api::Type::Array + name: 'networkInterfaces' + description: | + An array of configurations for this interface. This specifies + how this interface is configured to interact with other + network services, such as connecting to the internet. Only + one network interface is supported per instance. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::Array + name: 'accessConfigs' + description: | + An array of configurations for this interface. Currently, only + one access config, ONE_TO_ONE_NAT, is supported. If there are no + accessConfigs specified, then this instance will have no + external internet access. + item_type: !ruby/object:Api::Type::NestedObject + properties: + # 'kind' is not needed for object convergence + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name of this access configuration. The + default and recommended name is External NAT but you can + use any arbitrary string you would like. For example, My + external IP or Network Access. + required: true + - !ruby/object:Api::Type::ResourceRef + name: 'natIP' + resource: 'Address' + imports: 'address' + description: | + Specifies the title of a gcompute_address. + An external IP address associated with this instance. + Specify an unused static external IP address available to + the project or leave this field undefined to use an IP + from a shared ephemeral IP address pool. If you specify a + static external IP address, it must live in the same + region as the zone of the instance. + required: true + - !ruby/object:Api::Type::Enum + name: 'type' + description: | + The type of configuration. The default and only option is + ONE_TO_ONE_NAT. + values: + - :ONE_TO_ONE_NAT + required: true + - !ruby/object:Api::Type::Array + name: 'aliasIpRanges' + description: | + An array of alias IP ranges for this network interface. Can + only be specified for network interfaces on subnet-mode + networks. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'ipCidrRange' + description: | + The IP CIDR range represented by this alias IP range. + This IP CIDR range must belong to the specified + subnetwork and cannot contain IP addresses reserved by + system or used by other network interfaces. This range + may be a single IP address (e.g. 10.2.3.4), a netmask + (e.g. /24) or a CIDR format string (e.g. 10.1.2.0/24). + - !ruby/object:Api::Type::String + name: 'subnetworkRangeName' + description: | + Optional subnetwork secondary range name specifying + the secondary range from which to allocate the IP + CIDR range for this alias IP range. If left + unspecified, the primary range of the subnetwork will + be used. + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name of the network interface, generated by the + server. For network devices, these are eth0, eth1, etc + output: true + - !ruby/object:Api::Type::ResourceRef + name: 'network' + resource: 'Network' + imports: 'selfLink' + description: | + Specifies the title of an existing gcompute_network. When creating + an instance, if neither the network nor the subnetwork is specified, + the default network global/networks/default is used; if the network + is not specified but the subnetwork is specified, the network is + inferred. + - !ruby/object:Api::Type::String + name: 'networkIP' + description: | + An IPv4 internal network address to assign to the + instance for this network interface. If not specified + by the user, an unused internal IP is assigned by the + system. + - !ruby/object:Api::Type::ResourceRef + name: 'subnetwork' + resource: 'Subnetwork' + imports: 'selfLink' + description: | + Reference to a gcompute_subnetwork resource. + If the network resource is in legacy mode, do not + provide this property. If the network is in auto + subnet mode, providing the subnetwork is optional. If + the network is in custom subnet mode, then this field + should be specified. + # networkInterfaces.kind is not necessary for convergence. diff --git a/products/compute/instance_scheduling.yaml b/products/compute/instance_scheduling.yaml new file mode 100644 index 000000000000..f632e7d5b96b --- /dev/null +++ b/products/compute/instance_scheduling.yaml @@ -0,0 +1,38 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- !ruby/object:Api::Type::NestedObject + name: 'scheduling' + description: Sets the scheduling options for this instance. + properties: + - !ruby/object:Api::Type::Boolean + name: 'automaticRestart' + description: | + Specifies whether the instance should be automatically restarted + if it is terminated by Compute Engine (not terminated by a user). + You can only set the automatic restart option for standard + instances. Preemptible instances cannot be automatically + restarted. + - !ruby/object:Api::Type::String + name: 'onHostMaintenance' + description: | + Defines the maintenance behavior for this instance. For standard + instances, the default behavior is MIGRATE. For preemptible + instances, the default and only possible behavior is TERMINATE. + For more information, see Setting Instance Scheduling Options. + - !ruby/object:Api::Type::Boolean + name: 'preemptible' + description: | + Defines whether the instance is preemptible. This can only be set + during instance creation, it cannot be set or changed after the + instance has been created. diff --git a/products/compute/instance_serviceaccounts.yaml b/products/compute/instance_serviceaccounts.yaml new file mode 100644 index 000000000000..0ead5d9d29f6 --- /dev/null +++ b/products/compute/instance_serviceaccounts.yaml @@ -0,0 +1,30 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- !ruby/object:Api::Type::Array + name: 'serviceAccounts' + description: | + A list of service accounts, with their specified scopes, authorized + for this instance. Only one service account per VM instance is + supported. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::Boolean + name: 'email' + description: Email address of the service account. + - !ruby/object:Api::Type::Array + name: scopes + description: | + The list of scopes to be made available for this service + account. + item_type: Api::Type::String diff --git a/products/compute/instance_tags.yaml b/products/compute/instance_tags.yaml new file mode 100644 index 000000000000..67bac376b07b --- /dev/null +++ b/products/compute/instance_tags.yaml @@ -0,0 +1,38 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- !ruby/object:Api::Type::NestedObject + name: 'tags' + description: | + A list of tags to apply to this instance. Tags are used to identify + valid sources or targets for network firewalls and are specified by + the client during instance creation. The tags can be later modified + by the setTags method. Each tag within the list must comply with + RFC1035. + properties: + # TODO(alexstephen) Investigate bytes type + - !ruby/object:Api::Type::String + name: 'fingerprint' + description: | + Specifies a fingerprint for this request, which is essentially a + hash of the metadata's contents and used for optimistic locking. + The fingerprint is initially generated by Compute Engine and + changes after every request to modify or update metadata. You + must always provide an up-to-date fingerprint hash in order to + update or change metadata. + - !ruby/object:Api::Type::Array + name: 'items' + description: | + An array of tags. Each tag must be 1-63 characters long, and + comply with RFC1035. + item_type: Api::Type::String diff --git a/products/compute/puppet-e2e.yaml b/products/compute/puppet-e2e.yaml new file mode 100644 index 000000000000..c323bdce8e90 --- /dev/null +++ b/products/compute/puppet-e2e.yaml @@ -0,0 +1,311 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Puppet::Tester + product: 'Compute' + tests: + - !ruby/object:Puppet::StandardTest + name: 'Address' + verifiers: + - phase: ALL + command: | + gcloud compute addresses describe + --project=google.com:graphite-playground + --region=us-west1 + puppet-e2e-test1 + # TODO(alexstephen): Implement Backend Bucket test. + # E2E tester has no support for running scripts in other modules + # A Storage Bucket is required for this test. + #- !ruby/object:Puppet::StandardTest + # name: 'BackendBucket' + - !ruby/object:Puppet::StandardTest + name: 'BackendService' + verifiers: + - phase: ALL + command: | + gcloud compute backend-services describe + --project=google.com:graphite-playground + --global + puppet-e2e-my-app-backend + - !ruby/object:Puppet::StandardTest + name: 'Disk' + verifiers: + - phase: ALL + command: | + gcloud compute disks describe + --project=google.com:graphite-playground + --zone=us-central1-a + puppet-e2e-data-disk-1 + - !ruby/object:Puppet::VirtualTest + name: 'DiskType' + - !ruby/object:Puppet::StandardTest + name: 'Firewall' + change: + - name: 'change' + apply: + - run: 'firewall~change1.pp' + exits: [2, 0] + - name: 'change{again}' + apply: + - run: 'firewall~change1.pp' + exits: 0 + verifiers: + - phase: ALL + command: | + gcloud compute firewall-rules describe + --project=google.com:graphite-playground + puppet-e2e-test-fw-allow-ssh + - !ruby/object:Puppet::StandardTest + name: 'ForwardingRule' + verifiers: + - phase: ALL + command: | + gcloud compute forwarding-rules describe + --project=google.com:graphite-playground + puppet-e2e-test1 + - !ruby/object:Puppet::StandardTest + name: 'GlobalAddress' + verifiers: + - phase: ALL + command: | + gcloud compute addresses describe + --project=google.com:graphite-playground + --global + puppet-e2e-my-app-lb-address + - !ruby/object:Puppet::StandardTest + name: 'HealthCheck' + verifiers: + - phase: ALL + command: | + gcloud compute health-checks describe + --project=google.com:graphite-playground + puppet-e2e-my-app-tcp-hc + - !ruby/object:Puppet::StandardTest + name: 'HttpHealthCheck' + verifiers: + - phase: ALL + command: | + gcloud compute http-health-checks describe + --project=google.com:graphite-playground + puppet-e2e-my-app-http-hc + - !ruby/object:Puppet::StandardTest + name: 'HttpsHealthCheck' + verifiers: + - phase: ALL + command: | + gcloud compute https-health-checks describe + --project=google.com:graphite-playground + puppet-e2e-my-app-https-hc + - !ruby/object:Puppet::StandardTest + name: 'Image' + verifiers: + - phase: ALL + command: | + gcloud compute images describe + --project=google.com:graphite-playground + puppet-e2e-test-image + - !ruby/object:Puppet::StandardTest + name: 'InstanceGroup' + verifiers: + - phase: ALL + command: | + gcloud compute instance-groups unmanaged describe + --project=google.com:graphite-playground + --zone=us-central1-a + puppet-e2e-my-puppet-masters + - !ruby/object:Puppet::StandardTest + name: 'InstanceGroupManager' + verifiers: + - phase: ALL + command: | + gcloud compute instance-groups managed describe + --project=google.com:graphite-playground + --zone=us-west1-a + puppet-e2e-test1 + - !ruby/object:Puppet::StandardTest + name: 'InstanceTemplate' + verifiers: + - phase: ALL + command: | + gcloud compute instance-templates describe + --project=google.com:graphite-playground + puppet-e2e-instance-template + - !ruby/object:Puppet::StandardTest + name: 'Instance' + verifiers: + - phase: ALL + command: | + gcloud compute instances describe + --project=google.com:graphite-playground + --zone=us-central1-a + puppet-e2e-instance-test + - !ruby/object:Puppet::VirtualTest + name: 'License' + # Automatic Network + - !ruby/object:Puppet::StandardTest + name: 'NetworkAuto' + create: 'network~auto.pp' + delete: 'delete_network.pp' + env: + network_id: auto-1234 + verifiers: + - phase: ALL + command: | + gcloud compute networks describe + --project=google.com:graphite-playground + puppet-e2e-mynetwork-auto-1234 + # Custom Network + - !ruby/object:Puppet::StandardTest + name: 'NetworkCustom' + create: 'network~custom.pp' + delete: 'delete_network.pp' + env: + network_id: custom-1234 + verifiers: + - phase: ALL + command: | + gcloud compute networks describe + --project=google.com:graphite-playground + puppet-e2e-mynetwork-custom-1234 + # Convert to Custom Network + - !ruby/object:Puppet::StandardTest + name: 'NetworkConvertCustom' + create: 'network~convert_to_custom.pp' + delete: 'delete_network.pp' + env: + network_id: conv-cust-1234 + verifiers: + - phase: ALL + command: | + gcloud compute networks describe + --project=google.com:graphite-playground + puppet-e2e-mynetwork-conv-cust-1234 + # Legacy Network + - !ruby/object:Puppet::StandardTest + name: 'NetworkLegacy' + create: 'network~legacy.pp' + delete: 'delete_network.pp' + env: + network_id: legacy-1234 + verifiers: + - phase: ALL + command: | + gcloud compute networks describe + --project=google.com:graphite-playground + puppet-e2e-mynetwork-legacy-1234 + # TODO(alexstephen): Change when Region is virtual. + - name: 'Region' + phases: + - name: 'create' + apply: + - run: 'region.pp' + # This allows for both virtual + non-virtual running. + exits: [0, 2] + - !ruby/object:Puppet::StandardTest + name: 'Route' + verifiers: + - phase: ALL + command: | + gcloud compute routes describe + --project=google.com:graphite-playground + puppet-e2e-corp-route + - !ruby/object:Puppet::StandardTest + name: 'Snapshot' + verifiers: + - phase: ALL + command: | + gcloud compute snapshots describe + --project=google.com:graphite-playground + puppet-e2e-sample-snapshot-1 + - !ruby/object:Puppet::StandardTest + name: 'SslCertificate' + verifiers: + - phase: ALL + command: | + gcloud compute ssl-certificates describe + --project=google.com:graphite-playground + puppet-e2e-sample-certificate +# TODO(alexstephen): Fix subnetwork test +# Puppet doesn't like the network type of the gcompute_network block +# Currently matches the Chef test which works properly. +# - !ruby/object:Puppet::StandardTest +# name: 'Subnetwork' +# verifiers: +# - phase: ALL +# command: | +# gcloud compute networks describe +# --project=google.com:graphite-playground +# puppet-e2e-servers +# pre: +# - name: 'delete_network' +# apply: +# - run: 'delete_network.pp' +# env: +# network_id: subnetwork +# exits: [0, 2] +# post: +# - name: 'delete_network' +# apply: +# - run: 'delete_network.pp' +# env: +# network_id: subnetwork +# exits: [0, 2] + - !ruby/object:Puppet::StandardTest + name: 'TargetHttpProxy' + verifiers: + - phase: ALL + command: | + gcloud compute target-http-proxies describe + --project=google.com:graphite-playground + puppet-e2e-my-http-proxy + - !ruby/object:Puppet::StandardTest + name: 'TargetHttpsProxy' + verifiers: + - phase: ALL + command: | + gcloud compute target-https-proxies describe + --project=google.com:graphite-playground + puppet-e2e-my-https-proxy + - !ruby/object:Puppet::StandardTest + name: 'TargetPool' + verifiers: + - phase: ALL + command: | + gcloud compute target-pools describe + --project=google.com:graphite-playground + puppet-e2e-test1 + # gcloud compute does not have a 'target-tcp-proxies' to verify the + # TargetTcpProxy calls + - !ruby/object:Puppet::StandardTest + name: 'TargetTcpProxy' + - !ruby/object:Puppet::StandardTest + name: 'TargetSslProxy' + verifiers: + - phase: ALL + command: | + gcloud compute target-ssl-proxies describe + --project=google.com:graphite-playground + puppet-e2e-my-ssl-proxy + - !ruby/object:Puppet::StandardTest + name: 'UrlMap' + verifiers: + - phase: ALL + command: | + gcloud compute url-maps describe + --project=google.com:graphite-playground + puppet-e2e-my-url-map diff --git a/products/compute/puppet.yaml b/products/compute/puppet.yaml new file mode 100644 index 000000000000..5a0e1db98cc8 --- /dev/null +++ b/products/compute/puppet.yaml @@ -0,0 +1,880 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.2.2' + source: 'https://github.com/GoogleCloudPlatform/puppet-google-compute' + homepage: 'https://github.com/GoogleCloudPlatform/puppet-google-compute' + issues: + 'https://github.com/GoogleCloudPlatform/puppet-google-compute/issues' + summary: 'A Puppet module to manage Google Compute Engine resources' + tags: + - google + - cloud + - compute + - engine + - gce + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google/gauth' + versions: '>= 0.2.0 < 0.3.0' + operating_systems: +<%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> +# TODO(nelsonjr): Match all special behavior Puppet <=> Chef. +objects: !ruby/object:Api::Resource::HashArray + BackendService: + # TODO(alexstephen): Document the access_api_results field. + access_api_results: true + resource_to_request_patch: | + unless @fetched.nil? + # Convert to pure JSON + request = JSON.parse(request.to_json) + request['fingerprint'] = @fetched['fingerprint'] + end + Disk: + # TODO(nelsonjr): Implement specific actions, such as resize and setLabels. + flush: raise 'Disk cannot be edited' + DiskType: + # TODO(nelsonjr): Make sure that attempts to create a disk fails + flush: | + raise [ + 'DiskType requirements specified in the manifest do not match', + "values provided by Google Cloud Platform: #{dirty_display_formatted}" + ].join(' ') + provider_helpers: + include: + - 'products/compute/helpers/provider_disk_type.rb' + Instance: + provider_helpers: + include: + - 'products/compute/helpers/provider_instance.rb' + - 'products/compute/helpers/instance_metadata.rb' + InstanceGroup: + # TODO(nelsonjr): Implement specific actions, such as addInstance and + # setNamedPorts. + flush: raise 'InstanceGroup cannot be edited.' + InstanceTemplate: + provider_helpers: + include: + - 'products/compute/helpers/provider_instance_template.rb' + - 'products/compute/helpers/instance_metadata.rb' + Network: + flush: | + unless @dirty.keys == [:auto_create_subnetworks] + raise ['Network specification mismatch and cannot be edited.', + 'The only allowed change is from Auto to Custom type.'].join(' ') + end + handle_auto_to_custom_change + provider_helpers: + include: + - 'products/compute/helpers/provider_network.rb' + Snapshot: + requires: + - google/compute/api/gcompute_disk + create: | + disk = Google::Compute::Api::Disk.new(resource[:source].resource, + resource[:zone].resource, + resource[:project], + fetch_auth(resource)) + # TODO(nelsonjr): Wait for operation to complete + disk.snapshot( + resource[:name], + Google::HashUtils.symbolize_keys(JSON.parse(resource_to_request)) + ) + TargetPool: + provider_helpers: + include: + - 'products/compute/helpers/provider_target_pool.rb' + Region: + # TODO(nelsonjr): Make sure that attempts to create a region fails + flush: raise 'Region cannot be edited' if @dirty +functions: + - !ruby/object:Provider::Config::Function + name: 'gcompute_address_ip' + description: | + Returns the IP address associated with the Address managed by a + `gcompute_address` resource. + arguments: + - !ruby/object:Provider::Config::Function::Argument + name: name + type: Api::Type::String + description: 'the name of the address resource' + - !ruby/object:Provider::Config::Function::Argument + name: region + type: Api::Type::String + description: 'the region where the address resource is allocated' + - !ruby/object:Provider::Config::Function::Argument + name: project + type: Api::Type::String + description: 'the project name where resource is allocated' + - !ruby/object:Provider::Config::Function::Argument + name: cred + type: Google::Authorization + description: | + the credential to use to authorize the information request + requires: + - json + - google/authorization + - google/compute/network/get + - puppet + code: | + get_request = ::Google::Compute::Network::Get.new( + gcompute_address_self_link(name, region, project), cred + ) + response = JSON.parse(get_request.send.body) + response['address'] + helpers: | + def gcompute_address_self_link(name, region, project) + URI.join( + 'https://www.googleapis.com/compute/v1/', + "projects/#{project}/", + "regions/#{region}/", + "addresses/#{name}" + ) + end + examples: + - "{{function:name}}('my-server', 'us-central1', 'myproject', $fn_auth)" + notes: | + The credential parameter should be allocated with a + `gauth_credential_*_for_function` call. + # TODO(nelsonjr): Deprecate this function once ForwardingRule can take + # multiple resources for IPAddress so we can make it a ResourceRef. + - !ruby/object:Provider::Config::Function + name: 'gcompute_address_ref' + description: | + Builds a reference to the IP address associated with the Address managed + by a `gcompute_address` resource. + arguments: + - !ruby/object:Provider::Config::Function::Argument + name: name + type: Api::Type::String + description: 'the name of the address resource' + - !ruby/object:Provider::Config::Function::Argument + name: region + type: Api::Type::String + description: 'the region where the address resource is allocated' + - !ruby/object:Provider::Config::Function::Argument + name: project + type: Api::Type::String + description: 'the project name where resource is allocated' + requires: + - puppet + code: '"projects/#{project}/regions/#{region}/addresses/#{name}"' + examples: + - gcompute_address_ref('my-server', 'us-central1', 'myproject') + notes: | + This function is useful for when a reference to a resource that have + multiple facts, such as `gcompute_forwarding_rule { ip_address }` + # TODO(nelsonjr): Deprecate this function once GlobalForwardingRule can take + # multiple resources for IPAddress so we can make it a ResourceRef (or a new + # type that can mimic a ResourceRef to host an IP Address). + - !ruby/object:Provider::Config::Function + name: 'gcompute_global_address_ref' + description: | + Builds a reference to the global IP address associated with the Address + managed by a `gcompute_global_address` resource. + arguments: + - !ruby/object:Provider::Config::Function::Argument + name: name + type: Api::Type::String + description: 'the name of the address resource' + - !ruby/object:Provider::Config::Function::Argument + name: project + type: Api::Type::String + description: 'the project name where resource is allocated' + requires: + - puppet + code: '"projects/#{project}/global/addresses/#{name}"' + examples: + - gcompute_global_address_ref('my-server', 'myproject') + notes: | + This function is useful for when a reference to a resource that have + multiple facts, such as `gcompute_global_forwarding_rule { ip_address }` + - !ruby/object:Provider::Config::Function + name: 'gcompute_health_check_ref' + description: | + Builds a reference to a health check to be used in the backend service. + arguments: + - !ruby/object:Provider::Config::Function::Argument + name: name + type: Api::Type::String + description: 'the name of the health check' + - !ruby/object:Provider::Config::Function::Argument + name: project_name + type: Api::Type::String + description: 'the name of the project that hosts the check' + code: | + URI.join('https://www.googleapis.com/compute/v1/', + "projects/#{project_name}/global/healthChecks/#{name}").to_s + examples: + - "{{function:name}}('my-hc', 'my-project')" + - !ruby/object:Provider::Config::Function + name: 'gcompute_image_family' + description: | + Builds the family resource identifier required to uniquely identify the + family, e.g. to create virtual machines based on it. You can use this + function as `source_image` of a `gcompute_instance` resource. + arguments: + - !ruby/object:Provider::Config::Function::Argument + name: family_name + type: Api::Type::String + description: 'the name of the family, e.g. ubuntu-1604-lts' + - !ruby/object:Provider::Config::Function::Argument + name: project_name + type: Api::Type::String + description: | + the name of the project that hosts the family, + e.g. ubuntu-os-cloud + code: '"projects/#{project_name}/global/images/family/#{family_name}"' + examples: + - "{{function:name}}('ubuntu-1604-lts', 'ubuntu-os-cloud')" + - "{{function:name}}('my-web-server', 'my-project')" + notes: | + Note: In the case of private images, your credentials will need to have + the proper permissions to access the image. + + To get a list of supported families you can use the gcloud utility: + + gcloud compute images list + - !ruby/object:Provider::Config::Function + name: 'gcompute_target_http_proxy_ref' + description: | + Builds a reference to a target HTTP proxy to be used in the global + forwarding rule. + arguments: + - !ruby/object:Provider::Config::Function::Argument + name: name + type: Api::Type::String + description: 'the name of the proxy' + - !ruby/object:Provider::Config::Function::Argument + name: project_name + type: Api::Type::String + description: 'the name of the project that hosts the proxy' + code: | + URI.join('https://www.googleapis.com/compute/v1/', + "projects/#{project_name}/global/targetHttpProxies/#{name}").to_s + examples: + - "{{function:name}}('my-http-proxy', 'my-project')" +bolt_tasks: + - !ruby/object:Provider::Puppet::BoltTask + name: 'reset' + description: 'Resets a Google Compute Engine VM instance' + style: :ruby + input: :stdin + arguments: + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: name + type: Api::Type::String + description: 'The name of the instance to reset' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: zone + type: Api::Type::String + description: 'The zone where your instance resides' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: project + type: Api::Type::String + description: 'The project that hosts the VM instance' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: credential + type: Api::Type::String + description: 'Path to a service account credentials file' + required: true + requires: + - google/auth/gauth_credential + - google/compute/api/gcompute_instance + code: | + cred = Google::Auth::GAuthCredential \ + .serviceaccount_for_function(credential, COMPUTE_ADM_SCOPES) + instance = Google::Compute::Api::Instance.new(name, zone, project, cred) + + begin + instance.reset + puts({ status: 'success' }.to_json) + exit 0 + rescue Puppet::Error => e + puts({ status: 'failure', error: e }.to_json) + exit 1 + end + - !ruby/object:Provider::Puppet::BoltTask + name: 'instance' + description: | + Because sometimes you just want a quick way to get (or destroy) an + instance + style: :puppet + input: :stdin + manifest: products/compute/files/tasks~instance.pp + arguments: + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: name + type: Api::Type::String + description: 'Name of the machine to create (or delete)' + default: !ruby/object:Provider::Puppet::BoltTask::Argument::Default + code: | + bolt-\${inline_template('<%%= SecureRandom.hex(8) -%>')} + display: bolt- + comment: | + I usually can't advocate the use to inline_template but I am trying + hard not to require someone to install stdlib to run this task because + the gcompute modules currently does not require it. + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: image_family + type: Api::Type::String + description: | + An indication of which image family to launch the instance from + (format: :) + default: centos-7:centos-cloud + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: size_gb + type: Api::Type::Integer + description: The size of the VM disk (in GB) + default: 50 + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: machine_type + type: Api::Type::String + description: The type of the machine to create + default: n1-standard-1 + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: allocate_static_ip + type: Api::Type::Boolean + description: If true it will allocate a static IP for the machine + default: false + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: network_name + type: Api::Type::String + description: The network to connect the VM to + default: default + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: zone + type: Api::Type::String + description: The zone where your instance resides + default: us-west1-c + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: project + type: Api::Type::String + description: | + The project you have credentials for and will houses your instance + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: credential + type: Api::Type::String + description: 'Path to a service account credentials file' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument::Enum + name: ensure + description: | + If you'd wish to quickly delete an instance instead of creating one + default: present + values: + - :present + - :absent + code: | + declare -r vm_name=$(sed -e "s/.* as '\(.*\)'.*/\1/" <<< ${success_line}) + echo "{ \"status\": \"success\", \"name\": \"${vm_name}\" }" + - !ruby/object:Provider::Puppet::BoltTask + name: 'snapshot' + description: 'Create a snapshot of a Google Compute Engine Disk' + style: :ruby + input: :stdin + arguments: + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: name + type: Api::Type::String + description: The name of the disk to create snapshot + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: target + type: Api::Type::String + description: The name of the disk snapshot + default: !ruby/object:Provider::Puppet::BoltTask::Argument::Default + code: '"#{name}-#{Time.now.to_i}"' + display: '-' + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: zone + type: Api::Type::String + description: The zone where your disk resides + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: project + type: Api::Type::String + description: The project that hosts the disk + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: credential + type: Api::Type::String + description: 'Path to a service account credentials file' + required: true + requires: + - google/auth/gauth_credential + - google/compute/api/gcompute_disk + code: | + cred = Google::Auth::GAuthCredential \ + .serviceaccount_for_function(credential, COMPUTE_ADM_SCOPES) + disk = Google::Compute::Api::Disk.new(name, zone, project, cred) + + begin + disk.snapshot(target) + puts({ status: 'success' }.to_json) + exit 0 + rescue Puppet::Error => e + puts({ status: 'failure', error: e }.to_json) + exit 1 + end +examples: !ruby/object:Api::Resource::HashArray + Address: + - address.pp + - delete_address.pp + # Autoscaler + BackendBucket: + - backend_bucket.pp + - delete_backend_bucket.pp + BackendService: + - backend_service.pp + - delete_backend_service.pp + DiskType: + - disk_type.pp + Disk: + - delete_disk.pp + - disk.pp + Firewall: + - delete_firewall.pp + - firewall.pp + - firewall~change1.pp + ForwardingRule: + - delete_forwarding_rule.pp + - forwarding_rule.pp + GlobalAddress: + - delete_global_address.pp + - global_address.pp + GlobalForwardingRule: + - delete_global_forwarding_rule.pp + - global_forwarding_rule.pp + # GlobalOperation -- not useful for state convergence + HealthCheck: + - health_check.pp + - delete_health_check.pp + HttpHealthCheck: + - http_health_check.pp + - delete_http_health_check.pp + HttpsHealthCheck: + - https_health_check.pp + - delete_https_health_check.pp + # Image + Image: + - delete_image.pp + - image.pp + InstanceGroupManager: + - delete_instance_group_manager.pp + - instance_group_manager.pp + InstanceGroup: + - delete_instance_group.pp + - instance_group.pp + InstanceTemplate: + - delete_instance_template.pp + - instance_template.pp + Instance: + - delete_instance.pp + - instance.pp + # License -- useful? + License: + - license.pp + MachineType: + - machine_type.pp + Network: + - delete_network.pp + - network~auto.pp + - network~convert_to_custom.pp + - network~custom.pp + - network~legacy.pp + # Project -- useful for common instance metadata in v1 + # RegionAutoscaler + # RegionBackendService + # RegionInstanceGroupManager + # RegionInstanceGroup + Region: + - region.pp + # Router + Route: + - delete_route.pp + - route.pp + Snapshot: + - delete_snapshot.pp + - snapshot.pp + SslCertificate: + - delete_ssl_certificate.pp + - ssl_certificate.pp + Subnetwork: + - delete_subnetwork.pp + - subnetwork.pp + TargetHttpProxy: + - delete_target_http_proxy.pp + - target_http_proxy.pp + TargetHttpsProxy: + - delete_target_https_proxy.pp + - target_https_proxy.pp + # TargetInstance + TargetPool: + - delete_target_pool.pp + - target_pool.pp + TargetTcpProxy: + - delete_target_tcp_proxy.pp + - target_tcp_proxy.pp + TargetSslProxy: + - delete_target_ssl_proxy.pp + - target_ssl_proxy.pp + # TargetVpnGateway + UrlMap: + - delete_url_map.pp + - url_map.pp + # VpnTunnel + # ZoneOperation -- not useful for state convergence + Zone: + - zone.pp +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/puppet/common~copy.yaml'), 4) %> + # Client-side functions require 'google/authorization' + spec/stubs/google/authorization.rb: templates/spec_lib_stub.rb.erb + compile: + lib/google/compute/api/gcompute_disk.rb: + products/compute/helpers/api_gcompute_disk.rb + lib/google/compute/api/gcompute_instance.rb: + products/compute/helpers/api_gcompute_instance.rb + lib/google/object_store.rb: google/object_store.rb + lib/puppet/functions/gcompute_task_load_params.rb: + templates/puppet/bolt~task_load_params.rb.erb + lib/puppet/functions/gcompute_task_validate_param.rb: + templates/puppet/bolt~task_validate_param.rb.erb +<%= indent(include('provider/puppet/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/puppet/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData + network: !ruby/object:Api::Resource::HashArray + Instance: + - success1~name + - success1~title + - success2~name + - success2~title + - success3~name + - success3~title + InstanceTemplate: + - success1~name + - success1~title + - success2~name + - success2~title + - success3~name + - success3~title +tests: !ruby/object:Api::Resource::HashArray +<%= indent(include('products/compute/test.yaml'), 2) %> +style: + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/backendservice_backends.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/backendservice_cache_key_policy.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/image_deprecated.rb + pinpoints: + - function: == + exceptions: + - Metrics/CyclomaticComplexity + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/instance_disks.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/instance_network_interfaces.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/instancegroupmanager_current_actions.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/instancetemplate_disks.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/instancetemplate_network_interfaces.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/google/compute/property/instancetemplate_properties.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - function: to_s + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_backend_service/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/AbcSize + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_disk/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_disk_type/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_firewall/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_global_forwarding_rule/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/AbcSize + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_forwarding_rule/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_health_check/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_http_health_check/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_https_health_check/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_image/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/AbcSize + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_instance/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/AbcSize + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_instance_group/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_instance_group_manager/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_machine_type/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_network/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_region/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_target_pool/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_target_ssl_proxy/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcompute_url_map/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: spec/test_constants.rb + pinpoints: + - module: GoogleTests::Constants + exceptions: + - Metrics/ModuleLength + - !ruby/object:Provider::Config::StyleException + name: spec/gcompute_instance_template + pinpoints: + - test: InstanceTemplate > present > not_exist > success > title_eq_name > before + exceptions: + - Metrics/LineLength + - test: InstanceTemplate > present > not_exist > success > title_and_name > before + exceptions: + - Metrics/LineLength + - !ruby/object:Provider::Config::StyleException + name: spec/gcompute_instance + pinpoints: + - test: Instance > present > not_exist > success > title_eq_name > before + exceptions: + - Metrics/LineLength + - test: Instance > present > not_exist > success > title_and_name > before + exceptions: + - Metrics/LineLength +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.2.2' + date: 2017-10-25T09:00:00-0700 + features: + - Added support for `gcompute_snapshot` resources. + - !ruby/object:Provider::Config::Changelog + version: '0.2.1' + date: 2017-10-11T15:30:00-0700 + fixes: + - '#0001: Boolean[1] might be invalid' + - !ruby/object:Provider::Config::Changelog + version: '0.2.0' + date: 2017-10-10T06:00:00-0700 + features: + - | + Improvements to `gcompute_instance` resource: + * Added support for alias IP ranges in + `network_configs { alias_ip_ranges => [ ... ] }` + * Added support for `metadata` + * Added support for `interface` in `disks [ { interface } ]` + * Added support for `mode` in `disks [ { mode } ]` + * Added support for `source_image_encryption_key` in + `initializeParams { source_image_encryption_key }` + - Added support for `gcompute_forwarding_rule` resources. + - Added support for `gcompute_global_forwarding_rule` resources. + - Added support for `gcompute_instance_group_manager` resources. + - Added support for `gcompute_instance_template` resources. + - Added support for `gcompute_target_pool` resources. + - Added support for `gcompute_target_http_proxy` resources. + - Added support for `gcompute_target_https_proxy` resources. + - Added support for `gcompute_target_ssl_proxy` resources. + - Added support for `gcompute_target_tcp_proxy` resources. + - Added support for `gcompute_url_map` resources. + - Added Bolt task `instance` to (quickly) create new a VM instance. + - Added Bolt task `reset` to reset a VM instance. + - Added Bolt task `snapshot` to create a Disk snapshot. + - Added `gcompute_address_ip` client function. + - Added `gcompute_address_ref` client function. + - Added `gcompute_global_address_ref` client function. + - Added `gcompute_health_check_ref` client function. + - Added `gcompute_target_http_proxy_ref` client function. + fixes: + - gcompute_instance { network_interfaces { subnetwork } } now accepts a + reference to a gcompute_subnetwork resource instead of the GCP URL of a + subnetwork + - Improved validation of required parameter references + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-08-22T09:00:00-0700 + general: 'Initial release' diff --git a/products/compute/test.yaml b/products/compute/test.yaml new file mode 100644 index 000000000000..be2b7cd3453b --- /dev/null +++ b/products/compute/test.yaml @@ -0,0 +1,1067 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Instance: + present: + not_exist: + success: + title_and_name: + before: | + expect_network_get_failed 1, + name: 'test name#0 data', + zone: 'test name#0 data' + expect_network_create \\ + 1, + { + 'kind' => 'compute#instance', + 'canIpForward' => true, + 'disks' => [ + { + 'autoDelete' => true, + 'boot' => true, + 'deviceName' => 'test device_name#0 data', + 'diskEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'rsaEncryptedKey' => 'test rsa_encrypted_key#0 data', + 'sha256' => 'test sha256#0 data' + }, + 'index' => 1_443_881_260, + 'initializeParams' => { + 'diskName' => 'test disk_name#0 data', + 'diskSizeGb' => 450_092_159, + 'diskType' => 'selflink(resource(disk_type,0))', + 'sourceImage' => 'test source_image#0 data', + 'sourceImageEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'sha256' => 'test sha256#0 data' + } + }, + 'interface' => 'SCSI', + 'mode' => 'READ_WRITE', + 'source' => 'selflink(resource(disk,0))', + 'type' => 'SCRATCH' + }, + { + 'autoDelete' => false, + 'boot' => false, + 'deviceName' => 'test device_name#1 data', + 'diskEncryptionKey' => { + 'rawKey' => 'test raw_key#1 data', + 'rsaEncryptedKey' => 'test rsa_encrypted_key#1 data', + 'sha256' => 'test sha256#1 data' + }, + 'index' => 2_887_762_520, + 'initializeParams' => { + 'diskName' => 'test disk_name#1 data', + 'diskSizeGb' => 900_184_319, + 'diskType' => 'selflink(resource(disk_type,1))', + 'sourceImage' => 'test source_image#1 data', + 'sourceImageEncryptionKey' => { + 'rawKey' => 'test raw_key#1 data', + 'sha256' => 'test sha256#1 data' + } + }, + 'interface' => 'NVME', + 'mode' => 'READ_ONLY', + 'source' => 'selflink(resource(disk,1))', + 'type' => 'PERSISTENT' + } + ], + 'guestAccelerators' => [ + { + 'acceleratorCount' => 2_697_554_557, + 'acceleratorType' => 'test accelerator_type#0 data' + }, + { + 'acceleratorCount' => 5_395_109_114, + 'acceleratorType' => 'test accelerator_type#1 data' + }, + { + 'acceleratorCount' => 8_092_663_672, + 'acceleratorType' => 'test accelerator_type#2 data' + }, + { + 'acceleratorCount' => 10_790_218_229, + 'acceleratorType' => 'test accelerator_type#3 data' + } + ], + 'labelFingerprint' => 'test label_fingerprint#0 data', + 'metadata' => { + 'items' => [ + { + 'key' => 'test metadata#1 data', + 'value' => 'test metadata#1 data' + }, + { + 'key' => 'test metadata#2 data', + 'value' => 2_666_715_473 + }, + { + 'key' => 'test metadata#3 data', + 'value' => 'test metadata#3 data' + } + ] + }, + 'machineType' => 'selflink(resource(machine_type,0))', + 'minCpuPlatform' => 'test min_cpu_platform#0 data', + 'name' => 'test name#0 data', + 'networkInterfaces' => [ + { + 'accessConfigs' => [ + { + 'name' => 'test name#0 data', + 'natIP' => 'test address#0 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#0 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#0 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#1 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#1 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#0 data', + 'network' => 'selflink(resource(network,0))', + 'networkIP' => 'test network_ip#0 data', + 'subnetwork' => 'selflink(resource(subnetwork,0))' + }, + { + 'accessConfigs' => [ + { + 'name' => 'test name#1 data', + 'natIP' => 'test address#1 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#2 data', + 'natIP' => 'test address#2 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#1 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#1 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#1 data', + 'network' => 'selflink(resource(network,1))', + 'networkIP' => 'test network_ip#1 data', + 'subnetwork' => 'selflink(resource(subnetwork,1))' + }, + { + 'accessConfigs' => [ + { + 'name' => 'test name#2 data', + 'natIP' => 'test address#2 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#3 data', + 'natIP' => 'test address#0 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#4 data', + 'natIP' => 'test address#1 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#2 data', + 'network' => 'selflink(resource(network,2))', + 'networkIP' => 'test network_ip#2 data', + 'subnetwork' => 'selflink(resource(subnetwork,2))' + } + ], + 'scheduling' => { + 'automaticRestart' => true, + 'onHostMaintenance' => 'test on_host_maintenance#0 data', + 'preemptible' => true + }, + 'serviceAccounts' => [ + { + 'email' => true, + 'scopes' => %w[rr ss tt uu vv] + }, + { + 'email' => false, + 'scopes' => %w[ll mm nn oo pp] + }, + { + 'email' => true, + 'scopes' => %w[ee ff gg hh] + } + ], + 'tags' => { + 'fingerprint' => 'test fingerprint#0 data', + 'items' => %w[hh ii jj] + } + }, + name: 'title0', + zone: 'test name#0 data' + expect_network_get_async 1, name: 'title0', zone: 'test name#0 data' + expect_network_get_success_zone 1 + expect_network_get_success_zone 2 + expect_network_get_success_disk_type 1, zone: 'test name#0 data' + expect_network_get_success_disk_type 2, zone: 'test name#1 data' + expect_network_get_success_disk 1, zone: 'test name#0 data' + expect_network_get_success_disk 2, zone: 'test name#1 data' + expect_network_get_success_machine_type 1, zone: 'test name#0 data' + expect_network_get_success_region 1 + expect_network_get_success_region 2 + expect_network_get_success_region 3 + expect_network_get_success_address 1, region: 'test name#0 data' + expect_network_get_success_address 2, region: 'test name#1 data' + expect_network_get_success_address 3, region: 'test name#2 data' + expect_network_get_success_network 1 + expect_network_get_success_network 2 + expect_network_get_success_network 3 + expect_network_get_success_subnetwork 1, region: 'test name#0 data' + expect_network_get_success_subnetwork 2, region: 'test name#1 data' + expect_network_get_success_subnetwork 3, region: 'test name#2 data' + title_eq_name: + before: | + expect_network_get_failed 1, + name: 'title0', + zone: 'test name#0 data' + expect_network_create \\ + 1, + { + 'kind' => 'compute#instance', + 'canIpForward' => true, + 'disks' => [ + { + 'autoDelete' => true, + 'boot' => true, + 'deviceName' => 'test device_name#0 data', + 'diskEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'rsaEncryptedKey' => 'test rsa_encrypted_key#0 data', + 'sha256' => 'test sha256#0 data' + }, + 'index' => 1_443_881_260, + 'initializeParams' => { + 'diskName' => 'test disk_name#0 data', + 'diskSizeGb' => 450_092_159, + 'diskType' => 'selflink(resource(disk_type,0))', + 'sourceImage' => 'test source_image#0 data', + 'sourceImageEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'sha256' => 'test sha256#0 data' + } + }, + 'interface' => 'SCSI', + 'mode' => 'READ_WRITE', + 'source' => 'selflink(resource(disk,0))', + 'type' => 'SCRATCH' + }, + { + 'autoDelete' => false, + 'boot' => false, + 'deviceName' => 'test device_name#1 data', + 'diskEncryptionKey' => { + 'rawKey' => 'test raw_key#1 data', + 'rsaEncryptedKey' => 'test rsa_encrypted_key#1 data', + 'sha256' => 'test sha256#1 data' + }, + 'index' => 2_887_762_520, + 'initializeParams' => { + 'diskName' => 'test disk_name#1 data', + 'diskSizeGb' => 900_184_319, + 'diskType' => 'selflink(resource(disk_type,1))', + 'sourceImage' => 'test source_image#1 data', + 'sourceImageEncryptionKey' => { + 'rawKey' => 'test raw_key#1 data', + 'sha256' => 'test sha256#1 data' + } + }, + 'interface' => 'NVME', + 'mode' => 'READ_ONLY', + 'source' => 'selflink(resource(disk,1))', + 'type' => 'PERSISTENT' + } + ], + 'guestAccelerators' => [ + { + 'acceleratorCount' => 2_697_554_557, + 'acceleratorType' => 'test accelerator_type#0 data' + }, + { + 'acceleratorCount' => 5_395_109_114, + 'acceleratorType' => 'test accelerator_type#1 data' + }, + { + 'acceleratorCount' => 8_092_663_672, + 'acceleratorType' => 'test accelerator_type#2 data' + }, + { + 'acceleratorCount' => 10_790_218_229, + 'acceleratorType' => 'test accelerator_type#3 data' + } + ], + 'labelFingerprint' => 'test label_fingerprint#0 data', + 'metadata' => { + 'items' => [ + { + 'key' => 'test metadata#1 data', + 'value' => 'test metadata#1 data' + }, + { + 'key' => 'test metadata#2 data', + 'value' => 2_666_715_473 + }, + { + 'key' => 'test metadata#3 data', + 'value' => 'test metadata#3 data' + } + ] + }, + 'machineType' => 'selflink(resource(machine_type,0))', + 'minCpuPlatform' => 'test min_cpu_platform#0 data', + 'name' => 'title0', + 'networkInterfaces' => [ + { + 'accessConfigs' => [ + { + 'name' => 'test name#0 data', + 'natIP' => 'test address#0 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#0 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#0 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#1 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#1 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#0 data', + 'network' => 'selflink(resource(network,0))', + 'networkIP' => 'test network_ip#0 data', + 'subnetwork' => 'selflink(resource(subnetwork,0))' + }, + { + 'accessConfigs' => [ + { + 'name' => 'test name#1 data', + 'natIP' => 'test address#1 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#2 data', + 'natIP' => 'test address#2 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#1 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#1 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#1 data', + 'network' => 'selflink(resource(network,1))', + 'networkIP' => 'test network_ip#1 data', + 'subnetwork' => 'selflink(resource(subnetwork,1))' + }, + { + 'accessConfigs' => [ + { + 'name' => 'test name#2 data', + 'natIP' => 'test address#2 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#3 data', + 'natIP' => 'test address#0 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#4 data', + 'natIP' => 'test address#1 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#2 data', + 'network' => 'selflink(resource(network,2))', + 'networkIP' => 'test network_ip#2 data', + 'subnetwork' => 'selflink(resource(subnetwork,2))' + } + ], + 'scheduling' => { + 'automaticRestart' => true, + 'onHostMaintenance' => 'test on_host_maintenance#0 data', + 'preemptible' => true + }, + 'serviceAccounts' => [ + { + 'email' => true, + 'scopes' => %w[rr ss tt uu vv] + }, + { + 'email' => false, + 'scopes' => %w[ll mm nn oo pp] + }, + { + 'email' => true, + 'scopes' => %w[ee ff gg hh] + } + ], + 'tags' => { + 'fingerprint' => 'test fingerprint#0 data', + 'items' => %w[hh ii jj] + } + }, + name: 'title0', + zone: 'test name#0 data' + expect_network_get_async 1, name: 'title0', zone: 'test name#0 data' + expect_network_get_success_zone 1 + expect_network_get_success_zone 2 + expect_network_get_success_disk_type 1, zone: 'test name#0 data' + expect_network_get_success_disk_type 2, zone: 'test name#1 data' + expect_network_get_success_disk 1, zone: 'test name#0 data' + expect_network_get_success_disk 2, zone: 'test name#1 data' + expect_network_get_success_machine_type 1, zone: 'test name#0 data' + expect_network_get_success_region 1 + expect_network_get_success_region 2 + expect_network_get_success_region 3 + expect_network_get_success_address 1, region: 'test name#0 data' + expect_network_get_success_address 2, region: 'test name#1 data' + expect_network_get_success_address 3, region: 'test name#2 data' + expect_network_get_success_network 1 + expect_network_get_success_network 2 + expect_network_get_success_network 3 + expect_network_get_success_subnetwork 1, region: 'test name#0 data' + expect_network_get_success_subnetwork 2, region: 'test name#1 data' + expect_network_get_success_subnetwork 3, region: 'test name#2 data' +InstanceTemplate: + present: + not_exist: + success: + title_and_name: + before: | + expect_network_get_failed 1, + name: 'test name#0 data' + expect_network_create \ + 1, + 'kind' => 'compute#instanceTemplate', + 'description' => 'test description#0 data', + 'name' => 'test name#0 data', + 'properties' => { + 'canIpForward' => true, + 'description' => 'test description#0 data', + 'disks' => [ + { + 'autoDelete' => true, + 'boot' => true, + 'deviceName' => 'test device_name#0 data', + 'diskEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'rsaEncryptedKey' => 'test rsa_encrypted_key#0 data', + 'sha256' => 'test sha256#0 data' + }, + 'index' => 1_443_881_260, + 'initializeParams' => { + 'diskName' => 'test disk_name#0 data', + 'diskSizeGb' => 450_092_159, + 'diskType' => 'selflink(resource(disk_type,0))', + 'sourceImage' => 'test source_image#0 data', + 'sourceImageEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'sha256' => 'test sha256#0 data' + } + }, + 'interface' => 'SCSI', + 'mode' => 'READ_WRITE', + 'source' => 'test name#0 data', + 'type' => 'SCRATCH' + }, + { + 'autoDelete' => false, + 'boot' => false, + 'deviceName' => 'test device_name#1 data', + 'diskEncryptionKey' => { + 'rawKey' => 'test raw_key#1 data', + 'rsaEncryptedKey' => 'test rsa_encrypted_key#1 data', + 'sha256' => 'test sha256#1 data' + }, + 'index' => 2_887_762_520, + 'initializeParams' => { + 'diskName' => 'test disk_name#1 data', + 'diskSizeGb' => 900_184_319, + 'diskType' => 'selflink(resource(disk_type,1))', + 'sourceImage' => 'test source_image#1 data', + 'sourceImageEncryptionKey' => { + 'rawKey' => 'test raw_key#1 data', + 'sha256' => 'test sha256#1 data' + } + }, + 'interface' => 'NVME', + 'mode' => 'READ_ONLY', + 'source' => 'test name#1 data', + 'type' => 'PERSISTENT' + } + ], + 'machineType' => 'test name#0 data', + 'metadata' => { + 'items' => [ + { + 'key' => 'test metadata#1 data', + 'value' => 'test metadata#1 data' + }, + { + 'key' => 'test metadata#2 data', + 'value' => 2_666_715_473 + }, + { + 'key' => 'test metadata#3 data', + 'value' => 'test metadata#3 data' + } + ] + }, + 'guestAccelerators' => [ + { + 'acceleratorCount' => 2_697_554_557, + 'acceleratorType' => 'test accelerator_type#0 data' + }, + { + 'acceleratorCount' => 5_395_109_114, + 'acceleratorType' => 'test accelerator_type#1 data' + }, + { + 'acceleratorCount' => 8_092_663_672, + 'acceleratorType' => 'test accelerator_type#2 data' + }, + { + 'acceleratorCount' => 10_790_218_229, + 'acceleratorType' => 'test accelerator_type#3 data' + } + ], + 'networkInterfaces' => [ + { + 'accessConfigs' => [ + { + 'name' => 'test name#0 data', + 'natIP' => 'test address#0 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#0 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#0 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#1 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#1 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#0 data', + 'network' => 'selflink(resource(network,0))', + 'networkIP' => 'test network_ip#0 data', + 'subnetwork' => 'selflink(resource(subnetwork,0))' + }, + { + 'accessConfigs' => [ + { + 'name' => 'test name#1 data', + 'natIP' => 'test address#1 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#2 data', + 'natIP' => 'test address#2 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#1 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#1 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#1 data', + 'network' => 'selflink(resource(network,1))', + 'networkIP' => 'test network_ip#1 data', + 'subnetwork' => 'selflink(resource(subnetwork,1))' + }, + { + 'accessConfigs' => [ + { + 'name' => 'test name#2 data', + 'natIP' => 'test address#2 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#3 data', + 'natIP' => 'test address#0 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#4 data', + 'natIP' => 'test address#1 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#2 data', + 'network' => 'selflink(resource(network,2))', + 'networkIP' => 'test network_ip#2 data', + 'subnetwork' => 'selflink(resource(subnetwork,2))' + } + ], + 'scheduling' => { + 'automaticRestart' => true, + 'onHostMaintenance' => 'test on_host_maintenance#0 data', + 'preemptible' => true + }, + 'serviceAccounts' => [ + { + 'email' => true, + 'scopes' => %w[rr ss tt uu vv] + }, + { + 'email' => false, + 'scopes' => %w[ll mm nn oo pp] + }, + { + 'email' => true, + 'scopes' => %w[ee ff gg hh] + } + ], + 'tags' => { + 'fingerprint' => 'test fingerprint#0 data', + 'items' => %w[hh ii jj] + } + } + expect_network_get_async 1 + expect_network_get_success_zone 1 + expect_network_get_success_zone 2 + expect_network_get_success_disk_type 1, zone: 'test name#0 data' + expect_network_get_success_disk_type 2, zone: 'test name#1 data' + expect_network_get_success_disk 1, zone: 'test name#0 data' + expect_network_get_success_disk 2, zone: 'test name#1 data' + expect_network_get_success_machine_type 1, zone: 'test name#0 data' + expect_network_get_success_region 1 + expect_network_get_success_region 2 + expect_network_get_success_region 3 + expect_network_get_success_address 1, region: 'test name#0 data' + expect_network_get_success_address 2, region: 'test name#1 data' + expect_network_get_success_address 3, region: 'test name#2 data' + expect_network_get_success_network 1 + expect_network_get_success_network 2 + expect_network_get_success_network 3 + expect_network_get_success_subnetwork 1, region: 'test name#0 data' + expect_network_get_success_subnetwork 2, region: 'test name#1 data' + expect_network_get_success_subnetwork 3, region: 'test name#2 data' + title_eq_name: + before: | + expect_network_get_failed 1, + name: 'title0' + expect_network_create \ + 1, + 'kind' => 'compute#instanceTemplate', + 'description' => 'test description#0 data', + 'name' => 'title0', + 'properties' => { + 'canIpForward' => true, + 'description' => 'test description#0 data', + 'disks' => [ + { + 'autoDelete' => true, + 'boot' => true, + 'deviceName' => 'test device_name#0 data', + 'diskEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'rsaEncryptedKey' => 'test rsa_encrypted_key#0 data', + 'sha256' => 'test sha256#0 data' + }, + 'index' => 1_443_881_260, + 'initializeParams' => { + 'diskName' => 'test disk_name#0 data', + 'diskSizeGb' => 450_092_159, + 'diskType' => 'selflink(resource(disk_type,0))', + 'sourceImage' => 'test source_image#0 data', + 'sourceImageEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'sha256' => 'test sha256#0 data' + } + }, + 'interface' => 'SCSI', + 'mode' => 'READ_WRITE', + 'source' => 'test name#0 data', + 'type' => 'SCRATCH' + }, + { + 'autoDelete' => false, + 'boot' => false, + 'deviceName' => 'test device_name#1 data', + 'diskEncryptionKey' => { + 'rawKey' => 'test raw_key#1 data', + 'rsaEncryptedKey' => 'test rsa_encrypted_key#1 data', + 'sha256' => 'test sha256#1 data' + }, + 'index' => 2_887_762_520, + 'initializeParams' => { + 'diskName' => 'test disk_name#1 data', + 'diskSizeGb' => 900_184_319, + 'diskType' => 'selflink(resource(disk_type,1))', + 'sourceImage' => 'test source_image#1 data', + 'sourceImageEncryptionKey' => { + 'rawKey' => 'test raw_key#1 data', + 'sha256' => 'test sha256#1 data' + } + }, + 'interface' => 'NVME', + 'mode' => 'READ_ONLY', + 'source' => 'test name#1 data', + 'type' => 'PERSISTENT' + } + ], + 'machineType' => 'test name#0 data', + 'metadata' => { + 'items' => [ + { + 'key' => 'test metadata#1 data', + 'value' => 'test metadata#1 data' + }, + { + 'key' => 'test metadata#2 data', + 'value' => 2_666_715_473 + }, + { + 'key' => 'test metadata#3 data', + 'value' => 'test metadata#3 data' + } + ] + }, + 'guestAccelerators' => [ + { + 'acceleratorCount' => 2_697_554_557, + 'acceleratorType' => 'test accelerator_type#0 data' + }, + { + 'acceleratorCount' => 5_395_109_114, + 'acceleratorType' => 'test accelerator_type#1 data' + }, + { + 'acceleratorCount' => 8_092_663_672, + 'acceleratorType' => 'test accelerator_type#2 data' + }, + { + 'acceleratorCount' => 10_790_218_229, + 'acceleratorType' => 'test accelerator_type#3 data' + } + ], + 'networkInterfaces' => [ + { + 'accessConfigs' => [ + { + 'name' => 'test name#0 data', + 'natIP' => 'test address#0 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#0 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#0 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#1 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#1 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#0 data', + 'network' => 'selflink(resource(network,0))', + 'networkIP' => 'test network_ip#0 data', + 'subnetwork' => 'selflink(resource(subnetwork,0))' + }, + { + 'accessConfigs' => [ + { + 'name' => 'test name#1 data', + 'natIP' => 'test address#1 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#2 data', + 'natIP' => 'test address#2 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#1 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#1 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#1 data', + 'network' => 'selflink(resource(network,1))', + 'networkIP' => 'test network_ip#1 data', + 'subnetwork' => 'selflink(resource(subnetwork,1))' + }, + { + 'accessConfigs' => [ + { + 'name' => 'test name#2 data', + 'natIP' => 'test address#2 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#3 data', + 'natIP' => 'test address#0 data', + 'type' => 'ONE_TO_ONE_NAT' + }, + { + 'name' => 'test name#4 data', + 'natIP' => 'test address#1 data', + 'type' => 'ONE_TO_ONE_NAT' + } + ], + 'aliasIpRanges' => [ + { + 'ipCidrRange' => 'test ip_cidr_range#2 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#2 data' + }, + { + 'ipCidrRange' => 'test ip_cidr_range#3 data', + 'subnetworkRangeName' => 'test subnetwork_range_name#3 data' + } + ], + 'name' => 'test name#2 data', + 'network' => 'selflink(resource(network,2))', + 'networkIP' => 'test network_ip#2 data', + 'subnetwork' => 'selflink(resource(subnetwork,2))' + } + ], + 'scheduling' => { + 'automaticRestart' => true, + 'onHostMaintenance' => 'test on_host_maintenance#0 data', + 'preemptible' => true + }, + 'serviceAccounts' => [ + { + 'email' => true, + 'scopes' => %w[rr ss tt uu vv] + }, + { + 'email' => false, + 'scopes' => %w[ll mm nn oo pp] + }, + { + 'email' => true, + 'scopes' => %w[ee ff gg hh] + } + ], + 'tags' => { + 'fingerprint' => 'test fingerprint#0 data', + 'items' => %w[hh ii jj] + } + } + expect_network_get_async 1 + expect_network_get_success_zone 1 + expect_network_get_success_zone 2 + expect_network_get_success_disk_type 1, zone: 'test name#0 data' + expect_network_get_success_disk_type 2, zone: 'test name#1 data' + expect_network_get_success_disk 1, zone: 'test name#0 data' + expect_network_get_success_disk 2, zone: 'test name#1 data' + expect_network_get_success_machine_type 1, zone: 'test name#0 data' + expect_network_get_success_region 1 + expect_network_get_success_region 2 + expect_network_get_success_region 3 + expect_network_get_success_address 1, region: 'test name#0 data' + expect_network_get_success_address 2, region: 'test name#1 data' + expect_network_get_success_address 3, region: 'test name#2 data' + expect_network_get_success_network 1 + expect_network_get_success_network 2 + expect_network_get_success_network 3 + expect_network_get_success_subnetwork 1, region: 'test name#0 data' + expect_network_get_success_subnetwork 2, region: 'test name#1 data' + expect_network_get_success_subnetwork 3, region: 'test name#2 data' +Snapshot: + present: + not_exist: + success: + title_eq_name: + before: | + expect_network_get_failed 1, name: 'title0' + expect_network_create \ + 1, + { + 'kind' => 'compute#snapshot', + 'name' => 'title0', + 'description' => 'test description#0 data', + 'licenses' => [ + 'selflink(resource(license,0))', + 'selflink(resource(license,1))', + 'selflink(resource(license,2))' + ], + 'labels' => %w[kk ll], + 'source' => 'test name#0 data', + 'zone' => 'test name#0 data', + 'snapshotEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'sha256' => 'test sha256#0 data' + }, + 'sourceDiskEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'sha256' => 'test sha256#0 data' + } + }, + name: 'test name#0 data' + # TODO(alexstephen): Make this async + #expect_network_get_async 1, name: 'title0' + expect_network_get_success_license 1 + expect_network_get_success_license 2 + expect_network_get_success_license 3 + expect_network_get_success_zone 1 + expect_network_get_success_disk 1, zone: 'test name#0 data' + title_and_name: + before: | + expect_network_get_failed 1, name: 'test name#0 data' + expect_network_create \ + 1, + { + 'kind' => 'compute#snapshot', + 'name' => 'test name#0 data', + 'description' => 'test description#0 data', + 'licenses' => [ + 'selflink(resource(license,0))', + 'selflink(resource(license,1))', + 'selflink(resource(license,2))' + ], + 'labels' => %w[kk ll], + 'source' => 'test name#0 data', + 'zone' => 'test name#0 data', + 'snapshotEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'sha256' => 'test sha256#0 data' + }, + 'sourceDiskEncryptionKey' => { + 'rawKey' => 'test raw_key#0 data', + 'sha256' => 'test sha256#0 data' + } + }, + name: 'test name#0 data', disk: 'test name#0 data' + # TODO(alexstephen): Make this async. + #expect_network_get_async 1 + expect_network_get_success_license 1 + expect_network_get_success_license 2 + expect_network_get_success_license 3 + expect_network_get_success_zone 1 + expect_network_get_success_disk 1, zone: 'test name#0 data' + expectations: + custom: + create: true + expectation_helpers: 'products/compute/helpers/expect_snapshot.rb.erb' diff --git a/products/container/api.yaml b/products/container/api.yaml new file mode 100644 index 000000000000..45d009bfff64 --- /dev/null +++ b/products/container/api.yaml @@ -0,0 +1,405 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: Google Container Engine +prefix: gcontainer +base_url: https://container.googleapis.com/v1/ +scopes: + - https://www.googleapis.com/auth/cloud-platform +objects: + - !ruby/object:Api::Resource + name: 'Cluster' +<%= indent(compile('products/container/async.yaml'), 4) %> + base_url: 'projects/{{project}}/zones/{{zone}}/clusters' + exports: + - !ruby/object:Api::Type::FetchedExternal + name: endpoint + - !ruby/object:Api::Type::FetchedExternal + name: masterAuth + - name + description: 'A Google Container Engine cluster.' + transport: !ruby/object:Api::Resource::Transport + encoder: encode_request + parameters: + - !ruby/object:Api::Type::String + name: 'zone' + description: 'The zone where the cluster is deployed' + required: true + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name of this cluster. The name must be unique within this project + and zone, and can be up to 40 characters with the following + restrictions: + + * Lowercase letters, numbers, and hyphens only. + * Must start with a letter. + * Must end with a number or a letter. + - !ruby/object:Api::Type::String + name: 'description' + description: 'An optional description of this cluster.' + - !ruby/object:Api::Type::Integer + name: 'initialNodeCount' + description: | + The number of nodes to create in this cluster. You must ensure that + your Compute Engine resource quota is sufficient for this number of + instances. You must also have available firewall and routes quota. For + requests, this field should only be used in lieu of a "nodePool" + object, since this configuration (along with the "nodeConfig") will be + used to create a "NodePool" object with an auto-generated name. Do not + use this and a nodePool at the same time. + required: true + input: true + - !ruby/object:Api::Type::NestedObject + name: 'nodeConfig' + description: | + Parameters used in creating the cluster's nodes. + + For requests, this field should only be used in lieu of a "nodePool" + object, since this configuration (along with the "initialNodeCount") + will be used to create a "NodePool" object with an auto-generated + name. Do not use this and a nodePool at the same time. For responses, + this field will be populated with the node configuration of the first + node pool. If unspecified, the defaults are used. + input: true + properties: +<%= indent(compile('products/container/node_config.yaml'), 10) %> + - !ruby/object:Api::Type::NestedObject + name: 'masterAuth' + description: | + The authentication information for accessing the master endpoint. + properties: + # TODO(nelsonjr): Make this a ResourceRef pointing to the Compute + # resources (external to this product) + # | - !ruby/object:Api::Type::ResourceRef + # | name: 'machineType' + # | product: 'Compute' + # | resource: 'MachineType' + # | imports: 'name' + - !ruby/object:Api::Type::String + name: 'username' + description: | + The username to use for HTTP basic authentication to the master + endpoint. + - !ruby/object:Api::Type::String + name: 'password' + description: | + The password to use for HTTP basic authentication to the master + endpoint. Because the master endpoint is open to the Internet, you + should create a strong password. + - !ruby/object:Api::Type::String + name: 'clusterCaCertificate' + description: | + Base64-encoded public certificate that is the root of trust for + the cluster. + output: true + - !ruby/object:Api::Type::String + name: 'clientCertificate' + description: | + Base64-encoded public certificate used by clients to authenticate + to the cluster endpoint. + output: true + - !ruby/object:Api::Type::String + name: 'clientKey' + description: | + Base64-encoded private key used by clients to authenticate to the + cluster endpoint. + output: true + - !ruby/object:Api::Type::Enum + name: 'loggingService' + description: | + The logging service the cluster should use to write logs. Currently + available options: + + * logging.googleapis.com - the Google Cloud Logging service. + * none - no logs will be exported from the cluster. + + if left as an empty string,logging.googleapis.com will be used. + values: + - 'logging.googleapis.com' + - 'none' + - !ruby/object:Api::Type::Enum + name: 'monitoringService' + description: | + The monitoring service the cluster should use to write metrics. + Currently available options: + + * monitoring.googleapis.com - the Google Cloud Monitoring service. + * none - no metrics will be exported from the cluster. + + if left as an empty string, monitoring.googleapis.com will be used. + values: + - 'monitoring.googleapis.com' + - 'none' + # TODO(nelsonjr): This is a ResourceRef but on a resource on another + # product (Compute). Figure out how to represent/implement this. For now + # making it a string. + - !ruby/object:Api::Type::String + name: 'network' + description: | + The name of the Google Compute Engine network to which the cluster is + connected. If left unspecified, the default network will be used. + + * Tip: To ensure it exists and it is operations, configure the network + using 'gcompute_network' resource. + - !ruby/object:Api::Type::String + name: 'clusterIpv4Cidr' + description: | + The IP address range of the container pods in this cluster, in CIDR + notation (e.g. 10.96.0.0/14). Leave blank to have one automatically + chosen or specify a /14 block in 10.0.0.0/8. + - !ruby/object:Api::Type::NestedObject + name: 'addonsConfig' + description: | + Configurations for the various addons available to run in the cluster. + properties: + - !ruby/object:Api::Type::NestedObject + name: 'httpLoadBalancing' + description: | + Configuration for the HTTP (L7) load balancing controller addon, + which makes it easy to set up HTTP load balancers for services in + a cluster. + properties: + - !ruby/object:Api::Type::Boolean + name: 'disabled' + description: | + Whether the HTTP Load Balancing controller is enabled in the + cluster. When enabled, it runs a small pod in the cluster that + manages the load balancers. + - !ruby/object:Api::Type::NestedObject + name: 'horizontalPodAutoscaling' + description: | + Configuration for the horizontal pod autoscaling feature, which + increases or decreases the number of replica pods a replication + controller has based on the resource usage of the existing pods. + properties: + - !ruby/object:Api::Type::Boolean + name: 'disabled' + description: | + Whether the Horizontal Pod Autoscaling feature is enabled in + the cluster. When enabled, it ensures that a Heapster pod is + running in the cluster, which is also used by the Cloud + Monitoring service. + # TODO(nelsonjr): This is a ResourceRef but on a resource on another + # product (Compute). Figure out how to represent/implement this. For now + # making it a string. + - !ruby/object:Api::Type::String + name: 'subnetwork' + description: | + The name of the Google Compute Engine subnetwork to which the cluster + is connected. + # TODO(nelsonjr): 'nodePools' will not be supported inline here (until we + # have a way to add a compile()/include() to the api.yaml. For the time + # being NodePool will be described outside in their own resource, reaching + # the same behavior and also compliant with the current API. + - !ruby/object:Api::Type::Array + name: 'location' + description: | + The list of Google Compute Engine locations in which the cluster's + nodes should be located. + item_type: Api::Type::String + # 'enableKubernetesAlpha' not supported: we are only producing GA API. + - !ruby/object:Api::Type::String + name: 'endpoint' + description: | + The IP address of this cluster's master endpoint. + + The endpoint can be accessed from the internet at + https://username:password@endpoint/ + + See the masterAuth property of this resource for username and password + information. + output: true + - !ruby/object:Api::Type::String + name: 'initialClusterVersion' + description: | + The software version of the master endpoint and kubelets used in the + cluster when it was first created. The version can be upgraded over + time. + output: true + - !ruby/object:Api::Type::String + name: 'currentMasterVersion' + description: 'The current software version of the master endpoint.' + output: true + - !ruby/object:Api::Type::String + name: 'currentNodeVersion' + description: | + The current version of the node software components. If they are + currently at multiple versions because they're in the process of being + upgraded, this reflects the minimum version of all nodes. + output: true + - !ruby/object:Api::Type::Time + name: 'createTime' + description: | + The time the cluster was created, in RFC3339 text format. + output: true + # | 'status' is not applicable for state enforcement + # | 'statusMessage' is not applicable for state enforcement + - !ruby/object:Api::Type::Integer + name: 'nodeIpv4CidrSize' + description: | + The size of the address space on each node for hosting containers. + This is provisioned from within the container_ipv4_cidr range. + output: true + - !ruby/object:Api::Type::String + name: 'servicesIpv4Cidr' + description: | + The IP address range of the Kubernetes services in this cluster, in + CIDR notation (e.g. 1.2.3.4/29). Service addresses are typically put + in the last /16 from the container CIDR. + output: true + # | 'instanceGroupUrls' not supported as it is dynamically created by the + # | server and admin has no way to predict it + - !ruby/object:Api::Type::Integer + name: 'currentNodeCount' + description: 'The number of nodes currently in the cluster.' + output: true + - !ruby/object:Api::Type::Time + name: 'expireTime' + description: | + The time the cluster will be automatically deleted in RFC3339 text + format. + output: true + - !ruby/object:Api::Resource + name: 'NodePool' +<%= indent(compile('products/container/async.yaml'), 4) %> + base_url: | + projects/{{project}}/zones/{{zone}}/clusters/{{cluster}}/ + nodePools + description: | + NodePool contains the name and configuration for a cluster's node pool. + Node pools are a set of nodes (i.e. VM's), with a common configuration and + specification, under the control of the cluster master. They may have a + set of Kubernetes labels applied to them, which may be used to reference + them during pod scheduling. They may also be resized up or down, to + accommodate the workload. + transport: !ruby/object:Api::Resource::Transport + encoder: encode_request + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'cluster' + resource: 'Cluster' + imports: 'name' + description: 'The cluster this node pool belongs to.' + required: true + - !ruby/object:Api::Type::String + name: 'zone' + description: 'The zone where the node pool is deployed' + required: true + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: 'The name of the node pool.' + - !ruby/object:Api::Type::NestedObject + name: 'config' + description: 'The node configuration of the pool.' + properties: +<%= indent(compile('products/container/node_config.yaml'), 10) %> + - !ruby/object:Api::Type::Integer + name: 'initialNodeCount' + description: | + The initial node count for the pool. You must ensure that your Compute + Engine resource quota is sufficient for this number of instances. You + must also have available firewall and routes quota. + required: true + input: true + # | 'instanceGroupUrls' not supported as it is dynamically created by the + # | server and admin has no way to predict it + - !ruby/object:Api::Type::String + name: 'version' + description: 'The version of the Kubernetes of this node.' + output: true + # | 'status' is not applicable for state enforcement + # | 'statusMessage' is not applicable for state enforcement + - !ruby/object:Api::Type::NestedObject + name: 'autoscaling' + description: | + Autoscaler configuration for this NodePool. Autoscaler is enabled only + if a valid configuration is present. + properties: + - !ruby/object:Api::Type::Boolean + name: 'enabled' + description: 'Is autoscaling enabled for this node pool.' + - !ruby/object:Api::Type::Integer + name: 'minNodeCount' + description: | + Minimum number of nodes in the NodePool. Must be >= 1 and <= + maxNodeCount. + - !ruby/object:Api::Type::Integer + name: 'maxNodeCount' + description: | + Maximum number of nodes in the NodePool. Must be >= minNodeCount. + There has to enough quota to scale up the cluster. + - !ruby/object:Api::Type::NestedObject + name: 'management' + description: 'Management configuration for this NodePool.' + properties: + - !ruby/object:Api::Type::Boolean + name: 'autoUpgrade' + description: | + A flag that specifies whether node auto-upgrade is enabled for the + node pool. If enabled, node auto-upgrade helps keep the nodes in + your node pool up to date with the latest release version of + Kubernetes. + - !ruby/object:Api::Type::Boolean + name: 'autoRepair' + description: | + A flag that specifies whether the node auto-repair is enabled for + the node pool. If enabled, the nodes in this node pool will be + monitored and, if they fail health checks too many times, an + automatic repair action will be triggered. + - !ruby/object:Api::Type::NestedObject + name: 'upgradeOptions' + description: 'Specifies the Auto Upgrade knobs for the node pool.' + properties: + - !ruby/object:Api::Type::Time + name: 'autoUpgradeStartTime' + description: | + This field is set when upgrades are about to commence with the + approximate start time for the upgrades, in RFC3339 text + format. + output: true + - !ruby/object:Api::Type::String + name: 'description' + description: | + This field is set when upgrades are about to commence with the + description of the upgrade. + output: true + - !ruby/object:Api::Resource + name: 'KubeConfig' + base_url: 'unused' + description: | + Generates a compatible Kuberenetes '.kube/config' file + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: 'The config file kubectl settings will be written to.' + required: true + - !ruby/object:Api::Type::ResourceRef + name: 'cluster' + resource: 'Cluster' + imports: 'name' + description: 'A reference to Cluster resource' + required: true + # TODO(nelsonjr): Make 'zone' a ResourceRef once cross-module references + # are possible. + - !ruby/object:Api::Type::String + name: 'zone' + description: 'The zone where the container is deployed' + required: true + - !ruby/object:Api::Type::String + name: 'context' + description: 'The name of the context. Defaults to cluster name.' + required: true diff --git a/products/container/async.yaml b/products/container/async.yaml new file mode 100644 index 000000000000..14943e17964b --- /dev/null +++ b/products/container/async.yaml @@ -0,0 +1,32 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +async: !ruby/object:Api::Async + operation: !ruby/object:Api::Async::Operation + kind: 'container#operation' + path: 'name' + base_url: 'projects/{{project}}/zones/{{zone}}/operations/{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::Async::Result + path: 'targetLink' + status: !ruby/object:Api::Async::Status + path: 'status' + complete: 'DONE' + allowed: + - 'PENDING' + - 'RUNNING' + - 'DONE' + - 'ABORTING' + error: !ruby/object:Api::Async::Error + path: 'error/errors' + message: 'message' diff --git a/products/container/chef-e2e.yaml b/products/container/chef-e2e.yaml new file mode 100644 index 000000000000..db7d5650ec02 --- /dev/null +++ b/products/container/chef-e2e.yaml @@ -0,0 +1,111 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Chef::Tester + product: 'Container' + tests: + - name: 'Cluster' + phases: + # Auto network + - name: 'create' + apply: + - run: 'google-gcontainer::tests~cluster' + env: + cluster_id: cluster-{{run_id}} + outputs: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'create(again)' + apply: + - run: 'google-gcontainer::tests~cluster' + env: + cluster_id: cluster-{{run_id}} + outputs: + - - 'Chef Client finished, 0/2 resources updated' + - name: 'delete' + apply: + - run: 'google-gcontainer::tests~delete_cluster' + env: + cluster_id: cluster-{{run_id}} + outputs: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'delete' + apply: + - run: 'google-gcontainer::tests~delete_cluster' + env: + cluster_id: cluster-{{run_id}} + outputs: + - - 'Chef Client finished, 0/2 resources updated' + - name: 'NodePool' + phases: + - name: 'create' + apply: + - run: 'google-gcontainer::tests~cluster' + env: + cluster_id: nodepool-{{run_id}} + outputs: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'create(again)' + apply: + - run: 'google-gcontainer::tests~cluster' + env: + cluster_id: nodepool-{{run_id}} + outputs: + - - 'Chef Client finished, 0/2 resources updated' + - name: 'create' + apply: + - run: 'google-gcontainer::tests~node_pool' + env: + cluster_id: nodepool-{{run_id}} + outputs: + - - 'Chef Client finished, 1/3 resources updated' + - name: 'create(again)' + apply: + - run: 'google-gcontainer::tests~node_pool' + env: + cluster_id: nodepool-{{run_id}} + outputs: + - - 'Chef Client finished, 0/3 resources updated' + - name: 'delete' + apply: + - run: 'google-gcontainer::tests~delete_node_pool' + env: + cluster_id: nodepool-{{run_id}} + outputs: + - - 'Chef Client finished, 1/3 resources updated' + - name: 'delete(again)' + apply: + - run: 'google-gcontainer::tests~delete_node_pool' + env: + cluster_id: nodepool-{{run_id}} + outputs: + - - 'Chef Client finished, 0/3 resources updated' + # Cleanup Node Pools + - name: 'cleanup' + apply: + - run: 'google-gcontainer::tests~delete_cluster' + env: + cluster_id: nodepool-{{run_id}} + outputs: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'cleanup(again)' + apply: + - run: 'google-gcontainer::tests~delete_cluster' + env: + cluster_id: nodepool-{{run_id}} + outputs: + - - 'Chef Client finished, 0/2 resources updated' diff --git a/products/container/chef.yaml b/products/container/chef.yaml new file mode 100644 index 000000000000..3d6b3f197285 --- /dev/null +++ b/products/container/chef.yaml @@ -0,0 +1,104 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Chef::Config +manifest: !ruby/object:Provider::Chef::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/chef-google-container' + issues: 'https://github.com/GoogleCloudPlatform/chef-google-container/issues' + summary: 'A Chef cookbook to manage Google Container Engine resources' + description: | + This cookbook provides the built-in types and services for Chef to manage + Google Container Engine resources, as native Chef types. + depends: + - !ruby/object:Provider::Config::Requirements + name: 'google-gauth' + versions: '< 0.2.0' + operating_systems: +<%= indent(include('provider/chef/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + Cluster: + provider_helpers: + include: + - 'products/container/helpers/provider_cluster.rb' + NodePool: + provider_helpers: + include: + - 'products/container/helpers/provider_node_pool.rb' +examples: !ruby/object:Api::Resource::HashArray + Cluster: + - delete_cluster.rb + - cluster.rb + NodePool: + - delete_node_pool.rb + - node_pool.rb +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/chef/common~copy.yaml'), 4) %> + compile: +<%= indent(include('provider/chef/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/chef/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: "Autogenerated" +style: + - !ruby/object:Provider::Config::StyleException + name: resources/cluster.rb + pinpoints: + - class: Google::GCONTAINER::Cluster + exceptions: + - Metrics/ClassLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - Metrics/AbcSize + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/node_pool.rb + pinpoints: + - class: Google::GCONTAINER::NodePool + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: libraries/google/container/property/cluster_node_config.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: libraries/google/container/property/nodepool_config.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: spec/gcontainer_cluster + pinpoints: + - test: Cluster > present > not_exist > success > title_eq_name > before + exceptions: + - Metrics/LineLength + - !ruby/object:Provider::Config::StyleException + name: spec/node_pool_spec.rb + pinpoints: + - function: expect_network_create + exceptions: + - Metrics/AbcSize + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: metadata.rb + pinpoints: + - attribute: issues_url + exceptions: + - Metrics/LineLength diff --git a/products/container/files/examples~cluster.pp b/products/container/files/examples~cluster.pp new file mode 100644 index 000000000000..acb6bfe12255 --- /dev/null +++ b/products/container/files/examples~cluster.pp @@ -0,0 +1,41 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +if $cluster_id == undef { + fail('Please specify $cluster_id variable to run this example.') +} + +<% end # name == README.md -%> +gcontainer_cluster { <%= example_resource_name('mycluster-${cluster_id}') -%>: + ensure => present, + initial_node_count => 2, + master_auth => { + username => 'cluster_admin', + password => 'my-secret-password', + }, + node_config => { + machine_type => 'n1-standard-4', # we want a 4-core machine for our cluster + disk_size_gb => 500, # ... and a lot of disk space + }, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/container/files/examples~cookbook~cluster.rb b/products/container/files/examples~cookbook~cluster.rb new file mode 100644 index 000000000000..bdbad965cd87 --- /dev/null +++ b/products/container/files/examples~cookbook~cluster.rb @@ -0,0 +1,41 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise "Missing parameter 'cluster_id'. Please read docs at #{__FILE__}" \ + unless ENV.key?('cluster_id') + +<% end # name == README.md -%> +<% res_name = 'mycluster-#{ENV[\'cluster_id\']}' -%> +gcontainer_cluster <%= example_resource_name(res_name) -%> do + action :create + initial_node_count 2 + master_auth( + username: 'cluster_admin', + password: 'my-secret-password' + ) + node_config( + machine_type: 'n1-standard-4', # we want 4-cores for our cluster + disk_size_gb: 500 # ... and a lot of disk space + ) + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/container/files/examples~cookbook~delete_cluster.rb b/products/container/files/examples~cookbook~delete_cluster.rb new file mode 100644 index 000000000000..b4aaca207211 --- /dev/null +++ b/products/container/files/examples~cookbook~delete_cluster.rb @@ -0,0 +1,32 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise "Missing parameter 'cluster_id'. Please read docs at #{__FILE__}" \ + unless ENV.key?('cluster_id') + +<% end # name == README.md -%> +<% res_name = 'mycluster-#{ENV[\'cluster_id\']}' -%> +gcontainer_cluster <%= example_resource_name(res_name) -%> do + action :delete + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/container/files/examples~cookbook~delete_node_pool.rb b/products/container/files/examples~cookbook~delete_node_pool.rb new file mode 100644 index 000000000000..2beab7560c63 --- /dev/null +++ b/products/container/files/examples~cookbook~delete_node_pool.rb @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% cluster_name = 'mycluster-#{ENV[\'cluster_id\']}' -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise "Missing parameter 'cluster_id'. Please read docs at #{__FILE__}" \ + unless ENV.key?('cluster_id') + +gcontainer_cluster <%= example_resource_name(cluster_name) -%> do + action :create + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Tip: Have environment variable 'cluster-id' set. +# Tip: Insert a gcontainer cluster with name mycluster-${cluster_id} +<% end # name == README -%> +gcontainer_node_pool <%= example_resource_name('web-servers') -%> do + action :delete + cluster <%= example_resource_name(cluster_name) %> + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/container/files/examples~cookbook~node_pool.rb b/products/container/files/examples~cookbook~node_pool.rb new file mode 100644 index 000000000000..c5a62e55b988 --- /dev/null +++ b/products/container/files/examples~cookbook~node_pool.rb @@ -0,0 +1,44 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% cluster_name = 'mycluster-#{ENV[\'cluster_id\']}' -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise "Missing parameter 'cluster_id'. Please read docs at #{__FILE__}" \ + unless ENV.key?('cluster_id') + +gcontainer_cluster <%= example_resource_name(cluster_name) -%> do + action :create + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Tip: Have environment variable 'cluster-id' set. +# Tip: Insert a gcontainer cluster with name mycluster-${cluster_id} +<% end # name == README.md -%> +gcontainer_node_pool <%= example_resource_name('web-servers') -%> do + action :create + initial_node_count 4 + cluster <%= example_resource_name(cluster_name) %> + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/container/files/examples~cookbook~readme.rb b/products/container/files/examples~cookbook~readme.rb new file mode 100644 index 000000000000..3beaca74aa77 --- /dev/null +++ b/products/container/files/examples~cookbook~readme.rb @@ -0,0 +1,38 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% end -%> +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcontainer_cluster 'test-cluster' do + action :create + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcontainer_node_pool 'web-servers' do + action :create + initial_node_count 4 + cluster 'test-cluster' + zone 'us-central1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/container/files/examples~delete_cluster.pp b/products/container/files/examples~delete_cluster.pp new file mode 100644 index 000000000000..1562dfab097f --- /dev/null +++ b/products/container/files/examples~delete_cluster.pp @@ -0,0 +1,32 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +if $cluster_id == undef { + fail('Please specify $cluster_id variable to run this example.') +} + +<% end # name == README.md -%> +gcontainer_cluster { <%= example_resource_name('mycluster-${cluster_id}') -%>: + ensure => absent, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/container/files/examples~delete_node_pool.pp b/products/container/files/examples~delete_node_pool.pp new file mode 100644 index 000000000000..7f3da9df40e2 --- /dev/null +++ b/products/container/files/examples~delete_node_pool.pp @@ -0,0 +1,41 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% cluster_name = 'mycluster-${cluster_id}' -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +if $cluster_id == undef { + fail('Please specify $cluster_id variable to run this example.') +} + +gcontainer_cluster { <%= example_resource_name(cluster_name) -%>: + ensure => present, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gcontainer_node_pool { <%= example_resource_name('web-servers') -%>: + ensure => absent, + cluster => <%= example_resource_name(cluster_name) -%>, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/container/files/examples~kube_config.pp b/products/container/files/examples~kube_config.pp new file mode 100644 index 000000000000..025cd171d0d9 --- /dev/null +++ b/products/container/files/examples~kube_config.pp @@ -0,0 +1,69 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +if $cluster_id == undef { + fail('Please specify $cluster_id variable to run this example.') +} + +gcontainer_cluster { "mycluster-${cluster_id}": + ensure => present, + initial_node_count => 2, + master_auth => { + username => 'cluster_admin', + password => 'my-secret-password', + }, + node_config => { + machine_type => 'n1-standard-4', # we want a 4-core machine for our cluster + disk_size_gb => 500, # ... and a lot of disk space + }, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +file { '/home/nelsona/.kube': + ensure => directory, +} + +file { '/home/nelsona/.puppetlabs/etc/puppet': + ensure => directory, +} + +<% end # name == README.md -%> +# ~/.kube/config is used by Kubernetes client (kubectl) +gcontainer_kube_config { '/home/nelsona/.kube/config': + ensure => present, + context => "gke-mycluster-${cluster_id}", + cluster => "mycluster-${cluster_id}", + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +# A file named ~/.puppetlabs/etc/puppet/kubernetes is used by the +# garethr-kubernetes module. +gcontainer_kube_config { '/home/nelsona/.puppetlabs/etc/puppet/kubernetes.conf': + ensure => present, + cluster => "mycluster-${cluster_id}", + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/container/files/examples~node_pool.pp b/products/container/files/examples~node_pool.pp new file mode 100644 index 000000000000..3a40d2c0e3ca --- /dev/null +++ b/products/container/files/examples~node_pool.pp @@ -0,0 +1,45 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% cluster_name = 'mycluster-${cluster_id}' -%> +<% if name != "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +if $cluster_id == undef { + fail('Please specify $cluster_id variable to run this example.') +} + +gcontainer_cluster { <%= example_resource_name(cluster_name) -%>: + ensure => present, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else # name == README.md -%> +# A node pool requires a container to exist. Please ensure its presence with: +# gcontainer_cluster { ..... } +<% end # name == README.md -%> +gcontainer_node_pool { <%= example_resource_name('web-servers') -%>: + ensure => present, + initial_node_count => 4, + cluster => <%= example_resource_name(cluster_name) -%>, + zone => 'us-central1-a', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/container/files/provider~kube_config.rb b/products/container/files/provider~kube_config.rb new file mode 100644 index 000000000000..02c3452db0c5 --- /dev/null +++ b/products/container/files/provider~kube_config.rb @@ -0,0 +1,135 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/container/network/get' +require 'google/container/property/boolean' +require 'google/container/property/cluster_name' +require 'google/container/property/string' +require 'google/hash_utils' +require 'puppet' +require 'yaml' + +Puppet::Type.type(:gcontainer_kube_config).provide(:google) do + mk_resource_methods + + def self.instances + debug('instances') + raise [ + '"puppet resource" is not supported at the moment:', + 'TODO(nelsonjr): https://goto.google.com/graphite-bugs-view?id=167' + ].join(' ') + end + + def exists? + debug("exists? #{@property_hash[:ensure] == :present}") + @property_hash[:ensure] == :present + end + + def create + debug('create') + @created = true + IO.write(@resource[:name], kube_config.to_yaml) + @property_hash[:ensure] = :present + end + + def destroy + debug('destroy') + @deleted = true + File.delete(@resource[:name]) if File.exist?(@resource[:name]) + @property_hash[:ensure] = :absent + end + + def flush + debug('flush') + # return on !@dirty is for aiding testing (puppet already guarantees that) + return if @created || @deleted || !@dirty + end + + private + + def fetch_auth(resource) + self.class.fetch_auth(resource) + end + + def self.fetch_auth(resource) + Puppet::Type.type(:gauth_credential).fetch(resource) + end + + def debug(message) + puts("DEBUG: #{message}") if ENV['PUPPET_HTTP_VERBOSE'] + super(message) + end + + def cluster + @cluster_ref ||= begin + id = @resource[:cluster].resource + Google::ObjectStore.instance[:gcontainer_cluster].each do |entry| + return entry if entry.title == id + end + raise ArgumentError, "gcontainer_cluster[#{id}] not found" + end + end + + # rubocop:disable Metrics/MethodLength + def kube_config + endpoint = URI.parse("https://#{cluster.exports[:endpoint]}") + auth = fetch_auth(@resource).authorize(endpoint).last + context = @resource[:context] || cluster.name + + { + 'apiVersion' => 'v1', + 'clusters' => [ + { + 'name' => context, + 'cluster' => { + 'certificate-authority-data' => + cluster.exports[:master_auth]['clusterCaCertificate'], + 'server' => endpoint.to_s + } + } + ], + 'contexts' => [ + { + 'name' => context, + 'context' => { + 'cluster' => context, + 'user' => context + } + } + ], + 'current-context' => context, + 'kind' => 'Config', + 'preferences' => {}, + 'users' => [ + { + 'name' => context, + 'user' => { + 'auth-provider' => { + 'config' => { + 'access-token' => auth.token, + 'cmd-args' => 'config config-helper --format=json', + 'cmd-path' => '/usr/lib64/google-cloud-sdk/bin/gcloud', + 'expiry-key' => '{.credential.token_expiry}', + 'token-key' => '{.credential.access_token}' + }, + 'name' => 'gcp' + }, + 'username' => cluster.exports[:master_auth]['username'], + 'password' => cluster.exports[:master_auth]['password'] + } + } + ] + } + # rubocop:enable Metrics/MethodLength + end +end diff --git a/products/container/helpers/api_gcontainer_node_pool.rb b/products/container/helpers/api_gcontainer_node_pool.rb new file mode 100644 index 000000000000..d53f9a6192c6 --- /dev/null +++ b/products/container/helpers/api_gcontainer_node_pool.rb @@ -0,0 +1,59 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/container/network/post' + +module Google + module Container + module Api + # A helper class to provide access to (some) Google Container Engine API. + class NodePool + def initialize(name, cluster, zone, project, cred) + @name = name + @cluster = cluster + @zone = zone + @project = project + @cred = cred + end + + # TODO(nelsonjr): Implement this as gcontainer_node_pool { size } + # TODO(nelsonjr): Make this function wait for the operation to complete + # TODO(nelsonjr): Add error checking on response on this task + # (ditto on all Bolt tasks) + def resize(size) + resize_request = ::Google::Container::Network::Post.new( + gcontainer_node_pool_resize, @cred, 'application/json', + { 'nodeCount' => size }.to_json + ) + response = JSON.parse(resize_request.send.body) + raise Puppet::Error, response['error']['errors'][0]['message'] \ + if response['error'] + end + + private + + def gcontainer_node_pool_resize + URI.parse( + format( + '%s/%s', + Puppet::Type.type(:gcontainer_node_pool).provider(:google) + .self_link(name: @name, zone: @zone, + project: @project, cluster: @cluster), + 'setSize' + ) + ) + end + end + end + end +end diff --git a/products/container/helpers/provider_cluster.rb b/products/container/helpers/provider_cluster.rb new file mode 100644 index 000000000000..14d348518e3c --- /dev/null +++ b/products/container/helpers/provider_cluster.rb @@ -0,0 +1,32 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Google Container Engine API has its own layout for the create method, +# defined like this: +# +# { +# 'cluster': { +# ... cluster data +# } +# } +# +# Format the request to match the expected input by the API +def self.encode_request(resource_request) + { + 'cluster' => resource_request + } +end + +def encode_request(resource_request) + self.class.encode_request(resource_request) +end diff --git a/products/container/helpers/provider_node_pool.rb b/products/container/helpers/provider_node_pool.rb new file mode 100644 index 000000000000..c6570623bb18 --- /dev/null +++ b/products/container/helpers/provider_node_pool.rb @@ -0,0 +1,32 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Google Container Engine API has its own layout for the create method, +# defined like this: +# +# { +# 'nodePool': { +# ... node pool data +# } +# } +# +# Format the request to match the expected input by the API +def self.encode_request(resource_request) + { + 'nodePool' => resource_request + } +end + +def encode_request(resource_request) + self.class.encode_request(resource_request) +end diff --git a/products/container/node_config.yaml b/products/container/node_config.yaml new file mode 100644 index 000000000000..a9e9fd7a9265 --- /dev/null +++ b/products/container/node_config.yaml @@ -0,0 +1,126 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Make this a ResourceRef pointing to the Compute +# resources (external to this product) +# | - !ruby/object:Api::Type::ResourceRef +# | name: 'machineType' +# | product: 'Compute' +# | resource: 'MachineType' +# | imports: 'name' +- !ruby/object:Api::Type::String + name: 'machineType' + description: | + The name of a Google Compute Engine machine type (e.g. + n1-standard-1). If unspecified, the default machine type is + n1-standard-1. +- !ruby/object:Api::Type::Integer + name: 'diskSizeGb' + description: | + Size of the disk attached to each node, specified in GB. The + smallest allowed disk size is 10GB. If unspecified, the default + disk size is 100GB. +- !ruby/object:Api::Type::Array + name: 'oauthScopes' + item_type: Api::Type::String + description: | + The set of Google API scopes to be made available on all of the + node VMs under the "default" service account. + + The following scopes are recommended, but not required, and by + default are not included: + + * https://www.googleapis.com/auth/compute is required for mounting + persistent storage on your nodes. + * https://www.googleapis.com/auth/devstorage.read_only is required + for communicating with gcr.io (the Google Container Registry). + + If unspecified, no scopes are added, unless Cloud Logging or Cloud + Monitoring are enabled, in which case their required scopes will + be added. +- !ruby/object:Api::Type::String + name: 'serviceAccount' + description: | + The Google Cloud Platform Service Account to be used by the node + VMs. If no Service Account is specified, the "default" service + account is used. +- !ruby/object:Api::Type::NameValues + name: 'metadata' + key_type: Api::Type::String + value_type: Api::Type::String + description: | + The metadata key/value pairs assigned to instances in the cluster. + + Keys must conform to the regexp [a-zA-Z0-9-_]+ and be less than + 128 bytes in length. These are reflected as part of a URL in the + metadata server. Additionally, to avoid ambiguity, keys must not + conflict with any other metadata keys for the project or be one of + the four reserved keys: "instance-template", "kube-env", + "startup-script", and "user-data" + + Values are free-form strings, and only have meaning as interpreted + by the image running in the instance. The only restriction placed + on them is that each value's size must be less than or equal to 32 + KB. + + The total size of all keys and values must be less than 512 KB. + + An object containing a list of "key": value pairs. + Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }. +- !ruby/object:Api::Type::String + name: 'imageType' + description: | + The image type to use for this node. Note that for a given image + type, the latest version of it will be used. +- !ruby/object:Api::Type::NameValues + name: 'labels' + key_type: Api::Type::String + value_type: Api::Type::String + description: | + The map of Kubernetes labels (key/value pairs) to be applied to + each node. These will added in addition to any default label(s) + that Kubernetes may apply to the node. In case of conflict in + label keys, the applied set may differ depending on the Kubernetes + version -- it's best to assume the behavior is undefined and + conflicts should be avoided. For more information, including usage + and the valid values, see: + http://kubernetes.io/v1.1/docs/user-guide/labels.html + + An object containing a list of "key": value pairs. + Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }. +- !ruby/object:Api::Type::Integer + name: 'localSsdCount' + description: | + The number of local SSD disks to be attached to the node. + + The limit for this value is dependant upon the maximum number of + disks available on a machine per zone. See: + + https://cloud.google.com/compute/docs/disks/ + local-ssd#local_ssd_limits + + for more information. +- !ruby/object:Api::Type::Array + name: 'tags' + item_type: Api::Type::String + description: | + The list of instance tags applied to all nodes. Tags are used to + identify valid sources or targets for network firewalls and are + specified by the client during cluster or node pool creation. Each + tag within the list must comply with RFC1035. +- !ruby/object:Api::Type::Boolean + name: 'preemptible' + description: | + Whether the nodes are created as preemptible VM instances. See: + https://cloud.google.com/compute/docs/instances/preemptible for + more inforamtion about preemptible VM instances. diff --git a/products/container/puppet-e2e.yaml b/products/container/puppet-e2e.yaml new file mode 100644 index 000000000000..43edf9a292e8 --- /dev/null +++ b/products/container/puppet-e2e.yaml @@ -0,0 +1,97 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Puppet::Tester + product: 'Container' + tests: + - name: 'Cluster' + phases: + - name: 'create' + apply: + - run: 'cluster.pp' + env: + cluster_id: '{{run_id}}' + exits: 2 + - name: 'create(again)' + apply: + - run: 'cluster.pp' + env: + cluster_id: '{{run_id}}' + exits: 0 + - name: 'delete' + apply: + - run: 'delete_cluster.pp' + env: + cluster_id: '{{run_id}}' + exits: 2 + - name: 'delete' + apply: + - run: 'delete_cluster.pp' + env: + cluster_id: '{{run_id}}' + exits: 0 + - name: 'NodePool' + phases: + - name: 'create' + apply: + - run: 'cluster.pp' + env: + cluster_id: '{{run_id}}' + exits: 2 + - name: 'create(again)' + apply: + - run: 'cluster.pp' + env: + cluster_id: '{{run_id}}' + - name: 'create' + apply: + - run: 'node_pool.pp' + env: + cluster_id: '{{run_id}}' + exits: 2 + - name: 'create(again)' + apply: + - run: 'node_pool.pp' + env: + cluster_id: '{{run_id}}' + exits: 0 + - name: 'delete' + apply: + - run: 'delete_node_pool.pp' + env: + cluster_id: '{{run_id}}' + exits: 2 + - name: 'delete(again)' + apply: + - run: 'delete_node_pool.pp' + env: + cluster_id: '{{run_id}}' + exits: 0 + # Cleanup Node Pools + - name: 'cleanup' + apply: + - run: 'delete_cluster.pp' + env: + cluster_id: '{{run_id}}' + exits: 2 + - name: 'cleanup(again)' + apply: + - run: 'delete_cluster.pp' + env: + cluster_id: '{{run_id}}' + exits: 0 diff --git a/products/container/puppet.yaml b/products/container/puppet.yaml new file mode 100644 index 000000000000..f55208367f9a --- /dev/null +++ b/products/container/puppet.yaml @@ -0,0 +1,172 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.2.0' + source: 'https://github.com/GoogleCloudPlatform/puppet-google-container' + homepage: 'https://github.com/GoogleCloudPlatform/puppet-google-container' + issues: + 'https://github.com/GoogleCloudPlatform/puppet-google-container/issues' + summary: 'A Puppet module to manage Google Container Engine resources' + tags: + - google + - cloud + - container + - engine + - gke + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google/gauth' + versions: '>= 0.2.0 < 0.3.0' + operating_systems: +<%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + Cluster: + provider_helpers: + include: + - 'products/container/helpers/provider_cluster.rb' + NodePool: + provider_helpers: + include: + - 'products/container/helpers/provider_node_pool.rb' + KubeConfig: + manual: true +bolt_tasks: + - !ruby/object:Provider::Puppet::BoltTask + name: 'resize' + description: 'Resizes a cluster container node pool' + style: :ruby + input: :stdin + arguments: + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: name + type: Api::Type::String + description: 'The name of the node pool to resize' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: cluster + type: Api::Type::String + description: 'The name of the cluster that hosts the node pool' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: size + type: Api::Type::Integer + description: 'The new size of the container (in nodes)' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: zone + type: Api::Type::String + description: 'The zone that hosts the container' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: project + type: Api::Type::String + description: 'the project name where the cluster is hosted' + required : true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: credential + type: Api::Type::String + description: 'Path to a service account credentials file' + required: true + requires: + - google/auth/gauth_credential + - google/container/api/gcontainer_node_pool + code: | + cred = Google::Auth::GAuthCredential \ + .serviceaccount_for_function(credential, CONTAINER_ADM_SCOPES) + pool_instance = Google::Container::Api::NodePool \ + .new(name, cluster, zone, project, cred) + + begin + pool_instance.resize(size) + puts({ status: 'success' }.to_json) + exit 0 + rescue Puppet::Error => e + puts({ status: 'failure', error: e }.to_json) + exit 1 + end +tests: !ruby/object:Api::Resource::HashArray::NONE + # TODO(nelsonjr): Create all the necessary code here. + reason: 'Prototype.' +properties: + - array + - namevalues + - enum + - integer + - string + - time +examples: !ruby/object:Api::Resource::HashArray + Cluster: + - delete_cluster.pp + - cluster.pp + NodePool: + - delete_node_pool.pp + - node_pool.pp + KubeConfig: + - kube_config.pp +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/puppet/common~copy.yaml'), 4) %> + compile: + lib/google/container/api/gcontainer_node_pool.rb: + products/container/helpers/api_gcontainer_node_pool.rb + lib/google/object_store.rb: google/object_store.rb +<%= indent(include('provider/puppet/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/puppet/common~compile~after.yaml'), 4) %> +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'Autogenerated' +style: + - !ruby/object:Provider::Config::StyleException + name: lib/google/container/property/cluster_node_config.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/google/container/property/nodepool_config.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gcontainer_cluster/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/AbcSize + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: spec/gcontainer_cluster + pinpoints: + - test: Cluster > present > not_exist > success > title_eq_name > before + exceptions: + - Metrics/LineLength +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.2.0' + date: 2017-10-10T06:00:00-0700 + features: + - Added support for `gcompute_kube_config` resources. + fixes: + - Improved validation of required parameter references + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-08-22T09:00:00-0700 + general: 'Initial release' diff --git a/products/dns/api.yaml b/products/dns/api.yaml new file mode 100644 index 000000000000..1df48e9fe1a0 --- /dev/null +++ b/products/dns/api.yaml @@ -0,0 +1,183 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: Google Cloud DNS +prefix: gdns +base_url: https://www.googleapis.com/dns/v1/ +scopes: + - https://www.googleapis.com/auth/ndev.clouddns.readwrite +objects: + - !ruby/object:Api::Resource + # We are not exposing Change directly to the customer, as we cannot + # guarantee idempotency of it given its transactional nature. + name: 'Change' + description: 'An atomic update to a collection of ResourceRecordSets.' + exclude: true + - !ruby/object:Api::Resource + name: 'ManagedZone' + kind: 'dns#managedZone' + base_url: 'projects/{{project}}/managedZones' + description: | + A zone is a subtree of the DNS namespace under one administrative + responsibility. A ManagedZone is a resource that represents a DNS zone + hosted by the Cloud DNS service. + exports: + - name + properties: + - !ruby/object:Api::Type::String + name: 'description' + description: | + A mutable string of at most 1024 characters associated with this + resource for the user's convenience. Has no effect on the managed + zone's function. + - !ruby/object:Api::Type::String + name: 'dnsName' + description: | + The DNS name of this managed zone, for instance "example.com.". + - !ruby/object:Api::Type::Integer + name: 'id' + description: Unique identifier for the resource; defined by the server. + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + User assigned name for this resource. + Must be unique within the project. + required: true + - !ruby/object:Api::Type::Array + item_type: Api::Type::String + name: 'nameServers' + description: | + Delegate your managed_zone to these virtual name servers; + defined by the server + output: true + - !ruby/object:Api::Type::Array + item_type: Api::Type::String + name: 'nameServerSet' + description: | + Optionally specifies the NameServerSet for this ManagedZone. A + NameServerSet is a set of DNS name servers that all host the same + ManagedZones. Most users will leave this field unset. + - !ruby/object:Api::Type::Time + name: 'creationTime' + description: | + The time that this resource was created on the server. + This is in RFC3339 text format. + output: true + - !ruby/object:Api::Resource + name: 'Project' + kind: 'dns#project' + description: | + A project resource. The project is a top level container for resources + including Cloud DNS ManagedZones. + base_url: 'projects' + virtual: true + properties: + - !ruby/object:Api::Type::Integer + name: 'number' + description: | + Unique numeric identifier for the resource; defined by the server. + output: true + - !ruby/object:Api::Type::Integer + name: 'quota.managedZones' + description: Maximum allowed number of managed zones in the project. + output: true + - !ruby/object:Api::Type::Integer + name: 'quota.resourceRecordsPerRrset' + description: | + Maximum allowed number of ResourceRecords per ResourceRecordSet. + output: true + - !ruby/object:Api::Type::Integer + name: 'quota.rrsetAdditionsPerChange' + description: | + Maximum allowed number of ResourceRecordSets to add per + ChangesCreateRequest. + output: true + - !ruby/object:Api::Type::Integer + name: 'quota.rrsetDeletionsPerChange' + description: | + Maximum allowed number of ResourceRecordSets to delete per + ChangesCreateRequest. + output: true + - !ruby/object:Api::Type::Integer + name: 'quota.rrsetsPerManagedZone' + description: | + Maximum allowed number of ResourceRecordSets per zone in the + project. + output: true + - !ruby/object:Api::Type::Integer + name: 'quota.totalRrdataSizePerChange' + description: | + Maximum allowed size for total rrdata in one ChangesCreateRequest + in bytes. + output: true + - !ruby/object:Api::Resource + name: 'ResourceRecordSet' + kind: 'dns#resourceRecordSet' + description: 'A unit of data that will be returned by the DNS servers.' + base_url: | + projects/{{project}}/managedZones/{{managed_zone}}/changes + <|extra|> + self_link: | + projects/{{project}}/managedZones/{{managed_zone}}/rrsets + ?name={{name}}&type={{type}} + self_link_query: !ruby/object:Api::Resource::ResponseList + kind: 'dns#resourceRecordSetsListResponse' + items: 'rrsets' + identity: + - name + - type + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'managed_zone' + description: | + Identifies the managed zone addressed by this request. + Can be the managed zone name or id. + required: true + resource: 'ManagedZone' + imports: 'name' + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: For example, www.example.com. + required: true + - !ruby/object:Api::Type::Enum + name: 'type' + values: + - :A + - :AAAA + - :CAA + - :CNAME + - :MX + - :NAPTR + - :NS + - :PTR + - :SOA + - :SPF + - :SRV + - :TXT + description: One of valid DNS resource types. + # TODO(nelsonjr): Enforce required in provider manifest + required: true + - !ruby/object:Api::Type::Integer + name: 'ttl' + description: | + Number of seconds that this ResourceRecordSet can be cached by + resolvers. + - !ruby/object:Api::Type::Array + item_type: Api::Type::String + name: 'target' + description: | + As defined in RFC 1035 (section 5) and RFC 1034 (section 3.6.1) + field: rrdatas diff --git a/products/dns/chef-e2e.yaml b/products/dns/chef-e2e.yaml new file mode 100644 index 000000000000..4f033308327a --- /dev/null +++ b/products/dns/chef-e2e.yaml @@ -0,0 +1,29 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Chef::Tester + product: 'DNS' + tests: + - !ruby/object:Chef::StandardTest + name: 'ManagedZone' + module: 'gdns' + - !ruby/object:Chef::StandardTest + name: 'ResourceRecordSet' + module: 'gdns' + affected_count: 3 + resource_count: 4 diff --git a/products/dns/chef.yaml b/products/dns/chef.yaml new file mode 100644 index 000000000000..8cb01351b5f5 --- /dev/null +++ b/products/dns/chef.yaml @@ -0,0 +1,116 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Chef::Config +manifest: !ruby/object:Provider::Chef::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/chef-google-dns' + issues: 'https://github.com/GoogleCloudPlatform/chef-google-dns/issues' + summary: 'A Chef cookbook to manage Google Cloud DNS resources' + description: | + This cookbook provides the built-in types and services for Chef to manage + Google Cloud DNS resources, as native Chef types. + depends: + - !ruby/object:Provider::Config::Requirements + name: 'google-gauth' + versions: '< 0.2.0' + operating_systems: +<%= indent(include('provider/chef/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + ManagedZone: + update: | + message = 'ManagedZone cannot be edited' + Chef::Log.fatal message + raise message + ResourceRecordSet: + create: | + change = create_change nil, updated_record, new_resource + change_id = change['id'].to_i + debug("created for transaction '#{change_id}' to complete") + wait_for_change_to_complete change_id, @new_resource \ + if change['status'] == 'pending' + delete: | + change = create_change fetch, nil, @new_resource + change_id = change['id'].to_i + debug("created for transaction '#{change_id}' to complete") + wait_for_change_to_complete change_id, @new_resource \ + if change['status'] == 'pending' + update: | + change = create_change fetch, updated_record, new_resource + change_id = change['id'].to_i + debug("created for transaction '#{change_id}' to complete") + wait_for_change_to_complete change_id, new_resource \ + if change['status'] == 'pending' + provider_helpers: + visible: + unwrap_resource: false + resource_to_request: false + return_if_object: false + include: + - 'products/dns/helpers/chef_provider_resource_set.rb.erb' + - 'products/dns/helpers/provider_resource_set.rb.erb' + Project: + update: | + message = 'Project cannot be edited' + Chef::Log.fatal message + raise message +tests: !ruby/object:Api::Resource::HashArray +<%= indent(include('products/dns/test.yaml'), 2) %> +properties: + - array + - enum + - integer + - string + - time +# TODO(nelsonjr): Figure out a way to create a test cookbook and publish +# examples to whatever structure we eventually create. +examples: !ruby/object:Api::Resource::HashArray + ManagedZone: + - delete_managed_zone.rb + - managed_zone.rb + Project: + - project.rb + ResourceRecordSet: + - delete_resource_record_set.rb + - resource_record_set.rb +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/chef/common~copy.yaml'), 4) %> + compile: +<%= indent(include('provider/chef/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/chef/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData +<%= indent(include('products/dns/network_test_data.yaml'), 2) %> +style: + - !ruby/object:Provider::Config::StyleException + name: resources/managed_zone.rb + pinpoints: + - class: Google::GDNS::ManagedZone + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/resource_record_set.rb + pinpoints: + - class: Google::GDNS::ResourceRecordSet + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/project.rb + pinpoints: + - class: Google::GDNS::Project + exceptions: + - Metrics/ClassLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength diff --git a/products/dns/files/examples~cookbook~delete_managed_zone.rb b/products/dns/files/examples~cookbook~delete_managed_zone.rb new file mode 100644 index 000000000000..5eecec1136d8 --- /dev/null +++ b/products/dns/files/examples~cookbook~delete_managed_zone.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gdns_managed_zone <%= example_resource_name('testzone-3-com') -%> do + action :delete + credential 'mycred' + project 'google.com:graphite-playground' +end diff --git a/products/dns/files/examples~cookbook~delete_resource_record_set.rb b/products/dns/files/examples~cookbook~delete_resource_record_set.rb new file mode 100644 index 000000000000..699cf5cd5128 --- /dev/null +++ b/products/dns/files/examples~cookbook~delete_resource_record_set.rb @@ -0,0 +1,47 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gdns_resource_record_set <%= example_resource_name('www.testzone-4.com.') -%> do + action :delete + managed_zone <%= example_resource_name('testzone-4-com') %> + type 'A' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% res_name = 'sites.testzone-4.com.' -%> +gdns_resource_record_set <%= example_resource_name(res_name) -%> do + action :delete + managed_zone <%= example_resource_name('testzone-4-com') %> + type 'CNAME' + project 'google.com:graphite-playground' + credential 'mycred' +end +<% unless name == 'README.md' -%> + +gdns_managed_zone <%= example_resource_name('testzone-4-com') -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end +<% end -%> diff --git a/products/dns/files/examples~cookbook~managed_zone.rb b/products/dns/files/examples~cookbook~managed_zone.rb new file mode 100644 index 000000000000..f94c93d24dc7 --- /dev/null +++ b/products/dns/files/examples~cookbook~managed_zone.rb @@ -0,0 +1,49 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gdns_managed_zone <%= example_resource_name('testzone-3-com') -%> do + action :create + dns_name 'test.somewild-example.com.' + description 'Test Example Zone' + + # You can also set output-only values as well. Chef will ignore the values + # when creating the resource, but will assert that its value matches what you + # specified. + # + # This important to ensure that, for example, the top-level registrar is using + # the correct DNS server names. Although this can cause failures in a run from + # a clean project, it is useful to ensure that there are no mismatches in the + # different services. + # + # id 579_667_184_320_567_887 + # name_servers [ + # 'ns-cloud-b1.googledomains.com.', + # 'ns-cloud-b2.googledomains.com.', + # 'ns-cloud-b3.googledomains.com.', + # 'ns-cloud-b4.googledomains.com.' + # ] + # creation_time '2016-12-02T04:59:24.333Z' + + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/dns/files/examples~cookbook~project.rb b/products/dns/files/examples~cookbook~project.rb new file mode 100644 index 000000000000..e3f8f85b19e8 --- /dev/null +++ b/products/dns/files/examples~cookbook~project.rb @@ -0,0 +1,36 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +# Ensures a project exists and has the correct values. +# +# All project settings are read-only, yet we are setting them anyway. Chef will +# use these values to check if they match, and fail the run otherwise. +# +# This important to ensure that your project quotas are set properly and avoid +# discrepancies from it to fail in production. +gdns_project <%= example_resource_name('google.com:graphite-playground') -%> do + quota_managed_zones 10_000 + quota_total_rrdata_size_per_change 100_000 + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/dns/files/examples~cookbook~readme.rb b/products/dns/files/examples~cookbook~readme.rb new file mode 100644 index 000000000000..9e2a54fee33b --- /dev/null +++ b/products/dns/files/examples~cookbook~readme.rb @@ -0,0 +1,37 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gdns_managed_zone 'testzone-3-com' do + action :create + dns_name 'test.somewild-example.com.' + description 'Test Example Zone' + credential 'mycred' + project 'google.com:graphite-playground' +end + +gdns_resource_record_set 'www.testzone-4.com.' do + action :create + managed_zone 'testzone-3-com' + type 'A' + ttl 600 + target [ + '10.1.2.3', + '40.5.6.7', + '80.9.10.11' + ] + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/dns/files/examples~cookbook~resource_record_set.rb b/products/dns/files/examples~cookbook~resource_record_set.rb new file mode 100644 index 000000000000..87a72aac8d8c --- /dev/null +++ b/products/dns/files/examples~cookbook~resource_record_set.rb @@ -0,0 +1,59 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +<% if name == 'README.md' -%> +# The property managed_zone below needs to match a gdns_managed_zone recipe +# block executed before it +<% else -%> +gdns_managed_zone <%= example_resource_name('testzone-4-com') -%> do + action :create + dns_name 'testzone-4.com.' + description 'Test Example Zone' + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end -%> +gdns_resource_record_set <%= example_resource_name('www.testzone-4.com.') -%> do + action :create + managed_zone <%= example_resource_name('testzone-4-com') %> + type 'A' + ttl 600 + target [ + '10.1.2.3', + '40.5.6.7', + '80.9.10.11' + ] + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% res_name = 'sites.testzone-4.com.' -%> +gdns_resource_record_set <%= example_resource_name(res_name) -%> do + action :create + managed_zone <%= example_resource_name('testzone-4-com') %> + type 'CNAME' + target ['www.testzone-4.com.'] + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/dns/files/examples~delete_managed_zone.pp b/products/dns/files/examples~delete_managed_zone.pp new file mode 100644 index 000000000000..5da87ab6ad1a --- /dev/null +++ b/products/dns/files/examples~delete_managed_zone.pp @@ -0,0 +1,51 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +# An example Puppet manifest that ensures Google Cloud Computing DNS Managed +# Zones in a project do not exist. + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# Ensures the managed zone is not in the project. +gdns_managed_zone { <%= example_resource_name('test-example-zone') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +# Ensures the managed zone is not in the project. +gdns_managed_zone { <%= example_resource_name('testzone-2-com') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +# Ensures the managed zone is not in the project. +gdns_managed_zone { <%= example_resource_name('id-for-testzone-3-com') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gdns_managed_zone { <%= example_resource_name('testzone-4-com') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/dns/files/examples~delete_resource_record_set.pp b/products/dns/files/examples~delete_resource_record_set.pp new file mode 100644 index 000000000000..11a3f1eeba50 --- /dev/null +++ b/products/dns/files/examples~delete_resource_record_set.pp @@ -0,0 +1,59 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +# An example Puppet manifest that ensures Google Cloud Computing DNS Resource +# Record Set in a project do not exist. + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gdns_managed_zone { <%= example_resource_name('testzone-4-com') -%>: + ensure => present, + name => 'testzone-4-com', + dns_name => 'testzone-4.com.', + description => 'Test Example Zone', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gdns_resource_record_set { <%= example_resource_name('www.testzone-4.com.') -%>: + ensure => absent, + managed_zone => <%= example_resource_name('testzone-4-com') -%>, + type => 'A', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% res_name = 'sites.testzone-4.com.' -%> +gdns_resource_record_set { <%= example_resource_name(res_name) -%>: + ensure => absent, + managed_zone => <%= example_resource_name('testzone-4-com') -%>, + type => 'CNAME', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +<% res_name = 'deleteme.testzone-4.com.' -%> +gdns_resource_record_set { <%= example_resource_name(res_name) -%>: + ensure => absent, + managed_zone => <%= example_resource_name('testzone-4-com') -%>, + type => 'A', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/dns/files/examples~managed_zone.pp b/products/dns/files/examples~managed_zone.pp new file mode 100644 index 000000000000..66132806d328 --- /dev/null +++ b/products/dns/files/examples~managed_zone.pp @@ -0,0 +1,71 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +# An example Puppet manifest that creates a Google Cloud Computing DNS Managed +# Zone in a project. + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# Ensures a managed zone exists and has the correct values. +# +gdns_managed_zone { <%= example_resource_name('test-example-zone') -%>: + ensure => present, + dns_name => 'test.somewild-example.com.', + description => 'Test Example Zone', + + # You can also set output-only values as well. Puppet will ignore the values + # when creating the resource, but will assert that its value matches what you + # specified. + # + # This important to ensure that, for example, the top-level registrar is using + # the correct DNS server names. Although this can cause failures in a run from + # a clean project, it is useful to ensure that there are no mismatches in the + # different services. + # + # id => 8550163345207615620, + # name_servers => [ + # 'ns-cloud-a1.googledomains.com.', + # 'ns-cloud-a2.googledomains.com.', + # 'ns-cloud-a3.googledomains.com.', + # 'ns-cloud-a4.googledomains.com.', + # ], + # creation_time => '2016-12-02T04:59:24.333Z', + + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +# Ensures a managed zone exists and has the correct values. +gdns_managed_zone { <%= example_resource_name('testzone-2-com') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +# Ensures a managed zone exists and has the correct values. +<% end # name == README.md -%> +<% res_name = 'id-for-testzone-3-com' -%> +gdns_managed_zone { <%= example_resource_name(res_name) -%>: + ensure => present, + name => 'testzone-3-com', + dns_name => 'test.somewild-example.com.', + description => 'Test Example Zone', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/dns/files/examples~project.pp b/products/dns/files/examples~project.pp new file mode 100644 index 000000000000..77ecc545cc61 --- /dev/null +++ b/products/dns/files/examples~project.pp @@ -0,0 +1,37 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +# An example Puppet manifest that creates a Google Cloud Computing DNS Managed +# Zone in a project. + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# Ensures a project exists and has the correct values. +# +# All project settings are read-only, yet we are setting them anyway. Puppet +# will use these values to check if they match, and fail the run otherwise. +# +# This important to ensure that your project quotas are set properly and avoid +# discrepancies from it to fail in production. +<% end # name == README.md -%> +gdns_project { <%= example_resource_name('google.com:graphite-playground') -%>: + credential => 'mycred', + quota_managed_zones => 10000, + quota_total_rrdata_size_per_change => 100000, +} diff --git a/products/dns/files/examples~resource_record_set.pp b/products/dns/files/examples~resource_record_set.pp new file mode 100644 index 000000000000..4328dddf87ed --- /dev/null +++ b/products/dns/files/examples~resource_record_set.pp @@ -0,0 +1,64 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gdns_managed_zone { <%= example_resource_name('some-managed-zone') -%>: + ensure => present, + name => 'testzone-4-com', + dns_name => 'testzone-4.com.', + description => 'Test Example Zone', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% res_name = 'www.testzone-4.com.' -%> +gdns_resource_record_set { <%= example_resource_name(res_name) -%>: + ensure => present, + managed_zone => <%= example_resource_name('some-managed-zone') -%>, + type => 'A', + ttl => 600, + target => [ + '10.1.2.3', + '40.5.6.7', + '80.9.10.11' + ], + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% res_name = 'sites.testzone-4.com.' -%> +gdns_resource_record_set { <%= example_resource_name(res_name) -%>: + ensure => present, + managed_zone => <%= example_resource_name('some-managed-zone') -%>, + type => 'CNAME', + target => 'www.testzone-4.com.', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% res_name = 'deleteme.testzone-4.com.' -%> +gdns_resource_record_set { <%= example_resource_name(res_name) -%>: + ensure => absent, + managed_zone => <%= example_resource_name('some-managed-zone') -%>, + type => 'A', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/dns/files/spec~resource_record_set~create~name.yaml b/products/dns/files/spec~resource_record_set~create~name.yaml new file mode 100644 index 000000000000..51686ebfab3c --- /dev/null +++ b/products/dns/files/spec~resource_record_set~create~name.yaml @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#change +additions: +- kind: dns#resourceRecordSet + name: dns5.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 667 222 333 444 555 +- kind: dns#resourceRecordSet + name: test name#0 data + type: A + ttl: 1842713477 + rrdatas: + - ff + - gg + - hh +deletions: +- kind: dns#resourceRecordSet + name: dns5.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 666 222 333 444 555 +startTime: '2017-01-02T03:04:05+00:00' diff --git a/products/dns/files/spec~resource_record_set~create~title.yaml b/products/dns/files/spec~resource_record_set~create~title.yaml new file mode 100644 index 000000000000..dbce40ae7a4a --- /dev/null +++ b/products/dns/files/spec~resource_record_set~create~title.yaml @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#change +additions: +- kind: dns#resourceRecordSet + name: dns5.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 556 222 333 444 555 +- kind: dns#resourceRecordSet + name: title0 + type: A + ttl: 1842713477 + rrdatas: + - ff + - gg + - hh +deletions: +- kind: dns#resourceRecordSet + name: dns5.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 555 222 333 444 555 +startTime: '2017-01-02T03:04:05+00:00' diff --git a/products/dns/files/spec~resource_record_set~delete~name.yaml b/products/dns/files/spec~resource_record_set~delete~name.yaml new file mode 100644 index 000000000000..206d9a24079f --- /dev/null +++ b/products/dns/files/spec~resource_record_set~delete~name.yaml @@ -0,0 +1,50 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +# NOTE: The deleted resource record set is intentionally missing here. Because +# this is an asynchronous operation, and our FakeWeb network capture framework +# can only accomodate looking at the last request, then we miss its passing +# through the wire. +# +# TODO(nelsonjr): Contribute to FakeWeb to enable capturing all traffic of a +# session, so we can inspect properly this request. +--- +kind: dns#change +additions: +- kind: dns#resourceRecordSet + name: dns4.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 223 222 333 444 555 +deletions: +- kind: dns#resourceRecordSet + name: dns4.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 222 222 333 444 555 +- kind: dns#resourceRecordSet + name: test name#0 data + type: A + ttl: 1842713477 + rrdatas: + - ff + - gg + - hh +startTime: '2017-01-02T03:04:05+00:00' diff --git a/products/dns/files/spec~resource_record_set~delete~title.yaml b/products/dns/files/spec~resource_record_set~delete~title.yaml new file mode 100644 index 000000000000..c3923b79e1f6 --- /dev/null +++ b/products/dns/files/spec~resource_record_set~delete~title.yaml @@ -0,0 +1,50 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +# NOTE: The deleted resource record set is intentionally missing here. Because +# this is an asynchronous operation, and our FakeWeb network capture framework +# can only accomodate looking at the last request, then we miss its passing +# through the wire. +# +# TODO(nelsonjr): Contribute to FakeWeb to enable capturing all traffic of a +# session, so we can inspect properly this request. +--- +kind: dns#change +additions: +- kind: dns#resourceRecordSet + name: dns4.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 112 222 333 444 555 +deletions: +- kind: dns#resourceRecordSet + name: dns4.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 111 222 333 444 555 +- kind: dns#resourceRecordSet + name: title0 + type: A + ttl: 1842713477 + rrdatas: + - ff + - gg + - hh +startTime: '2017-01-02T03:04:05+00:00' diff --git a/products/dns/files/spec~resource_record_set~soa1-111.yaml b/products/dns/files/spec~resource_record_set~soa1-111.yaml new file mode 100644 index 000000000000..413193bbee13 --- /dev/null +++ b/products/dns/files/spec~resource_record_set~soa1-111.yaml @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#resourceRecordSetsListResponse +rrsets: + - kind: dns#resourceRecordSet + name: dns4.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 111 222 333 444 555 diff --git a/products/dns/files/spec~resource_record_set~soa1-222.yaml b/products/dns/files/spec~resource_record_set~soa1-222.yaml new file mode 100644 index 000000000000..4540840140b9 --- /dev/null +++ b/products/dns/files/spec~resource_record_set~soa1-222.yaml @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#resourceRecordSetsListResponse +rrsets: + - kind: dns#resourceRecordSet + name: dns4.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 222 222 333 444 555 diff --git a/products/dns/files/spec~resource_record_set~soa1-555.yaml b/products/dns/files/spec~resource_record_set~soa1-555.yaml new file mode 100644 index 000000000000..660369c5813f --- /dev/null +++ b/products/dns/files/spec~resource_record_set~soa1-555.yaml @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#resourceRecordSetsListResponse +rrsets: + - kind: dns#resourceRecordSet + name: dns5.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 555 222 333 444 555 diff --git a/products/dns/files/spec~resource_record_set~soa1-666.yaml b/products/dns/files/spec~resource_record_set~soa1-666.yaml new file mode 100644 index 000000000000..f808c6df0823 --- /dev/null +++ b/products/dns/files/spec~resource_record_set~soa1-666.yaml @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#resourceRecordSetsListResponse +rrsets: + - kind: dns#resourceRecordSet + name: dns5.com. + type: SOA + ttl: 666 + rrdatas: + - dns1.example.com admin.example.com 666 222 333 444 555 diff --git a/products/dns/files/spec~resource_record_set~success1~name.yaml b/products/dns/files/spec~resource_record_set~success1~name.yaml new file mode 100644 index 000000000000..b474e49301f7 --- /dev/null +++ b/products/dns/files/spec~resource_record_set~success1~name.yaml @@ -0,0 +1,29 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#resourceRecordSetsListResponse +rrsets: + - kind: dns#resourceRecordSet + name: 'test name#0 data' + type: 'A' + ttl: 1842713477 + rrdatas: + - ff + - gg + - hh diff --git a/products/dns/files/spec~resource_record_set~success1~title.yaml b/products/dns/files/spec~resource_record_set~success1~title.yaml new file mode 100644 index 000000000000..4b437b97d6db --- /dev/null +++ b/products/dns/files/spec~resource_record_set~success1~title.yaml @@ -0,0 +1,29 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#resourceRecordSetsListResponse +rrsets: + - kind: dns#resourceRecordSet + name: 'title0' + type: 'A' + ttl: 1842713477 + rrdatas: + - ff + - gg + - hh diff --git a/products/dns/files/spec~resource_record_set~success2~name.yaml b/products/dns/files/spec~resource_record_set~success2~name.yaml new file mode 100644 index 000000000000..92f5e4144ccd --- /dev/null +++ b/products/dns/files/spec~resource_record_set~success2~name.yaml @@ -0,0 +1,30 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#resourceRecordSetsListResponse +rrsets: + - kind: dns#resourceRecordSet + name: 'test name#1 data' + type: 'AAAA' + ttl: 3685426954 + rrdatas: + - mm + - nn + - oo + - pp diff --git a/products/dns/files/spec~resource_record_set~success2~title.yaml b/products/dns/files/spec~resource_record_set~success2~title.yaml new file mode 100644 index 000000000000..894d873f39f9 --- /dev/null +++ b/products/dns/files/spec~resource_record_set~success2~title.yaml @@ -0,0 +1,30 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#resourceRecordSetsListResponse +rrsets: + - kind: dns#resourceRecordSet + name: 'title1' + type: 'AAAA' + ttl: 3685426954 + rrdatas: + - mm + - nn + - oo + - pp diff --git a/products/dns/files/spec~resource_record_set~success3~name.yaml b/products/dns/files/spec~resource_record_set~success3~name.yaml new file mode 100644 index 000000000000..aade1c2b16f0 --- /dev/null +++ b/products/dns/files/spec~resource_record_set~success3~name.yaml @@ -0,0 +1,31 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#resourceRecordSetsListResponse +rrsets: + - kind: dns#resourceRecordSet + name: 'test name#2 data' + type: 'CAA' + ttl: 5528140432 + rrdatas: + - tt + - uu + - vv + - ww + - xx diff --git a/products/dns/files/spec~resource_record_set~success3~title.yaml b/products/dns/files/spec~resource_record_set~success3~title.yaml new file mode 100644 index 000000000000..d724d980a9d9 --- /dev/null +++ b/products/dns/files/spec~resource_record_set~success3~title.yaml @@ -0,0 +1,31 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: dns#resourceRecordSetsListResponse +rrsets: + - kind: dns#resourceRecordSet + name: 'title2' + type: 'CAA' + ttl: 5528140432 + rrdatas: + - tt + - uu + - vv + - ww + - xx diff --git a/products/dns/helpers/chef_provider_resource_set.rb.erb b/products/dns/helpers/chef_provider_resource_set.rb.erb new file mode 100644 index 000000000000..83cb812d8b1c --- /dev/null +++ b/products/dns/helpers/chef_provider_resource_set.rb.erb @@ -0,0 +1,69 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +def updated_record + { + kind: 'dns#resourceRecordSet', + name: @new_resource.rrs_label, + type: @new_resource.type, + ttl: @new_resource.ttl.nil? ? 900 : @new_resource.ttl, + rrdatas: @new_resource.target + } +end + +# Wraps the SOA resource to fetch from DNS API +class SOAResource + extend Forwardable + + attr_reader :name + attr_reader :type + + alias rrs_label name + + def_delegators :@resource, :ttl, :target + def_delegators :@resource, :managed_zone, :project, :credential + def_delegators :@resource, :resources + + def initialize(args) + @name = args[:name] || (raise 'Missing "name"') + @type = args[:type] || (raise 'Missing "type"') + @resource = SimpleDelegator.new(args[:resource]) \ + || (raise 'Missing "resource"') + end +end + +def unwrap_resource(result, resource) + self.class.unwrap_resource(result, resource) +end + +def self.unwrap_resource(result, _resource) + # DNS service already did server-side filtering. + result.first +end + +def prefetch_soa_resource + resource = SOAResource.new( + type: 'SOA', + name: "#{rrs_label.split('.').drop(1).join('.')}.", + resource: @new_resource + ) + result = fetch_wrapped_resource(resource, 'dns#resourceRecordSet', + 'dns#resourceRecordSetsListResponse', + 'rrsets') + if result.nil? + raise ['Google DNS Managed Zone ', "'#{resource.managed_zone}'", + 'recipe not found.'].join(' ') + end + result +end diff --git a/products/dns/helpers/expect_resource_set.rb.erb b/products/dns/helpers/expect_resource_set.rb.erb new file mode 100644 index 000000000000..e0ed892a55e2 --- /dev/null +++ b/products/dns/helpers/expect_resource_set.rb.erb @@ -0,0 +1,41 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def expect_network_get_soa(serial, id, extra = {}) + body = load_network_result("soa#{id}-#{serial}.yaml").to_json + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + extra = extra.merge(name: '.', type: :SOA) + expect(Google::<%= product_ns -%>::Network::Get).to receive(:new) + .with(self_link(uri_data(id).merge(extra)), + instance_of(Google::FakeAuthorization)) + .and_return(request) +end + +def expect_network_create_change(change_id, id, done, expected_body, + extra = {}) + body = { + kind: 'dns#change', + id: change_id, + status: done ? 'done' : 'pending' + }.to_json + + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + + expect(Google::<%= product_ns -%>::Network::Post).to receive(:new) + .with(collection(uri_data(id).merge(extra)), + instance_of(Google::FakeAuthorization), + 'application/json', expected_body.to_json) + .and_return(request) +end diff --git a/products/dns/helpers/provider_resource_set.rb.erb b/products/dns/helpers/provider_resource_set.rb.erb new file mode 100644 index 000000000000..3350c89280a8 --- /dev/null +++ b/products/dns/helpers/provider_resource_set.rb.erb @@ -0,0 +1,104 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +def create_change(original, updated, resource) + create_req = ::Google::<%= product_ns -%>::Network::Post.new( + collection(resource), fetch_auth(resource), + 'application/json', resource_to_change_request(original, updated) + ) + return_if_change_object create_req.send +end + +# Fetch current SOA. We need the last SOA so we can increment its serial +def update_soa + original_soa = prefetch_soa_resource + + # Create a clone of the SOA record so we can update it + updated_soa = original_soa.clone + updated_soa.each_key do |k| + updated_soa[k] = original_soa[k].clone \ + unless original_soa[k].is_a?(Integer) + end + + soa_parts = updated_soa['rrdatas'][0].split(' ') + soa_parts[2] = soa_parts[2].to_i + 1 + updated_soa['rrdatas'][0] = soa_parts.join(' ') + [original_soa, updated_soa] +end + +def resource_to_change_request(original_record, updated_record) + original_soa, updated_soa = update_soa + result = new_change_request + add_additions result, updated_soa, updated_record + add_deletions result, original_soa, original_record + ::Google::HashUtils.camelize_keys(result).to_json +end + +def add_additions(result, updated_soa, updated_record) + result[:additions] << updated_soa unless updated_soa.nil? + result[:additions] << updated_record unless updated_record.nil? +end + +def add_deletions(result, original_soa, original_record) + result[:deletions] << original_soa unless original_soa.nil? + result[:deletions] << original_record unless original_record.nil? +end + +# TODO(nelsonjr): Merge and delete this code once async operations +# declared in api.yaml is moved to master from: +# https://cloud-internal.googlesource.com/cloud-graphite-team/ +# config-modules/codegen/+/ +# 2ccb0eb5cb207f67b297c6058d2455240d7316bf/ +# compute/api.yaml#9 +def wait_for_change_to_complete(change_id, resource) + status = 'pending' + while status == 'pending' + debug("waiting for transaction '#{change_id}' to complete") + status = get_change_status(change_id, resource) + sleep(0.5) unless status == 'done' + end + debug("transaction '#{change_id}' complete") +end + +def get_change_status(change_id, resource) + change_req = ::Google::<%= product_ns -%>::Network::Get.new( + collection(resource, '/{{id}}', id: change_id), fetch_auth(resource) + ) + return_if_change_object(change_req.send)['status'] +end + +def new_change_request + { + kind: 'dns#change', + additions: [], + deletions: [], + start_time: Time.now.iso8601 + } +end + +def return_if_change_object(response) + raise "Bad request: #{response.body}" \ + if response.is_a?(Net::HTTPBadRequest) + raise "Bad response: #{response}" \ + unless response.is_a?(Net::HTTPResponse) + return unless response.class >= Net::HTTPOK + result = JSON.parse(response.body) + raise "Incorrect result: #{result['kind']}" \ + unless result['kind'] == 'dns#change' + result +end + +def fetch_resource(resource, self_link, kind) + self.class.fetch_resource(resource, self_link, kind) +end diff --git a/products/dns/helpers/puppet_provider_resource_set.rb.erb b/products/dns/helpers/puppet_provider_resource_set.rb.erb new file mode 100644 index 000000000000..3176cd532457 --- /dev/null +++ b/products/dns/helpers/puppet_provider_resource_set.rb.erb @@ -0,0 +1,54 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +def updated_record + { + kind: 'dns#resourceRecordSet', + name: @resource[:name], + type: @resource[:type], + ttl: @resource[:ttl].nil? ? 900 : @resource[:ttl], + rrdatas: @resource[:target] + } +end + +def unwrap_resource(result, resource) + self.class.unwrap_resource(result, resource) +end + +def self.unwrap_resource(result, _resource) + # DNS service already did server-side filtering. + result.first +end + +<%= + lines(emit_rubocop(binding, :function, 'prefetch_soa_resource', :disabled)) +-%> +def prefetch_soa_resource + resource = { + type: 'SOA', + name: "#{name.split('.').drop(1).join('.')}.", + managed_zone: @resource[:managed_zone], + project: @resource[:project], + credential: @resource[:credential] + } + result = fetch_wrapped_resource(resource, 'dns#resourceRecordSet', + 'dns#resourceRecordSetsListResponse', + 'rrsets') + if result.nil? + raise ['Google DNS Managed Zone ', "'#{managed_zone}'", + 'not present in the manifest file'].join(' ') + end + result +end +<%= emit_rubocop(binding, :function, 'prefetch_soa_resource', :enabled) -%> diff --git a/products/dns/network_test_data.yaml b/products/dns/network_test_data.yaml new file mode 100644 index 000000000000..9cb6d0d4d723 --- /dev/null +++ b/products/dns/network_test_data.yaml @@ -0,0 +1,29 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +network: !ruby/object:Api::Resource::HashArray + ResourceRecordSet: + - create~name + - create~title + - delete~name + - delete~title + - soa1-555 + - soa1-666 + - soa1-111 + - soa1-222 + - success1~name + - success1~title + - success2~name + - success2~title + - success3~name + - success3~title diff --git a/products/dns/puppet-e2e.yaml b/products/dns/puppet-e2e.yaml new file mode 100644 index 000000000000..7ce72e217e4c --- /dev/null +++ b/products/dns/puppet-e2e.yaml @@ -0,0 +1,40 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Puppet::Tester + product: 'DNS' + tests: + - !ruby/object:Puppet::StandardTest + name: 'ManagedZone' + verifiers: + - phase: ALL + command: | + gcloud dns managed-zones describe + --project=google.com:graphite-playground + puppet-e2e-test-example-zone + - !ruby/object:Puppet::StandardTest + name: 'ResourceRecordSet' + post: + - name: 'cleanup{post}' # our test creates the managed zone. delete it. + apply: + - run: 'managed_zone.pp' + exits: 2 + - name: 'cleanup{post}{again}' + apply: + - run: 'managed_zone.pp' + exits: 0 diff --git a/products/dns/puppet.yaml b/products/dns/puppet.yaml new file mode 100644 index 000000000000..e4d7cd5f2808 --- /dev/null +++ b/products/dns/puppet.yaml @@ -0,0 +1,111 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.1.1' + source: 'https://github.com/GoogleCloudPlatform/puppet-google-dns' + homepage: 'https://github.com/GoogleCloudPlatform/puppet-google-dns' + issues: 'https://github.com/GoogleCloudPlatform/puppet-google-dns/issues' + summary: 'A Puppet module to manage Google Cloud DNS resources' + tags: + - google + - cloud + - dns + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google/gauth' + versions: '>= 0.2.0 < 0.3.0' + operating_systems: +<%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + ManagedZone: + flush: raise 'DNS Managed Zone cannot be edited' if @dirty + ResourceRecordSet: + # template.provider: enables to specify another source for code production + # | template: + # | provider: 'products/dns/templates/resource_record_set.erb' + # TODO(alexstephen): Document the access_api_results field. + access_api_results: true + create: | + change = create_change nil, updated_record, @resource + change_id = change['id'].to_i + debug("created for transaction '#{change_id}' to complete") + wait_for_change_to_complete change_id, @resource \ + if change['status'] == 'pending' + delete: | + change = create_change @fetched, nil, @resource + change_id = change['id'].to_i + debug("created for transaction '#{change_id}' to complete") + wait_for_change_to_complete change_id, @resource \ + if change['status'] == 'pending' + flush: | + change = create_change @fetched, updated_record, @resource + change_id = change['id'].to_i + debug("created for transaction '#{change_id}' to complete") + wait_for_change_to_complete change_id, @resource \ + if change['status'] == 'pending' + request_to_query: | + # DNS service already did server-side filtering of resource. + lambda { |resource| true } + provider_helpers: + visible: + unwrap_resource: false + resource_to_request: false + return_if_object: false + include: + - 'products/dns/helpers/puppet_provider_resource_set.rb.erb' + - 'products/dns/helpers/provider_resource_set.rb.erb' + Project: + flush: raise 'DNS Project cannot be edited' if @dirty +tests: !ruby/object:Api::Resource::HashArray +<%= indent(include('products/dns/test.yaml'), 2) %> +examples: !ruby/object:Api::Resource::HashArray + ManagedZone: + - delete_managed_zone.pp + - managed_zone.pp + Project: + - project.pp + ResourceRecordSet: + - delete_resource_record_set.pp + - resource_record_set.pp +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/puppet/common~copy.yaml'), 4) %> + compile: +<%= indent(include('provider/puppet/common~compile~before.yaml'), 4) %> + lib/google/object_store.rb: google/object_store.rb +<%= indent(include('provider/puppet/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData +<%= indent(include('products/dns/network_test_data.yaml'), 2) %> +style: + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gdns_project/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.1.1' + date: 2017-10-10T06:00:00-0700 + fixes: + - Improved validation of required parameter references + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-08-22T09:00:00-0700 + general: 'Initial release' diff --git a/products/dns/test.yaml b/products/dns/test.yaml new file mode 100644 index 000000000000..2f583714cd8d --- /dev/null +++ b/products/dns/test.yaml @@ -0,0 +1,134 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ManagedZone: + flush: + cases: + - | + it 'cannot edit' do + expect do + subject.flush + end.to raise_error(StandardError, /cannot be edited/) + end +ResourceRecordSet: + present: + not_exist: + success: + title_eq_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_failed 1, name: 'title0', + managed_zone: 'test name#0 data' + expect_network_get_soa 555, 1, managed_zone: 'test name#0 data' + expect_network_create_change \\ + 555, 1, true, load_network_result('create~title.yaml'), + managed_zone: 'test name#0 data' + expect_network_get_success_managed_zone 1 + title_and_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_failed 1, managed_zone: 'test name#0 data' + expect_network_get_soa 666, 1, managed_zone: 'test name#0 data' + expect_network_create_change \\ + 666, 1, true, load_network_result('create~name.yaml'), + managed_zone: 'test name#0 data' + expect_network_get_success_managed_zone 1 + failed: + title_eq_name: + # TODO(nelsonjr): Implement this test layout + title_and_name: + # TODO(nelsonjr): Implement this test layout + exist: + success: + title_eq_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_success 1, name: 'title0', + managed_zone: 'test name#0 data' + expect_network_get_success 2, name: 'title1', + managed_zone: 'test name#1 data' + expect_network_get_success 3, name: 'title2', + managed_zone: 'test name#2 data' + expect_network_get_success_managed_zone 1 + expect_network_get_success_managed_zone 2 + expect_network_get_success_managed_zone 3 + title_and_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_success 1, managed_zone: 'test name#0 data' + expect_network_get_success 2, managed_zone: 'test name#1 data' + expect_network_get_success 3, managed_zone: 'test name#2 data' + expect_network_get_success_managed_zone 1 + expect_network_get_success_managed_zone 2 + expect_network_get_success_managed_zone 3 + failed: + title_eq_name: + # TODO(nelsonjr): Implement this test layout + title_and_name: + # TODO(nelsonjr): Implement this test layout + absent: + not_exist: + success: + title_eq_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_failed 1, name: 'title0', + managed_zone: 'test name#0 data' + expect_network_get_success_managed_zone 1 + title_and_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_failed 1, managed_zone: 'test name#0 data' + expect_network_get_success_managed_zone 1 + exists: + success: + title_eq_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_success 1, name: 'title0', + managed_zone: 'test name#0 data' + expect_network_get_soa 111, 1, managed_zone: 'test name#0 data' + expect_network_create_change \\ + 111, 1, true, load_network_result('delete~title.yaml'), + managed_zone: 'test name#0 data' + expect_network_get_success_managed_zone 1 + title_and_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_success 1, managed_zone: 'test name#0 data' + expect_network_get_soa 222, 1, managed_zone: 'test name#0 data' + expect_network_create_change \\ + 222, 1, true, load_network_result('delete~name.yaml'), + managed_zone: 'test name#0 data' + expect_network_get_success_managed_zone 1 + expectations: + custom: + create: true + delete: true + expectation_helpers: 'products/dns/helpers/expect_resource_set.rb.erb' diff --git a/products/dns/test_chef.yaml b/products/dns/test_chef.yaml new file mode 100644 index 000000000000..ff4eeae1bb73 --- /dev/null +++ b/products/dns/test_chef.yaml @@ -0,0 +1,113 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(alexstephen): Consolidate with Puppet tests once title != name +ManagedZone: + flush: + cases: + - | + it 'cannot edit' do + expect do + subject.flush + end.to raise_error(StandardError, /cannot be edited/) + end +ResourceRecordSet: + present: + not_exist: + success: + title_eq_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_failed 1, name: 'title0' + expect_network_get_soa 555, 1 + expect_network_create_change \\ + 555, 1, true, load_network_result('create~title.yaml') + title_and_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_failed 1 + expect_network_get_soa 666, 1, + expect_network_create_change \\ + 666, 1, true, load_network_result('create~name.yaml') + failed: + title_eq_name: + # TODO(nelsonjr): Implement this test layout + title_and_name: + # TODO(nelsonjr): Implement this test layout + exist: + success: + title_eq_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_success 1, name: 'title0' + expect_network_get_success 2, name: 'title1' + expect_network_get_success 3, name: 'title2' + title_and_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_success 1 + expect_network_get_success 2 + expect_network_get_success 3 + failed: + title_eq_name: + # TODO(nelsonjr): Implement this test layout + title_and_name: + # TODO(nelsonjr): Implement this test layout + absent: + not_exist: + success: + title_eq_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_failed 1, name: 'title0' + title_and_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_failed 1 + exists: + success: + title_eq_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_success 1, name: 'title0' + expect_network_get_soa 111, 1 + expect_network_create_change \\ + 111, 1, true, load_network_result('delete~title.yaml') + title_and_name: + before: | + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_success 1 + expect_network_get_soa 222, 1 + expect_network_create_change \\ + 222, 1, true, load_network_result('delete~name.yaml') + expectations: + custom: + create: true + delete: true + expectation_helpers: 'products/dns/helpers/expect_resource_set.rb' diff --git a/products/iam/api.yaml b/products/iam/api.yaml new file mode 100644 index 000000000000..48a8fd73eade --- /dev/null +++ b/products/iam/api.yaml @@ -0,0 +1,104 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Make all Zone and Region resource ref + +--- !ruby/object:Api::Product +name: Google Cloud IAM +prefix: giam +base_url: https://iam.googleapis.com/v1/ +scopes: + - https://www.googleapis.com/auth/iam +objects: + - !ruby/object:Api::Resource + name: 'ServiceAccount' + base_url: projects/{{project}}/serviceAccounts/ + transport: !ruby/object:Api::Resource::Transport + decoder: decode_response + description: | + A service account in the Identity and Access Management API. + exports: + - name + properties: + - !ruby/object:Api::Type::String + name: name + description: The name of the service account. + - !ruby/object:Api::Type::String + name: projectId + description: Id of the project that owns the service account. + output: true + - !ruby/object:Api::Type::String + name: uniqueId + description: Unique and stable id of the service account + output: true + - !ruby/object:Api::Type::String + name: email + description: Email address of the service account. + output: true + - !ruby/object:Api::Type::String + name: displayName + description: User specified description of service account. + field: displayName + - !ruby/object:Api::Type::String + name: etag + description: Used to perform a consistent read/modify/write. + - !ruby/object:Api::Type::String + name: oauth2ClientId + description: OAuth2 client id for the service account. + output: true + - !ruby/object:Api::Resource + name: 'ServiceAccountKey' + base_url: projects/{{project}}/serviceAccounts/{{service_account}}/keys + description: | + A service account in the Identity and Access Management API. + parameters: + - !ruby/object:Api::Type::ResourceRef + name: serviceAccount + description: The name of the serviceAccount. + resource: ServiceAccount + imports: name + properties: + - !ruby/object:Api::Type::String + name: name + description: The name of the key. + output: true + - !ruby/object:Api::Type::Enum + name: privateKeyType + description: Output format for the service account key. + values: + - :TYPE_UNSPECIFIED + - :TYPE_PKCS12_FILE + - :TYPE_GOOGLE_CREDENTIALS_FILE + - !ruby/object:Api::Type::Enum + name: keyAlgorithm + description: Specifies the algorithm for the key. + values: + - :KEY_ALG_UNSPECIFIED + - :KEY_ALG_RSA_1024 + - :KEY_ALG_RSA_2048 + - !ruby/object:Api::Type::String + name: privateKeyData + description: Private key data. Base-64 encoded. + output: true + - !ruby/object:Api::Type::String + name: publicKeyData + description: Public key data. Base-64 encoded. + output: true + - !ruby/object:Api::Type::Time + name: validAfterTime + description: Key can only be used after this time. + output: true + - !ruby/object:Api::Type::Time + name: validBeforeTime + description: Key can only be used before this time. + output: true diff --git a/products/iam/files/examples~delete_service_account.pp b/products/iam/files/examples~delete_service_account.pp new file mode 100644 index 000000000000..a244cd4d82f8 --- /dev/null +++ b/products/iam/files/examples~delete_service_account.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +giam_service_account { 'test-23489723@graphite-playground.google.com.iam.gserviceaccount.com': + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/iam/files/examples~delete_service_account_key.pp b/products/iam/files/examples~delete_service_account_key.pp new file mode 100644 index 000000000000..302863cfaeba --- /dev/null +++ b/products/iam/files/examples~delete_service_account_key.pp @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +giam_service_account_key { 'testkey1': + ensure => absent, + service_account => 'test123', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/iam/files/examples~service_account.pp b/products/iam/files/examples~service_account.pp new file mode 100644 index 000000000000..0c2beb8205ab --- /dev/null +++ b/products/iam/files/examples~service_account.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +giam_service_account { 'test-23489723@graphite-playground.google.com.iam.gserviceaccount.com': + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/iam/files/examples~service_account_key.pp b/products/iam/files/examples~service_account_key.pp new file mode 100644 index 000000000000..a6d904d95413 --- /dev/null +++ b/products/iam/files/examples~service_account_key.pp @@ -0,0 +1,41 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +giam_service_account { 'myaccount': + ensure => present, + name => + 'test-23489723@graphite-playground.google.com.iam.gserviceaccount.com', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +giam_service_account_key { 'test-name': + ensure => present, + #key_id => '9669de7d22f7be4d630783e4560f37df78b98297', + key_file => '/home/alexstephen/test.json', + overwrite_if_missing => true, + service_account => 'myaccount', + key_algorithm => 'KEY_ALG_UNSPECIFIED', + private_key_type => 'TYPE_GOOGLE_CREDENTIALS_FILE', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/iam/helpers/provider_service_account.rb b/products/iam/helpers/provider_service_account.rb new file mode 100644 index 000000000000..b8d1f180c49f --- /dev/null +++ b/products/iam/helpers/provider_service_account.rb @@ -0,0 +1,38 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Format the request to match the expected input by the API +def self.encode_request(resource_request) + account_id = resource_request[:name].split('@').first + resource_request.delete(:name) + { + 'accountId' => account_id, + 'serviceAccount' => resource_request + } +end + +def encode_request(resource_request) + self.class.encode_request(resource_request) +end + +# Format the response to match Puppet's expectations +def self.decode_response(response) + response = JSON.parse(response.body) + return response unless response.key? 'name' + response['name'] = response['name'].split('/').last + response +end + +def decode_response(response) + self.class.decode_response(response) +end diff --git a/products/iam/helpers/provider_service_account_key.rb b/products/iam/helpers/provider_service_account_key.rb new file mode 100644 index 000000000000..2b9c8e2a3ce9 --- /dev/null +++ b/products/iam/helpers/provider_service_account_key.rb @@ -0,0 +1,28 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Format the response to match Puppet's expectations +def self.decode_response(response) + response = JSON.parse(response.body) + if response.key? 'privateKeyData' + require 'base64' + require 'byebug' + @resource[:file] + Base64.decode(response['privateKeyData']) + end + response +end + +def decode_response(response) + self.class.decode_response(response) +end diff --git a/products/iam/puppet.yaml b/products/iam/puppet.yaml new file mode 100644 index 000000000000..2e70f100f690 --- /dev/null +++ b/products/iam/puppet.yaml @@ -0,0 +1,128 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/puppet-google-iam' + homepage: 'https://github.com/GoogleCloudPlatform/puppet-google-iam' + issues: + 'https://github.com/GoogleCloudPlatform/puppet-google-iam/issues' + summary: 'A Puppet module to manage Google Compute IAM resources' + tags: + - google + - cloud + - iam + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google/gauth' + versions: '< 0.2.0' + operating_systems: +<%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> +# TODO(nelsonjr): Match all special behavior Puppet <=> Chef. +objects: !ruby/object:Api::Resource::HashArray + ServiceAccountKey: + create: | + create_req = Google::Iam::Network::Post.new(collection(@resource), + fetch_auth(@resource), + 'application/json', + resource_to_request) + @fetched = return_if_object create_req.send + + # Write to file if a file name is provided. + if @fetched['privateKeyData'] and @resource[:key_file] + require 'base64' + json = Base64.decode64(@fetched['privateKeyData']) + File.open(@resource[:key_file], 'w') { |file| file.write(json) } + end + + delete: | + if @resource[:key_file] + file = File.open(@resource[:key_file]) + key_id = JSON.parse(file.read)['private_key_id'] + elsif @resource[:key_id] + key_id = @resource[:key_id] + else + raise 'You must provide either a key_file or a key_id' + end + + delete_req = Google::Iam::Network::Delete.new(self_link({ + service_account: @resource[:service_account], + project: @resource[:project], + key_id: key_id + }), + fetch_auth(@resource)) + return_if_object delete_req.send + prefetch: | + debug("prefetch #{name}") + if resource[:key_id] + # If key_id, check keys existence. + req = Google::Iam::Network::Get.new(self_link({ + service_account: resource[:service_account], + project: resource[:project], + key_id: resource[:key_id] + }), + fetch_auth(resource)) + resp = return_if_object req.send + raise 'This key no longer exists' if resp.nil? + resource.provider = present(name, resource) + elsif resource[:key_file] + filename = resource[:key_file] + resource.provider = present(name, resource) \ + if File.exists?(filename) + end + self_link: | + URI.join( + 'https://iam.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/serviceAccounts/{{service_account}}/keys/{{key_id}}', + data + ) + ) + present: | + def self.present(name, fetch) + new({ + title: name, + ensure: :present, + key_algorithm: fetch[:key_algorithm], + private_key_type: fetch[:private_key_type] + }) + end + ServiceAccount: + provider_helpers: + include: + - 'products/iam/helpers/provider_service_account.rb' +examples: !ruby/object:Api::Resource::HashArray + ServiceAccount: + - service_account.pp + - delete_service_account.pp + ServiceAccountKey: + - service_account_key.pp + - delete_service_account.pp +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/puppet/common~copy.yaml'), 4) %> + # Client-side functions require 'google/authorization' + spec/stubs/google/authorization.rb: templates/spec_lib_stub.rb.erb + compile: + lib/google/object_store.rb: google/object_store.rb +<%= indent(include('provider/puppet/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/puppet/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'Test Data will be automatically generated' +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-10-23T09:00:00-0700 + general: 'Initial release' diff --git a/products/presubmit.cfg b/products/presubmit.cfg new file mode 100644 index 000000000000..e2341e2b72a5 --- /dev/null +++ b/products/presubmit.cfg @@ -0,0 +1,54 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Projects tasks being skipped verification by presubmit +# +codegen = 80_chars:forgive |products/compute/puppet.yaml|homepage:| +codegen = 80_chars:forgive |products/container/puppet.yaml|homepage:| +codegen = 80_chars:forgive |products/dns/puppet.yaml|homepage:| +codegen = 80_chars:forgive |products/sql/puppet.yaml|homepage:| +codegen = 80_chars:forgive |products/storage/puppet.yaml|homepage:| +codegen = 80_chars:forgive |products/_bundle/templates/puppet/README.md.erb|\\[google-gauth]:| +codegen = 80_chars:forgive |products/_bundle/templates/chef/README.md.erb|\\[google-gauth]:| +codegen = 80_chars:forgive |products/compute/puppet.yaml|test: InstanceTemplate| +codegen = 80_chars:forgive |products/compute/chef.yaml|test: InstanceTemplate| +codegen = 80_chars:forgive |products/compute/chef.yaml|.*name:.*network_interfaces.*| +codegen = 80_chars:forgive |products/compute/test.yaml|.*subnetworkRangeName| +chef/auth = compile:skip +chef/auth = rubocop:noflag +chef/compute = 80_chars:forgive |spec/disk_provider_spec.rb|source_.*data| +chef/compute = 80_chars:forgive |spec/region_provider_spec.rb|deprecated.*data| +chef/compute = 80_chars:forgive |spec/backend_service_provider_spec.rb|group.*resource(instance| +chef/storage = 80_chars:forgive |spec/bucket_spec.rb|matches_storage_class.*| +chef/container = 80_chars:forgive |spec/cluster_spec.rb|cluster.*cluster_ca.*data| +chef/container = 80_chars:forgive |spec/node_pool_spec.rb|auto_upgrade_start_time.*| +chef/container = 80_chars:forgive |metadata.rb|issues_url.*| +chef/sql = 80_chars:forgive |spec/instance_spec.rb|service_account_email_address.*address.*| +chef/sql = 80_chars:forgive |spec/instance_spec.rb|client_certificate.*certificate.*data.*| +chef/sql = 80_chars:forgive |spec/instance_spec.rb|serviceAccountEmailAddress.*service.*data.*| +puppet/sql = 80_chars:forgive |spec/gsql_instance_provider_spec.rb|service_account_email_address => .*address.*| +puppet/sql = 80_chars:forgive |spec/gsql_instance_provider_spec.rb|client_certificate.*=> '.*certificate.*data.*| +puppet/sql = 80_chars:forgive |spec/gsql_instance_provider_spec.rb|serviceAccountEmailAddress.*=> '.*service.*data.*| +puppet/auth = compile:skip +puppet/auth = rubocop:noflag +puppet/compute = 80_chars:forgive |spec/gcompute_disk_provider_spec.rb|source_.*=>.*data| +puppet/compute = 80_chars:forgive |spec/gcompute_region_provider_spec.rb|deprecated.*=>.*data| +puppet/compute = 80_chars:forgive |spec/gcompute_backend_service_provider_spec.rb|group.*=>.*resource(instance| +puppet/container = 80_chars:forgive |spec/gcontainer_cluster_provider_spec.rb|cluster.*=>.*cluster_ca.*data| +puppet/logging = compile:skip +puppet/sql = 80_chars:forgive |spec/gsql_instance_provider_spec.rb|service_account_email_address => .*address.*| +puppet/sql = 80_chars:forgive |spec/gsql_instance_provider_spec.rb|client_certificate.*=> '.*certificate.*data.*| +puppet/sql = 80_chars:forgive |spec/gsql_instance_provider_spec.rb|serviceAccountEmailAddress.*=> '.*service.*data.*| +puppet/storage = 80_chars:forgive |spec/gstorage_bucket_provider_spec.rb|matches_storage_class.*=>| +# vim:tw=120: diff --git a/products/pubsub/api.yaml b/products/pubsub/api.yaml new file mode 100644 index 000000000000..efa41039b9e0 --- /dev/null +++ b/products/pubsub/api.yaml @@ -0,0 +1,87 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: Google Cloud Pub/Sub +prefix: gpubsub +base_url: https://pubsub.googleapis.com/v1/ +scopes: + - https://www.googleapis.com/auth/pubsub +objects: + - !ruby/object:Api::Resource + name: 'Topic' + base_url: projects/{{project}}/topics + create_verb: :PUT + description: | + A named resource to which messages are sent by publishers. + exports: + - name + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: 'Name of the topic.' + transport: !ruby/object:Api::Resource::Transport + decoder: decode_request + - !ruby/object:Api::Resource + name: 'Subscription' + base_url: projects/{{project}}/subscriptions + create_verb: :PUT + description: | + A named resource representing the stream of messages from a single, + specific topic, to be delivered to the subscribing application. + transport: !ruby/object:Api::Resource::Transport + decoder: decode_request + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: 'Name of the subscription.' + - !ruby/object:Api::Type::ResourceRef + name: 'topic' + resource: 'Topic' + imports: 'name' + description: | + A reference to a Topic resource. + - !ruby/object:Api::Type::NestedObject + name: 'pushConfig' + description: | + If push delivery is used with this subscription, this field is used to + configure it. An empty pushConfig signifies that the subscriber will + pull and ack messages using API methods. + properties: + - !ruby/object:Api::Type::String + name: 'pushEndpoint' + description: | + A URL locating the endpoint to which messages should be pushed. + For example, a Webhook endpoint might use + "https://example.com/push". + - !ruby/object:Api::Type::Integer + name: 'ackDeadlineSeconds' + description: | + This value is the maximum time after a subscriber receives a message + before the subscriber should acknowledge the message. After message + delivery but before the ack deadline expires and before the message is + acknowledged, it is an outstanding message and will not be delivered + again during that time (on a best-effort basis). + + For pull subscriptions, this value is used as the initial value for + the ack deadline. To override this value for a given message, call + subscriptions.modifyAckDeadline with the corresponding ackId if using + pull. The minimum custom deadline you can specify is 10 seconds. The + maximum custom deadline you can specify is 600 seconds (10 minutes). + If this parameter is 0, a default value of 10 seconds is used. + + For push delivery, this value is also used to set the request timeout + for the call to the push endpoint. + + If the subscriber never acknowledges the message, the Pub/Sub system + will eventually redeliver the message. diff --git a/products/pubsub/chef-e2e.yaml b/products/pubsub/chef-e2e.yaml new file mode 100644 index 000000000000..6db5a6d01ce9 --- /dev/null +++ b/products/pubsub/chef-e2e.yaml @@ -0,0 +1,37 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- !ruby/object:Chef::Tester + product: 'Pubsub' + tests: + - !ruby/object:Chef::StandardTest + name: 'Topic' + module: 'gpubsub' + verifiers: + - phase: ALL + command: | + gcloud beta pubsub topics list + --project=google.com:graphite-playground + | grep 'topic: projects/.*/chef-e2e-conversation-1' + - !ruby/object:Chef::StandardTest + name: 'Subscription' + module: 'gpubsub' + affected_count: 1 + resource_count: 3 + verifiers: + - phase: ALL + command: | + gcloud beta pubsub subscriptions list + --project=google.com:graphite-playground + | grep "chef-e2e-subscription-1" + | grep "chef-e2e-conversation-1" diff --git a/products/pubsub/chef.yaml b/products/pubsub/chef.yaml new file mode 100644 index 000000000000..0c9a1f644690 --- /dev/null +++ b/products/pubsub/chef.yaml @@ -0,0 +1,135 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Chef::Config +manifest: !ruby/object:Provider::Chef::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/chef-google-pubsub' + issues: 'https://github.com/GoogleCloudPlatform/chef-google-pubsub/issues' + summary: 'A Chef cookbook to manage Google Pubsub Engine resources' + description: | + This cookbook provides the built-in types and services for Chef to manage + Google Pubsub resources, as native Chef types. + depends: + - !ruby/object:Provider::Config::Requirements + name: 'google-gauth' + versions: '< 0.2.0' + operating_systems: +<%= indent(include('provider/chef/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + Subscription: + flush: + # TODO(nelsonjr): Implement calling custom methods to update pushConfig. + raise 'Subscription cannot be edited.' + # TODO(alexstephen): Remove when b/66233673 fixed. + self_link: | + URI.join( + 'https://pubsub.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/subscriptions/{{name}}', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + # TODO(alexstephen): Remove when b/66233673 fixed. + collection: | + URI.join( + 'https://pubsub.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/subscriptions', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + resource_to_request_patch: | + # Conform with gRPC/HTTP topic name + request[:topic] = ['projects', new_resource.project, 'topics', + new_resource.topic].join('/') + request[:name] = ['projects', new_resource.project, 'subscriptions', + new_resource.s_label].join('/') + provider_helpers: + include: + - 'products/pubsub/helpers/provider_subscription.rb' + Topic: + resource_to_request_patch: | + # Conform with gRPC/HTTP topic name + request[:name] = ['projects', new_resource.project, 'topics', + new_resource.t_label].join('/') + # TODO(alexstephen): Remove when b/66233673 fixed. + self_link: | + URI.join( + 'https://pubsub.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/topics/{{name}}', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + # TODO(alexstephen): Remove when b/66233673 fixed. + collection: | + URI.join( + 'https://pubsub.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/topics', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + provider_helpers: + include: + - 'products/pubsub/helpers/provider_topic.rb' +examples: !ruby/object:Api::Resource::HashArray + Subscription: + - subscription.rb + - delete_subscription.rb + Topic: + - topic.rb + - delete_topic.rb +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/chef/common~copy.yaml'), 4) %> + compile: +<%= indent(include('provider/chef/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/chef/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +tests: !ruby/object:Api::Resource::HashArray +<%= indent(include('products/pubsub/test.yaml'), 2) %> +test_data: !ruby/object:Provider::Config::TestData + network: !ruby/object:Api::Resource::HashArray + Subscription: + - success1~title + - success1~name + - success2~title + - success2~name + - success3~title + - success3~name + Topic: + - success1~title + - success1~name + - success2~title + - success2~name + - success3~title + - success3~name +style: + - !ruby/object:Provider::Config::StyleException + name: resources/topic.rb + pinpoints: + - class: Google::GPUBSUB::Topic + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/subscription.rb + pinpoints: + - class: Google::GPUBSUB::Subscription + exceptions: + - Metrics/ClassLength diff --git a/products/pubsub/example.yaml b/products/pubsub/example.yaml new file mode 100644 index 000000000000..cd961ef49122 --- /dev/null +++ b/products/pubsub/example.yaml @@ -0,0 +1,41 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Example::Config +# This is where custom code would be defined eventually. +objects: !ruby/object:Api::Resource::HashArray::NONE + # All classes need at least one attribute for YAML parsing because Ruby. + reason: 'Not needed' +# This is for a list of example files. +examples: !ruby/object:Api::Resource::HashArray::NONE + reason: 'Not needed' +# This is for copying files over +files: !ruby/object:Provider::Config::Files + # All of these files will be copied verbatim. + copy: + 'copied_file': 'templates/example/copied_file' + # These files have templating (ERB) code that will be run. + # This is usually to add licensing info, autogeneration notices, etc. + compile: + 'compiled_file': 'templates/example/compiled_file' +# This is for custom testing code. All of our tests follow a specific pattern +# that sometimes needs to be deviated from. We're working towards a world where +# these handwritten tests would be unnecessary in many cases (custom types). +tests: !ruby/object:Api::Resource::HashArray::NONE + reason: 'Not needed.' +# This would be for custom network responses. Tests work by running some block +# of autogenerated Chef/Puppet code and then verifying the network calls. +# The network call verifications are automatically generated, but can be +# overriden. +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'Test Data will be automatically generated' diff --git a/products/pubsub/files/examples~cookbook~delete_subscription.rb b/products/pubsub/files/examples~cookbook~delete_subscription.rb new file mode 100644 index 000000000000..14abf38f6ee2 --- /dev/null +++ b/products/pubsub/files/examples~cookbook~delete_subscription.rb @@ -0,0 +1,34 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gpubsub_topic <%= example_resource_name('conversation-1') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end # name == README.md -%> +gpubsub_subscription <%= example_resource_name('subscription-1') -%> do + action :delete + topic <%= example_resource_name('conversation-1') %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/pubsub/files/examples~cookbook~delete_topic.rb b/products/pubsub/files/examples~cookbook~delete_topic.rb new file mode 100644 index 000000000000..e75e0d8eaa14 --- /dev/null +++ b/products/pubsub/files/examples~cookbook~delete_topic.rb @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end # name == README.md -%> +gpubsub_topic <%= example_resource_name('conversation-1') -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/pubsub/files/examples~cookbook~readme.rb b/products/pubsub/files/examples~cookbook~readme.rb new file mode 100644 index 000000000000..c1b704756ded --- /dev/null +++ b/products/pubsub/files/examples~cookbook~readme.rb @@ -0,0 +1,37 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> +gpubsub_topic 'conversation-1' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end # name == README.md -%> +gpubsub_subscription 'subscription-1' do + action :create + topic 'conversation-1' + push_config( + push_endpoint: 'https://myapp.graphite.cloudnativeapp.com/webhook/sub1' + ) + ack_deadline_seconds 300 + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/pubsub/files/examples~cookbook~subscription.rb b/products/pubsub/files/examples~cookbook~subscription.rb new file mode 100644 index 000000000000..1470f01d3c13 --- /dev/null +++ b/products/pubsub/files/examples~cookbook~subscription.rb @@ -0,0 +1,38 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gpubsub_topic <%= example_resource_name('conversation-1') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% end # name == README.md -%> +gpubsub_subscription <%= example_resource_name('subscription-1') -%> do + action :create + topic <%= example_resource_name('conversation-1') %> + push_config ( + push_endpoint: 'https://myapp.graphite.cloudnativeapp.com/webhook/sub1' + ) + ack_deadline_seconds 300 + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/pubsub/files/examples~cookbook~topic.rb b/products/pubsub/files/examples~cookbook~topic.rb new file mode 100644 index 000000000000..368e0ff089c6 --- /dev/null +++ b/products/pubsub/files/examples~cookbook~topic.rb @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end # name == README.md -%> +gpubsub_topic <%= example_resource_name('conversation-1') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/pubsub/files/examples~delete_subscription.pp b/products/pubsub/files/examples~delete_subscription.pp new file mode 100644 index 000000000000..4e6d1f5db22d --- /dev/null +++ b/products/pubsub/files/examples~delete_subscription.pp @@ -0,0 +1,34 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gpubsub_topic { <%= example_resource_name('conversation-1') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gpubsub_subscription { <%= example_resource_name('subscription-1') -%>: + ensure => absent, + topic => 'conversation-1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/pubsub/files/examples~delete_topic.pp b/products/pubsub/files/examples~delete_topic.pp new file mode 100644 index 000000000000..5c472fc0404a --- /dev/null +++ b/products/pubsub/files/examples~delete_topic.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gpubsub_topic { <%= example_resource_name('conversation-1') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/pubsub/files/examples~subscription.pp b/products/pubsub/files/examples~subscription.pp new file mode 100644 index 000000000000..161aff660fee --- /dev/null +++ b/products/pubsub/files/examples~subscription.pp @@ -0,0 +1,38 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gpubsub_topic { <%= example_resource_name('conversation-1') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gpubsub_subscription { <%= example_resource_name('subscription-1') -%>: + ensure => present, + topic => <%= example_resource_name('conversation-1') -%>, + push_config => { + push_endpoint => 'https://myapp.graphite.cloudnativeapp.com/webhook/sub1', + }, + ack_deadline_seconds => 300, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/pubsub/files/examples~topic.pp b/products/pubsub/files/examples~topic.pp new file mode 100644 index 000000000000..44be3a95be50 --- /dev/null +++ b/products/pubsub/files/examples~topic.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gpubsub_topic { <%= example_resource_name('conversation-1') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/pubsub/files/spec~subscription~success1~name.yaml b/products/pubsub/files/spec~subscription~success1~name.yaml new file mode 100644 index 000000000000..d16f4f15ae3a --- /dev/null +++ b/products/pubsub/files/spec~subscription~success1~name.yaml @@ -0,0 +1,34 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: test name#0 data +ackDeadlineSeconds: 1733817478 +project: "'test project#0 data'" +pushConfig: + pushEndpoint: test push_endpoint#0 data +topic: test name#0 data diff --git a/products/pubsub/files/spec~subscription~success1~title.yaml b/products/pubsub/files/spec~subscription~success1~title.yaml new file mode 100644 index 000000000000..a96391242938 --- /dev/null +++ b/products/pubsub/files/spec~subscription~success1~title.yaml @@ -0,0 +1,34 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: title0 +ackDeadlineSeconds: 1733817478 +project: "'test project#0 data'" +pushConfig: + pushEndpoint: test push_endpoint#0 data +topic: test name#0 data diff --git a/products/pubsub/files/spec~subscription~success2~name.yaml b/products/pubsub/files/spec~subscription~success2~name.yaml new file mode 100644 index 000000000000..290a68e5555e --- /dev/null +++ b/products/pubsub/files/spec~subscription~success2~name.yaml @@ -0,0 +1,34 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: test name#1 data +ackDeadlineSeconds: 3467634957 +project: "'test project#1 data'" +pushConfig: + pushEndpoint: test push_endpoint#1 data +topic: test name#1 data diff --git a/products/pubsub/files/spec~subscription~success2~title.yaml b/products/pubsub/files/spec~subscription~success2~title.yaml new file mode 100644 index 000000000000..57882d1d8ba3 --- /dev/null +++ b/products/pubsub/files/spec~subscription~success2~title.yaml @@ -0,0 +1,34 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: title1 +ackDeadlineSeconds: 3467634957 +project: "'test project#1 data'" +pushConfig: + pushEndpoint: test push_endpoint#1 data +topic: test name#1 data diff --git a/products/pubsub/files/spec~subscription~success3~name.yaml b/products/pubsub/files/spec~subscription~success3~name.yaml new file mode 100644 index 000000000000..3365b94f786e --- /dev/null +++ b/products/pubsub/files/spec~subscription~success3~name.yaml @@ -0,0 +1,34 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: test name#2 data +ackDeadlineSeconds: 5201452436 +project: "'test project#2 data'" +pushConfig: + pushEndpoint: test push_endpoint#2 data +topic: test name#2 data diff --git a/products/pubsub/files/spec~subscription~success3~title.yaml b/products/pubsub/files/spec~subscription~success3~title.yaml new file mode 100644 index 000000000000..41f23bac914b --- /dev/null +++ b/products/pubsub/files/spec~subscription~success3~title.yaml @@ -0,0 +1,34 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: title2 +ackDeadlineSeconds: 5201452436 +project: "'test project#2 data'" +pushConfig: + pushEndpoint: test push_endpoint#2 data +topic: test name#2 data diff --git a/products/pubsub/files/spec~topic~success1~name.yaml b/products/pubsub/files/spec~topic~success1~name.yaml new file mode 100644 index 000000000000..31da9479c8a5 --- /dev/null +++ b/products/pubsub/files/spec~topic~success1~name.yaml @@ -0,0 +1,30 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: test name#0 data +project: "'test project#0 data'" diff --git a/products/pubsub/files/spec~topic~success1~title.yaml b/products/pubsub/files/spec~topic~success1~title.yaml new file mode 100644 index 000000000000..20373fe42b8a --- /dev/null +++ b/products/pubsub/files/spec~topic~success1~title.yaml @@ -0,0 +1,30 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: title0 +project: "'test project#0 data'" diff --git a/products/pubsub/files/spec~topic~success2~name.yaml b/products/pubsub/files/spec~topic~success2~name.yaml new file mode 100644 index 000000000000..30853b263064 --- /dev/null +++ b/products/pubsub/files/spec~topic~success2~name.yaml @@ -0,0 +1,30 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: test name#1 data +project: "'test project#1 data'" diff --git a/products/pubsub/files/spec~topic~success2~title.yaml b/products/pubsub/files/spec~topic~success2~title.yaml new file mode 100644 index 000000000000..22dde6d58612 --- /dev/null +++ b/products/pubsub/files/spec~topic~success2~title.yaml @@ -0,0 +1,30 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: title1 +project: "'test project#1 data'" diff --git a/products/pubsub/files/spec~topic~success3~name.yaml b/products/pubsub/files/spec~topic~success3~name.yaml new file mode 100644 index 000000000000..23e2cb511f2e --- /dev/null +++ b/products/pubsub/files/spec~topic~success3~name.yaml @@ -0,0 +1,30 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: test name#2 data +project: "'test project#2 data'" diff --git a/products/pubsub/files/spec~topic~success3~title.yaml b/products/pubsub/files/spec~topic~success3~title.yaml new file mode 100644 index 000000000000..88c33c2a90e6 --- /dev/null +++ b/products/pubsub/files/spec~topic~success3~title.yaml @@ -0,0 +1,30 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: title2 +project: "'test project#2 data'" diff --git a/products/pubsub/helpers/api_gpubsub_topic.rb b/products/pubsub/helpers/api_gpubsub_topic.rb new file mode 100644 index 000000000000..f3d7b597fdfe --- /dev/null +++ b/products/pubsub/helpers/api_gpubsub_topic.rb @@ -0,0 +1,65 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/pubsub/network/post' + +module Google + module Pubsub + module Api + # A helper class to provide access to (some) Google Container Engine API. + class Topic + def initialize(topic, project, cred) + @topic = topic + @project = project + @cred = cred + end + + # TODO(nelsonjr): Implement this as gcontainer_node_pool { size } + # TODO(nelsonjr): Make this function wait for the operation to complete + # TODO(nelsonjr): Add error checking on response on this task + # (ditto on all Bolt tasks) + def publish(message, attributes) + message = { + messages: [{ + attributes: attributes, + data: Base64.encode64(message).strip + }] + } + + request = ::Google::Pubsub::Network::Post.new( + gtopic_publish, @cred, 'application/json', + message.to_json + ) + + response = JSON.parse(request.send.body) + raise Puppet::Error, response['error']['message'] if response['error'] + response + end + + private + + def gtopic_publish + URI.parse( + format( + '%s:%s', + Puppet::Type.type(:gpubsub_topic).provider(:google) + .self_link(name: @topic, project: @project, + cluster: @cluster), + 'publish' + ) + ) + end + end + end + end +end diff --git a/products/pubsub/helpers/provider_subscription.rb b/products/pubsub/helpers/provider_subscription.rb new file mode 100644 index 000000000000..912b5b4f0c66 --- /dev/null +++ b/products/pubsub/helpers/provider_subscription.rb @@ -0,0 +1,23 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def decode_request(response) + self.class.decode_request(response) +end + +def self.decode_request(response) + resp = JSON.parse(response.body) + resp['name'] = resp['name'].split('/').last if resp['name'] + resp['topic'] = resp['topic'].split('/').last if resp['topic'] + resp +end diff --git a/products/pubsub/helpers/provider_topic.rb b/products/pubsub/helpers/provider_topic.rb new file mode 100644 index 000000000000..6557fcce8e61 --- /dev/null +++ b/products/pubsub/helpers/provider_topic.rb @@ -0,0 +1,22 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +def decode_request(response) + self.class.decode_request(response) +end + +def self.decode_request(response) + resp = JSON.parse(response.body) + resp['name'] = resp['name'].split('/').last if resp['name'] + resp +end diff --git a/products/pubsub/puppet-e2e.yaml b/products/pubsub/puppet-e2e.yaml new file mode 100644 index 000000000000..4af2c0dff294 --- /dev/null +++ b/products/pubsub/puppet-e2e.yaml @@ -0,0 +1,38 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Puppet::Tester + product: 'Pubsub' + tests: + - !ruby/object:Puppet::StandardTest + name: 'Topic' + verifiers: + - phase: ALL + command: | + gcloud beta pubsub topics list + --project=google.com:graphite-playground + | grep 'topic: projects/.*/puppet-e2e-conversation-1' + - !ruby/object:Puppet::StandardTest + name: 'Subscription' + verifiers: + - phase: ALL + command: | + gcloud beta pubsub subscriptions list + --project=google.com:graphite-playground + | grep "puppet-e2e-subscription-1" + | grep "puppet-e2e-conversation-1" diff --git a/products/pubsub/puppet.yaml b/products/pubsub/puppet.yaml new file mode 100644 index 000000000000..7385864dff11 --- /dev/null +++ b/products/pubsub/puppet.yaml @@ -0,0 +1,185 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/puppet-google-pubsub.git' + homepage: 'https://github.com/GoogleCloudPlatform/puppet-google-pubsub' + issues: + 'https://github.com/GoogleCloudPlatform/puppet-google-pubsub/issues' + summary: 'A Puppet module to manage Google Cloud Pub/Sub resources' + tags: + - google + - cloud + - pubsub + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google/gauth' + versions: '>= 0.2.0 < 0.3.0' + operating_systems: +<%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + Subscription: + flush: + # TODO(nelsonjr): Implement calling custom methods to update pushConfig. + raise 'Subscription cannot be edited.' + # TODO(alexstephen): Remove when b/66233673 fixed. + self_link: | + URI.join( + 'https://pubsub.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/subscriptions/{{name}}', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + # TODO(alexstephen): Remove when b/66233673 fixed. + collection: | + URI.join( + 'https://pubsub.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/subscriptions', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + resource_to_request_patch: | + # Conform with gRPC/HTTP topic name + request[:topic] = ['projects', @resource[:project], 'topics', + @resource[:topic]].join('/') + request[:name] = ['projects', @resource[:project], 'subscriptions', + @resource[:name]].join('/') + provider_helpers: + include: + - 'products/pubsub/helpers/provider_subscription.rb' + Topic: + resource_to_request_patch: | + # Conform with gRPC/HTTP topic name + request[:name] = ['projects', @resource[:project], 'topics', + @resource[:name]].join('/') + # TODO(alexstephen): Remove when b/66233673 fixed. + self_link: | + URI.join( + 'https://pubsub.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/topics/{{name}}', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + # TODO(alexstephen): Remove when b/66233673 fixed. + collection: | + URI.join( + 'https://pubsub.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/topics', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + provider_helpers: + include: + - 'products/pubsub/helpers/provider_topic.rb' +examples: !ruby/object:Api::Resource::HashArray + Subscription: + - subscription.pp + - delete_subscription.pp + Topic: + - topic.pp + - delete_topic.pp +bolt_tasks: + - !ruby/object:Provider::Puppet::BoltTask + name: 'publish' + description: 'Publish a message to a specific topic.' + style: :ruby + input: :stdin + arguments: + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: topic + type: Api::Type::String + description: 'The name of the topic to send the message to.' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: attributes + type: Hash + default: {} + description: 'Optional attributes in { name => val } format' + required: false + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: message + type: Api::Type::String + description: 'The message to be published.' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: project + type: Api::Type::String + description: 'the project name where the cluster is hosted' + required : true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: credential + type: Api::Type::String + description: 'Path to a service account credentials file' + required: true + requires: + - google/auth/gauth_credential + - base64 + - google/pubsub/network/post + - google/pubsub/api/gpubsub_topic + code: | + cred = Google::Auth::GAuthCredential \ + .serviceaccount_for_function(credential, PUBSUB_ADM_SCOPES) + pubsub_message = Google::Pubsub::Api::Topic \ + .new(topic, project, cred) + + begin + resp = pubsub_message.publish(message, attributes) + puts({ status: 'success', id: resp['messageIds'][0] }.to_json) + exit 0 + rescue Puppet::Error => e + puts({ status: 'failure', error: e }.to_json) + exit 1 + end +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/puppet/common~copy.yaml'), 4) %> + compile: + lib/google/object_store.rb: google/object_store.rb + lib/google/pubsub/api/gpubsub_topic.rb: + products/pubsub/helpers/api_gpubsub_topic.rb +<%= indent(include('provider/puppet/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/puppet/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +tests: !ruby/object:Api::Resource::HashArray +<%= indent(include('products/pubsub/test.yaml'), 2) %> +test_data: !ruby/object:Provider::Config::TestData + network: !ruby/object:Api::Resource::HashArray + Subscription: + - success1~title + - success1~name + - success2~title + - success2~name + - success3~title + - success3~name + Topic: + - success1~title + - success1~name + - success2~title + - success2~name + - success3~title + - success3~name +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-10-10T06:00:00-0700 + general: 'Initial release' diff --git a/products/pubsub/test.yaml b/products/pubsub/test.yaml new file mode 100644 index 000000000000..8e3f775a7a34 --- /dev/null +++ b/products/pubsub/test.yaml @@ -0,0 +1,70 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Topic: + present: + not_exist: + success: + title_and_name: + before: | + expect_network_get_failed 1, name: 'test name#0 data' + expect_network_create \\ + 1, + name: 'projects/test project#0 data/topics/test name#0 data' + title_eq_name: + before: | + expect_network_get_failed 1, name: 'title0' + expect_network_create \\ + 1, + { + 'name' => 'projects/test project#0 data/topics/title0' + }, + name: 'title0' +Subscription: + present: + not_exist: + success: + title_and_name: + before: | + expect_network_get_failed 1, name: 'test name#0 data' + expect_network_create \\ + 1, + { + 'name' => + 'projects/test project#0 data/subscriptions/test name#0 data', + 'topic' => + 'projects/test project#0 data/topics/test name#0 data', + 'pushConfig' => { + 'pushEndpoint' => 'test push_endpoint#0 data' + }, + 'ackDeadlineSeconds' => 1_733_817_478 + }, + name: 'test name#0 data' + expect_network_get_success_topic 1, name: 'test name#0 data' + title_eq_name: + before: | + expect_network_get_failed 1, name: 'title0' + expect_network_create \\ + 1, + { + 'name' => + 'projects/test project#0 data/subscriptions/title0', + 'topic' => + 'projects/test project#0 data/topics/test name#0 data', + 'pushConfig' => { + 'pushEndpoint' => 'test push_endpoint#0 data' + }, + 'ackDeadlineSeconds' => 1_733_817_478 + }, + name: 'title0' + expect_network_get_success_topic 1, name: 'test name#0 data' diff --git a/products/resourcemanager/api.yaml b/products/resourcemanager/api.yaml new file mode 100644 index 000000000000..1ae0e8d85ebb --- /dev/null +++ b/products/resourcemanager/api.yaml @@ -0,0 +1,105 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: Google Cloud Resource Manager +prefix: gresourcemanager +base_url: https://cloudresourcemanager.googleapis.com/v1/ +scopes: + # All access is needed to create projects. + - https://www.googleapis.com/auth/cloud-platform +objects: + - !ruby/object:Api::Resource + name: 'Project' + base_url: projects + self_link: | + projects/{{project_id}} + description: | + Represents a GCP Project. A project is a container for ACLs, APIs, App + Engine Apps, VMs, and other Google Cloud Platform resources. +# async: !ruby/object:Api::Async +# operation: !ruby/object:Api::Async::Operation +# path: 'name' +# base_url: 'projects/{{project}}/zones/{{zone}}/operations/{{op_id}}' +# wait_ms: 1000 +# result: !ruby/object:Api::Async::Result +# path: 'targetLink' +# status: !ruby/object:Api::Async::Status +# path: 'done' +# complete: true +# allowed: +# - true +# - false +# error: !ruby/object:Api::Async::Error +# path: 'error' +# message: 'message' + properties: + - !ruby/object:Api::Type::Integer + name: 'projectNumber' + description: Number uniquely identifying the project. + output: true + - !ruby/object:Api::Type::Enum + name: 'lifecycleState' + description: The Project lifecycle state. + output: true + values: + - :LIFECYCLE_STATE_UNSPECIFIED + - :ACTIVE + - :DELETE_REQUESTED + - :DELETE_IN_PROGRESS + - !ruby/object:Api::Type::String + name: 'project_name' + description: | + The user-assigned display name of the Project. It must be 4 to 30 + characters. Allowed characters are: lowercase and uppercase letters, + numbers, hyphen, single-quote, double-quote, space, and exclamation + point. + field: name + - !ruby/object:Api::Type::String + name: 'projectId' + description: | + The unique, user-assigned ID of the Project. It must be 6 to 30 + lowercase letters, digits, or hyphens. It must start with a letter. + Trailing hyphens are prohibited. + - !ruby/object:Api::Type::Time + name: 'createTime' + description: 'Time of creation' + output: true + - !ruby/object:Api::Type::NameValues + name: 'labels' + key_type: Api::Type::String + value_type: Api::Type::String + description: | + The labels associated with this Project. + + Label keys must be between 1 and 63 characters long and must conform + to the following regular expression: [a-z]([-a-z0-9]*[a-z0-9])?. + + Label values must be between 0 and 63 characters long and must + conform to the regular expression ([a-z]([-a-z0-9]*[a-z0-9])?)?. + + No more than 256 labels can be associated with a given resource. + + Clients should store labels in a representation such as JSON that + does not depend on specific characters being disallowed + - !ruby/object:Api::Type::NestedObject + name: 'parent' + description: A parent organization + properties: + - !ruby/object:Api::Type::String + name: 'type' + description: Must be organization. + - !ruby/object:Api::Type::String + name: 'id' + description: Id of the organization + diff --git a/products/resourcemanager/chef.yaml b/products/resourcemanager/chef.yaml new file mode 100644 index 000000000000..8f022c97bfe2 --- /dev/null +++ b/products/resourcemanager/chef.yaml @@ -0,0 +1,72 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Chef::Config +manifest: !ruby/object:Provider::Chef::Manifest + version: '0.1.1' + source: 'https://github.com/GoogleCloudPlatform/chef-google-resourcemanager' + issues: 'https://github.com/GoogleCloudPlatform/chef-google-resourcemanager/issues' + summary: 'A Chef cookbook to manage Google Cloud ResourceManager resources' + description: | + This cookbook provides the built-in types and services for Chef to manage + Google Cloud Resource Manager resources, as native Chef types. + depends: + - !ruby/object:Provider::Config::Requirements + name: 'google-gauth' + versions: '< 0.2.0' + operating_systems: +<%= indent(include('provider/chef/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + Project: + return_if_object: | + # rubocop:disable Metrics/CyclomaticComplexity + def self.return_if_object(response) + raise "Bad response: #{response}" \ + unless response.is_a?(Net::HTTPResponse) + return if response.is_a?(Net::HTTPNotFound) + return if response.is_a?(Net::HTTPNoContent) + # TODO(nelsonjr): Remove return of Net::HTTPForbidden from + # return_if_object once Cloud SQL bug http://b/62635365 is resolved. + # Currently the API returns 403 for objects that do not exist, even when + # the user has access to the project. This is being changed to return + # 404 as it is supposed to be. Once 404 is the correct response the + # temporary workaround should be removed. + return if response.is_a?(Net::HTTPForbidden) + result = JSON.parse(response.body) + raise_if_errors result, %w[error errors], 'message' + raise "Bad response: #{response}" unless response.is_a?(Net::HTTPOK) + result + end + # rubocop:enable Metrics/CyclomaticComplexity + + def return_if_object(response) + self.class.return_if_object(response) + end +examples: !ruby/object:Api::Resource::HashArray + Project: + - project.rb + - delete_project.rb +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/chef/common~copy.yaml'), 4) %> + compile: +<%= indent(include('provider/chef/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/chef/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'Network data will be autogenerated' +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-10-17T09:00:00-0700 + general: 'Initial release' diff --git a/products/resourcemanager/files/examples~cookbook~delete_project.rb b/products/resourcemanager/files/examples~cookbook~delete_project.rb new file mode 100644 index 000000000000..f9fc4c76a98e --- /dev/null +++ b/products/resourcemanager/files/examples~cookbook~delete_project.rb @@ -0,0 +1,36 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gauth_credential 'mycred' do + action :defaultuseraccount + scopes [ + 'https://www.googleapis.com/auth/cloud-platform' + ] +end + +gresourcemanager_project 'sample-testproj' do + action :delete + project_id ENV['project_id'] + project_name 'My Sample Project' + credential 'mycred' +end diff --git a/products/resourcemanager/files/examples~cookbook~project.rb b/products/resourcemanager/files/examples~cookbook~project.rb new file mode 100644 index 000000000000..7d26a09cccbe --- /dev/null +++ b/products/resourcemanager/files/examples~cookbook~project.rb @@ -0,0 +1,36 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gauth_credential 'mycred' do + action :defaultuseraccount + scopes [ + 'https://www.googleapis.com/auth/cloud-platform' + ] +end + +gresourcemanager_project 'sample-testproj' do + action :create + project_id ENV['project_id'] + project_name 'My Sample Project' + credential 'mycred' +end diff --git a/products/resourcemanager/files/examples~cookbook~readme.rb b/products/resourcemanager/files/examples~cookbook~readme.rb new file mode 100644 index 000000000000..90cc47351615 --- /dev/null +++ b/products/resourcemanager/files/examples~cookbook~readme.rb @@ -0,0 +1,89 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% end -%> +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gcompute_zone 'us-west1-a' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_disk 'instance-test-os-1' do + action :create + source_image 'projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts' + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_network 'mynetwork-test' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_region 'us-west1' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_address 'instance-test-ip' do + action :create + region 'us-west1' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_machine_type 'n1-standard-1' do + action :create + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end + +gcompute_instance 'instance-test' do + action :create + machine_type 'n1-standard-1' + disks [ + { + boot: true, + auto_delete: true, + source: 'instance-test-os-1' + } + ] + network_interfaces [ + { + network: 'mynetwork-test', + access_configs: [ + { + name: 'External NAT', + nat_ip: 'instance-test-ip', + type: 'ONE_TO_ONE_NAT' + } + ] + } + ] + zone 'us-west1-a' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/resourcemanager/files/examples~delete_project.pp b/products/resourcemanager/files/examples~delete_project.pp new file mode 100644 index 000000000000..3f7df44bfb27 --- /dev/null +++ b/products/resourcemanager/files/examples~delete_project.pp @@ -0,0 +1,33 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% end # name == README.md -%> +gauth_credential { 'mycred': + provider => defaultuseraccount, + scopes => [ + 'https://www.googleapis.com/auth/cloud-platform', + ], +} + +gresourcemanager_project { 'sample-testproj': + ensure => absent, + project_id => $project_id, + project_name => 'My Sample Project', + credential => 'mycred', +} diff --git a/products/resourcemanager/files/examples~project.pp b/products/resourcemanager/files/examples~project.pp new file mode 100644 index 000000000000..5331afa7c265 --- /dev/null +++ b/products/resourcemanager/files/examples~project.pp @@ -0,0 +1,33 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% end # name == README.md -%> +gauth_credential { 'mycred': + provider => defaultuseraccount, + scopes => [ + 'https://www.googleapis.com/auth/cloud-platform', + ], +} + +gresourcemanager_project { 'sample-testproj': + ensure => present, + project_id => $project_id, + project_name => 'My Sample Project', + credential => 'mycred', +} diff --git a/products/resourcemanager/puppet.yaml b/products/resourcemanager/puppet.yaml new file mode 100644 index 000000000000..dbf44f0fba9a --- /dev/null +++ b/products/resourcemanager/puppet.yaml @@ -0,0 +1,76 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/puppet-google-resourcemanager' + homepage: + 'https://github.com/GoogleCloudPlatform/puppet-google-resourcemanager' + issues: + 'https://github.com/GoogleCloudPlatform/puppet-google-resourcemanager/issues' + summary: 'A Puppet module to manage GCP Projects' + tags: + - google + - cloud + - resourcemanager + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google/gauth' + versions: '< 0.2.0' + operating_systems: +<%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + Project: + return_if_object: | + # rubocop:disable Metrics/CyclomaticComplexity + def self.return_if_object(response) + raise "Bad response: #{response}" \ + unless response.is_a?(Net::HTTPResponse) + return if response.is_a?(Net::HTTPNotFound) + return if response.is_a?(Net::HTTPNoContent) + # TODO(nelsonjr): Remove return of Net::HTTPForbidden from + # return_if_object once Cloud SQL bug http://b/62635365 is resolved. + # Currently the API returns 403 for objects that do not exist, even when + # the user has access to the project. This is being changed to return + # 404 as it is supposed to be. Once 404 is the correct response the + # temporary workaround should be removed. + return if response.is_a?(Net::HTTPForbidden) + result = JSON.parse(response.body) + raise_if_errors result, %w[error errors], 'message' + raise "Bad response: #{response}" unless response.is_a?(Net::HTTPOK) + result + end + # rubocop:enable Metrics/CyclomaticComplexity + + def return_if_object(response) + self.class.return_if_object(response) + end +examples: !ruby/object:Api::Resource::HashArray + Project: + - project.pp + - delete_project.pp +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/puppet/common~copy.yaml'), 4) %> + compile: +<%= indent(include('provider/puppet/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/puppet/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'Network data will be autogenerated' +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-10-17T09:00:00-0700 + general: 'Initial release' diff --git a/products/spanner/api.yaml b/products/spanner/api.yaml new file mode 100644 index 000000000000..049bb219cc03 --- /dev/null +++ b/products/spanner/api.yaml @@ -0,0 +1,139 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: Google Spanner +prefix: gspanner +base_url: https://spanner.googleapis.com/v1/ +scopes: + - https://www.googleapis.com/auth/spanner.admin +objects: + - !ruby/object:Api::Resource + name: 'InstanceConfig' + base_url: 'projects/{{project}}/instanceConfigs' + description: | + A possible configuration for a Cloud Spanner instance. Configurations + define the geographic placement of nodes and their replication. + exports: + - !ruby/object:Api::Type::FetchedExternal + name: name + virtual: true + transport: !ruby/object:Api::Resource::Transport + encoder: encode_request + decoder: decode_response + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: | + A unique identifier for the instance configuration. Values are of the + form projects//instanceConfigs/[a-z][-a-z0-9]* + - !ruby/object:Api::Type::String + name: 'displayName' + description: | + The name of this instance configuration as it appears in UIs. + output: true + - !ruby/object:Api::Resource + name: 'Instance' + base_url: projects/{{project}}/instances + description: | + An isolated set of Cloud Spanner resources on which databases can be + hosted. + exports: + - name + transport: !ruby/object:Api::Resource::Transport + decoder: decode_response + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: | + A unique identifier for the instance, which cannot be changed after + the instance is created. Values are of the form + projects//instances/[a-z][-a-z0-9]*[a-z0-9]. The final + segment of the name must be between 6 and 30 characters in length. + - !ruby/object:Api::Type::ResourceRef + name: 'config' + resource: 'InstanceConfig' + imports: 'name' + description: 'A reference to the instance configuration.' + - !ruby/object:Api::Type::String + name: 'displayName' + description: | + The descriptive name for this instance as it appears in UIs. Must be + unique per project and between 4 and 30 characters in length. + required: true + - !ruby/object:Api::Type::Integer + name: 'nodeCount' + description: 'The number of nodes allocated to this instance.' + # 'state' not suitable for state convergeance. + - !ruby/object:Api::Type::NameValues + name: 'labels' + key_type: Api::Type::String + value_type: Api::Type::String + description: | + Cloud Labels are a flexible and lightweight mechanism for organizing + cloud resources into groups that reflect a customer's organizational + needs and deployment strategies. Cloud Labels can be used to filter + collections of resources. They can be used to control how resource + metrics are aggregated. And they can be used as arguments to policy + management rules (e.g. route, firewall, load balancing, etc.). + + Label keys must be between 1 and 63 characters long and must conform + to the following regular expression: [a-z]([-a-z0-9]*[a-z0-9])?. + Label values must be between 0 and 63 characters long and must conform + to the regular expression ([a-z]([-a-z0-9]*[a-z0-9])?)?. + + No more than 64 labels can be associated with a given resource. + + See https://goo.gl/xmQnxf for more information on and examples of + labels. + + If you plan to use labels in your own code, please note that + additional characters may be allowed in the future. And so you are + advised to use an internal label representation, such as JSON, which + doesn't rely upon specific characters being disallowed. For example, + representing labels as the string: name + "_" + value would prove + problematic if we were to allow "_" in a future release. + + An object containing a list of "key": value pairs. + Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }. + - !ruby/object:Api::Resource + name: 'Database' + base_url: projects/{{project}}/instances/{{instance}}/databases + description: | + A Cloud Spanner Database which is hosted on a Spanner instance. + transport: !ruby/object:Api::Resource::Transport + decoder: decode_response + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'instance' + resource: 'Instance' + imports: 'name' + description: 'The instance to create the database on.' + required: true + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: | + A unique identifier for the instance, which cannot be changed after + the instance is created. Values are of the form + projects//instances/[a-z][-a-z0-9]*[a-z0-9]. The final + segment of the name must be between 6 and 30 characters in length. + - !ruby/object:Api::Type::Array + item_type: Api::Type::String + name: 'extraStatements' + description: | + An optional list of DDL statements to run inside the newly created + database. Statements can create tables, indexes, etc. These statements + execute atomically with the creation of the database: if there is an + error in any statement, the database is not created. + input: true diff --git a/products/spanner/async.yaml b/products/spanner/async.yaml new file mode 100644 index 000000000000..c7a1678986cc --- /dev/null +++ b/products/spanner/async.yaml @@ -0,0 +1,14 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Make all objects async diff --git a/products/spanner/files/examples~database.pp b/products/spanner/files/examples~database.pp new file mode 100644 index 000000000000..524b1964491b --- /dev/null +++ b/products/spanner/files/examples~database.pp @@ -0,0 +1,38 @@ +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gspanner_instance_config { 'regional-us-central1': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +gspanner_instance { <%= example_resource_name('my-spanner') -%>: + display_name => 'My Spanner Instance', + node_count => 2, + labels => [ + { + 'cost-center' => 'ti-1700004', + }, + ], + config => 'regional-us-central1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gspanner_database { <%= example_resource_name('webstore') -%>: + ensure => present, + extra_statements => [ + 'CREATE TABLE customers ( + customer_id INT64 NOT NULL, + last_name STRING(MAX) + ) PRIMARY KEY (customer_id)', + ], + instance => <%= example_resource_name('my-spanner') -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/spanner/files/examples~delete_instance.pp b/products/spanner/files/examples~delete_instance.pp new file mode 100644 index 000000000000..a3fd6f622bab --- /dev/null +++ b/products/spanner/files/examples~delete_instance.pp @@ -0,0 +1,13 @@ +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gspanner_instance { <%= example_resource_name('my-spanner') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/spanner/files/examples~instance.pp b/products/spanner/files/examples~instance.pp new file mode 100644 index 000000000000..c5fce6c207e9 --- /dev/null +++ b/products/spanner/files/examples~instance.pp @@ -0,0 +1,25 @@ +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gspanner_instance_config { 'regional-us-central1': + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% end # name == README.md -%> +gspanner_instance { <%= example_resource_name('my-spanner') -%>: + display_name => 'My Spanner Instance', + node_count => 2, + labels => [ + { + 'cost-center' => 'ti-1700004', + }, + ], + config => 'regional-us-central1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/spanner/files/examples~instance_config.pp b/products/spanner/files/examples~instance_config.pp new file mode 100644 index 000000000000..db24e1df418a --- /dev/null +++ b/products/spanner/files/examples~instance_config.pp @@ -0,0 +1,12 @@ +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gspanner_instance_config { 'regional-us-central1': + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/spanner/files/spec~instance~success1~name.yaml b/products/spanner/files/spec~instance~success1~name.yaml new file mode 100644 index 000000000000..a0cd4f1cfe3f --- /dev/null +++ b/products/spanner/files/spec~instance~success1~name.yaml @@ -0,0 +1,36 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: projects/test project#0 data/instances/test name#0 data +config: test name#0 data +displayName: test display_name#0 data +labels: + test labels#1 data: test labels#1 data + test labels#2 data: 6131251034 +nodeCount: 2502187088 +project: "'test project#0 data'" diff --git a/products/spanner/files/spec~instance~success1~title.yaml b/products/spanner/files/spec~instance~success1~title.yaml new file mode 100644 index 000000000000..5bfc2a94a864 --- /dev/null +++ b/products/spanner/files/spec~instance~success1~title.yaml @@ -0,0 +1,36 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: projects/test project#0 data/instances/title0 +config: test name#0 data +displayName: test display_name#0 data +labels: + test labels#1 data: test labels#1 data + test labels#2 data: 6131251034 +nodeCount: 2502187088 +project: "test project#0 data" diff --git a/products/spanner/files/spec~instance~success2~name.yaml b/products/spanner/files/spec~instance~success2~name.yaml new file mode 100644 index 000000000000..02e4f39e7c4b --- /dev/null +++ b/products/spanner/files/spec~instance~success2~name.yaml @@ -0,0 +1,37 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: projects/test project#1 data/instances/test name#1 data +config: test name#1 data +displayName: test display_name#1 data +labels: + test labels#2 data: test labels#2 data + test labels#3 data: 8175001379 + test labels#4 data: test labels#4 data +nodeCount: 5004374177 +project: "test project#1 data" diff --git a/products/spanner/files/spec~instance~success2~title.yaml b/products/spanner/files/spec~instance~success2~title.yaml new file mode 100644 index 000000000000..6d14c48da002 --- /dev/null +++ b/products/spanner/files/spec~instance~success2~title.yaml @@ -0,0 +1,37 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: projects/test project#1 data/instances/title1 +config: test name#1 data +displayName: test display_name#1 data +labels: + test labels#2 data: test labels#2 data + test labels#3 data: 8175001379 + test labels#4 data: test labels#4 data +nodeCount: 5004374177 +project: "test project#1 data" diff --git a/products/spanner/files/spec~instance~success3~name.yaml b/products/spanner/files/spec~instance~success3~name.yaml new file mode 100644 index 000000000000..5852cea49b3f --- /dev/null +++ b/products/spanner/files/spec~instance~success3~name.yaml @@ -0,0 +1,38 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: projects/test project#2 data/instances/test name#2 data +config: test name#2 data +displayName: test display_name#2 data +labels: + test labels#3 data: test labels#3 data + test labels#4 data: 10218751724 + test labels#5 data: test labels#5 data + test labels#6 data: 14306252413 +nodeCount: 7506561265 +project: "test project#2 data" diff --git a/products/spanner/files/spec~instance~success3~title.yaml b/products/spanner/files/spec~instance~success3~title.yaml new file mode 100644 index 000000000000..9913ac116d68 --- /dev/null +++ b/products/spanner/files/spec~instance~success3~title.yaml @@ -0,0 +1,38 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by puppet-codegen and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- + +--- +name: projects/test project#2 data/instances/title2 +config: test name#2 data +displayName: test display_name#2 data +labels: + test labels#3 data: test labels#3 data + test labels#4 data: 10218751724 + test labels#5 data: test labels#5 data + test labels#6 data: 14306252413 +nodeCount: 7506561265 +project: "test project#2 data" diff --git a/products/spanner/helpers/database_helpers.rb.erb b/products/spanner/helpers/database_helpers.rb.erb new file mode 100644 index 000000000000..7b48c6ee23ad --- /dev/null +++ b/products/spanner/helpers/database_helpers.rb.erb @@ -0,0 +1,13 @@ +def self.decode_response(response) + response = JSON.parse(response.body) + return response if response.empty? + # Don't alter if it's a async operation + return response if response['name'].include? '/operations/' + + response['name'] = response['name'].split('/').last + response +end + +def decode_response(response) + self.class.decode_response(response) +end diff --git a/products/spanner/helpers/instance_config_helpers.rb.erb b/products/spanner/helpers/instance_config_helpers.rb.erb new file mode 100644 index 000000000000..a394b6cad43d --- /dev/null +++ b/products/spanner/helpers/instance_config_helpers.rb.erb @@ -0,0 +1,15 @@ +def encode_request(request) + request['name'] = + "projects/#{resource[:project]}/instanceConfigs/#{resource[:name]}" + request.to_json +end + +def self.decode_response(response) + response = JSON.parse(response.body) + response['name'] = response['name'].split('/').last + response +end + +def decode_response(response) + self.class.decode_response(response) +end diff --git a/products/spanner/helpers/instance_helpers.rb.erb b/products/spanner/helpers/instance_helpers.rb.erb new file mode 100644 index 000000000000..125906c9f588 --- /dev/null +++ b/products/spanner/helpers/instance_helpers.rb.erb @@ -0,0 +1,39 @@ +def resource_to_create + instance = JSON.parse(resource_to_request) + instance['name'] = + "projects/#{resource[:project]}/instances/#{resource[:name]}" + instance['config'] = + "projects/#{resource[:project]}/instanceConfigs/#{resource[:config]}" + { + 'instanceId' => resource[:name], + 'instance' => instance + }.to_json +end + +def resource_to_update + instance = JSON.parse(resource_to_request) + instance['name'] = + "projects/#{resource[:project]}/instances/#{resource[:name]}" + instance['config'] = + "projects/#{resource[:project]}/instanceConfigs/#{resource[:config]}" + { + 'instance' => instance, +<% fields = object.properties.select { |p| !p.output }.map(&:name) -%> + 'fieldMask' => %w[<%= fields.join(' ') -%>].join(',') + }.to_json +end + +def decode_response(response) + self.class.decode_response(response) +end + +def self.decode_response(response) + response = JSON.parse(response.body) + return response if response.empty? + # Don't alter if it's a async operation + return response if response['name'].include? '/operations/' + + response['name'] = response['name'].split('/').last + response['config'] = response['config'].split('/').last + response +end diff --git a/products/spanner/puppet-e2e.yaml b/products/spanner/puppet-e2e.yaml new file mode 100644 index 000000000000..0be2467a1215 --- /dev/null +++ b/products/spanner/puppet-e2e.yaml @@ -0,0 +1,39 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Puppet::Tester + product: 'Spanner' + tests: + - !ruby/object:Puppet::StandardTest + name: 'Instance' + verifiers: + - phase: ALL + command: | + gcloud spanner instances describe + --project=google.com:graphite-playground + puppet-e2e-my-spanner + - !ruby/object:Puppet::StandardTest + name: 'Database' + delete: delete_instance.pp + verifiers: + - phase: ALL + command: | + gcloud spanner databases describe + --project=google.com:graphite-playground + puppet-e2e-webstore + --instance=puppet-e2e-my-spanner diff --git a/products/spanner/puppet.yaml b/products/spanner/puppet.yaml new file mode 100644 index 000000000000..a7b4efd13d8f --- /dev/null +++ b/products/spanner/puppet.yaml @@ -0,0 +1,136 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/puppet-google-spanner' + homepage: 'https://github.com/GoogleCloudPlatform/puppet-google-spanner' + issues: + 'https://github.com/GoogleCloudPlatform/puppet-google-spanner/issues' + summary: 'A Puppet module to manage Google Spanner resources.' + tags: + - google + - cloud + - spanner + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google/gauth' + versions: '>= 0.2.0 < 0.3.0' + operating_systems: +<%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + Database: + resource_to_request_patch: | + request[:create_statement] = "CREATE DATABASE `#{name}`" + collection: | + URI.join( + 'https://spanner.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/instances/{{instance}}/databases', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + self_link: | + URI.join( + 'https://spanner.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/instances/{{instance}}/databases/{{name}}', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + provider_helpers: + include: + - 'products/spanner/helpers/database_helpers.rb.erb' + Instance: + methods: + update: PATCH + self_link: | + URI.join( + 'https://spanner.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/instances/{{name}}', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + collection: | + URI.join( + 'https://spanner.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/instances', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + provider_helpers: + custom_create_resource: true + custom_update_resource: true + include: + - 'products/spanner/helpers/instance_helpers.rb.erb' + InstanceConfig: + collection: | + URI.join( + 'https://spanner.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/instanceConfigs', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + self_link: | + URI.join( + 'https://spanner.googleapis.com/v1/', + expand_variables( + 'projects/{{project}}/instanceConfigs/{{name}}', + data + ).split('/').map { |p| p.gsub('%3A', ':') } + .join('/') + ) + provider_helpers: + include: + - 'products/spanner/helpers/instance_config_helpers.rb.erb' +tests: !ruby/object:Api::Resource::HashArray +<%= indent(include('products/spanner/test.yaml'), 2) %> +examples: !ruby/object:Api::Resource::HashArray + Database: + - database.pp + InstanceConfig: + - instance_config.pp + Instance: + - delete_instance.pp + - instance.pp +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/puppet/common~copy.yaml'), 4) %> + compile: +<%= indent(include('provider/puppet/common~compile~before.yaml'), 4) %> + lib/google/object_store.rb: google/object_store.rb +<%= indent(include('provider/puppet/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData + network: !ruby/object:Api::Resource::HashArray + Instance: + - success1~name + - success1~title + - success2~name + - success2~title + - success3~name + - success3~title +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-10-10T06:00:00-0700 + general: 'Initial release' diff --git a/products/spanner/test.yaml b/products/spanner/test.yaml new file mode 100644 index 000000000000..df23f357e6e2 --- /dev/null +++ b/products/spanner/test.yaml @@ -0,0 +1,97 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +Database: + present: + not_exist: + success: + title_and_name: + before: | + expect_network_get_failed 1, + name: 'test name#0 data', + instance: 'test name#0 data' + expect_network_create \\ + 1, + { + 'name' => 'test name#0 data', + 'extraStatements' => %w[ww xx], + 'create_statement' => 'CREATE DATABASE `test name#0 data`' + }, + name: 'title0', + instance: 'test name#0 data' + expect_network_get_success_instance 1 + title_eq_name: + before: | + expect_network_get_failed 1, + name: 'title0', + instance: 'test name#0 data' + expect_network_create \\ + 1, + { + 'name' => 'title0', + 'extraStatements' => %w[ww xx], + 'create_statement' => 'CREATE DATABASE `title0`' + }, + name: 'title0', + instance: 'test name#0 data' + expect_network_get_success_instance 1 +Instance: + present: + not_exist: + success: + title_and_name: + before: | + expect_network_get_failed 1 + expect_network_create \\ + 1, + 'instanceId' => 'test name#0 data', + 'instance' => { + 'name' => [ + 'projects/test project#0 data', + 'instances/test name#0 data' + ].join('/'), + 'config' => [ + 'projects/test project#0 data', + 'instanceConfigs/test name#0 data' + ].join('/'), + 'displayName' => 'test display_name#0 data', + 'nodeCount' => 2_502_187_088, + 'labels' => { + 'test labels#1 data' => 'test labels#1 data', + 'test labels#2 data' => 6_131_251_034 + } + } + expect_network_get_success_instance_config 1 + title_eq_name: + before: | + expect_network_get_failed 1, name: 'title0' + expect_network_create \\ + 1, + 'instanceId' => 'title0', + 'instance' => { + 'name' => [ + 'projects/test project#0 data', + 'instances/title0' + ].join('/'), + 'config' => [ + 'projects/test project#0 data', + 'instanceConfigs/test name#0 data' + ].join('/'), + 'displayName' => 'test display_name#0 data', + 'nodeCount' => 2_502_187_088, + 'labels' => { + 'test labels#1 data' => 'test labels#1 data', + 'test labels#2 data' => 6_131_251_034 + } + } + expect_network_get_success_instance_config 1 diff --git a/products/sql/api.yaml b/products/sql/api.yaml new file mode 100644 index 000000000000..91d0b62985dd --- /dev/null +++ b/products/sql/api.yaml @@ -0,0 +1,531 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: Google Cloud SQL +prefix: gsql +base_url: https://www.googleapis.com/sql/v1beta4/ +scopes: + - https://www.googleapis.com/auth/sqlservice.admin +objects: + # 'BackupRun' is not idempotent and will not be covered. + # | - !ruby/object:Api::Resource + # | name: 'BackupRun' + # | kind: 'sql#backupRun' + # | base_url: projects/{{project}}/instances/{{instance}}/backupRuns + - !ruby/object:Api::Resource + name: 'Instance' + kind: 'sql#instance' + base_url: projects/{{project}}/instances + description: | + Represents a Cloud SQL instance. Cloud SQL instances are SQL databases + hosted in Google's cloud. The Instances resource provides methods for + common configuration and management tasks. + exports: + - name +<%= indent(compile('templates/async.yaml.erb'), 4) %> + properties: + - !ruby/object:Api::Type::Enum + name: 'backendType' + description: | + * FIRST_GEN: First Generation instance. MySQL only. + * SECOND_GEN: Second Generation instance or PostgreSQL instance. + * EXTERNAL: A database server that is not managed by Google. + values: + - :FIRST_GEN + - :SECOND_GEN + - :EXTERNAL + - !ruby/object:Api::Type::String + name: 'connectionName' + description: | + Connection name of the Cloud SQL instance used in connection strings. + # currentDiskSize (long) [DEPRECATED] + # | - !ruby/object:Api::Type::Long + # | name: 'currentDiskSize' + # | description: | + # | The current disk usage of the instance in bytes. This property has + # | been deprecated. Users should use the + # | "cloudsql.googleapis.com/database/disk/bytes_used" metric in Cloud + # | Monitoring API instead. Please see + # | https://groups.google.com/d/msg/google-cloud-sql-announce/ + # | I_7-F9EBhT0/BtvFtdFeAgAJ for details. + - !ruby/object:Api::Type::Enum + name: 'databaseVersion' + description: | + The database engine type and version. For First Generation instances, + can be MYSQL_5_5, or MYSQL_5_6. For Second Generation instances, can + be MYSQL_5_6 or MYSQL_5_7. Defaults to MYSQL_5_6. + + The databaseVersion property can not be changed after instance + creation. + values: + - :MYSQL_5_5 + - :MYSQL_5_6 + - :MYSQL_5_7 + - !ruby/object:Api::Type::NestedObject + name: 'failoverReplica' + description: | + The name and status of the failover replica. This property is + applicable only to Second Generation instances. + properties: + - !ruby/object:Api::Type::Boolean + name: 'available' + description: | + The availability status of the failover replica. A false status + indicates that the failover replica is out of sync. The master + can only failover to the falover replica when the status is true. + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name of the failover replica. If specified at instance + creation, a failover replica is created for the instance. The name + doesn't include the project ID. This property is applicable only + to Second Generation instances. + - !ruby/object:Api::Type::Enum + name: 'instanceType' + description: | + The instance type. This can be one of the following. + * CLOUD_SQL_INSTANCE: A Cloud SQL instance that is not replicating + from a master. + * ON_PREMISES_INSTANCE: An instance running on the customer's + premises. + * READ_REPLICA_INSTANCE: A Cloud SQL instance configured as a + read-replica. + values: + - :CLOUD_SQL_INSTANCE + - :ON_PREMISES_INSTANCE + - :READ_REPLICA_INSTANCE + - !ruby/object:Api::Type::Array + name: 'ipAddresses' + description: 'The assigned IP addresses for the instance.' + output: true + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'ipAddress' + description: 'The IP address assigned.' + - !ruby/object:Api::Type::Time + name: 'timeToRetire' + description: | + The due time for this IP to be retired in RFC 3339 format, for + example 2012-11-15T16:19:00.094Z. This field is only available + when the IP is scheduled to be retired. + - !ruby/object:Api::Type::Enum + name: 'type' + description: | + The type of this IP address. A PRIMARY address is an address + that can accept incoming connections. An OUTGOING address is the + source address of connections originating from the instance, if + supported. + values: + - :PRIMARY + - :OUTGOING + - !ruby/object:Api::Type::String + name: 'ipv6Address' + description: | + The IPv6 address assigned to the instance. This property is applicable + only to First Generation instances. + - !ruby/object:Api::Type::String + name: 'masterInstanceName' + description: | + The name of the instance which will act as master in the replication + setup. + - !ruby/object:Api::Type::Integer + name: 'maxDiskSize' + description: 'The maximum disk size of the instance in bytes.' + - !ruby/object:Api::Type::String + name: 'name' + description: | + Name of the Cloud SQL instance. This does not include the project + ID. + required: true + # TODO(alexstephen): Investigate if worth to make this depend on compute's + # region as a resource reference. (low priority, non launch blocker) + - !ruby/object:Api::Type::String + name: 'region' + description: | + The geographical region. Defaults to us-central or us-central1 + depending on the instance type (First Generation or Second + Generation/PostgreSQL). + - !ruby/object:Api::Type::NestedObject + name: 'replicaConfiguration' + description: | + Configuration specific to failover replicas and read replicas. + properties: + - !ruby/object:Api::Type::Boolean + name: 'failoverTarget' + description: | + Specifies if the replica is the failover target. If the field is + set to true the replica will be designated as a failover replica. + In case the master instance fails, the replica instance will be + promoted as the new master instance. + + Only one replica can be specified as failover target, and the + replica has to be in different zone with the master instance. + # TODO(nelsonjr): Is this needed or output only? + # | - !ruby/object:Api::Type::Constant + # | name: 'kind' + # | description: | + # | This is always sql#replicaConfiguration. + # | value: 'sql#replicaConfiguration' + - !ruby/object:Api::Type::NestedObject + name: 'mysqlReplicaConfiguration' + description: | + MySQL specific configuration when replicating from a MySQL + on-premises master. Replication configuration information such as + the username, password, certificates, and keys are not stored in + the instance metadata. The configuration information is used + only to set up the replication connection and is stored by MySQL + in a file named master.info in the data directory. + properties: + - !ruby/object:Api::Type::String + name: 'caCertificate' + description: | + PEM representation of the trusted CA's x509 certificate. + - !ruby/object:Api::Type::String + name: 'clientCertificate' + description: | + PEM representation of the slave's x509 certificate + - !ruby/object:Api::Type::String + name: 'clientKey' + description: | + PEM representation of the slave's private key. The + corresponsing public key is encoded in the client's asf asd + certificate. + - !ruby/object:Api::Type::Integer + name: 'connectRetryInterval' + description: | + Seconds to wait between connect retries. MySQL's default is 60 + seconds. + - !ruby/object:Api::Type::String + name: 'dumpFilePath' + description: | + Path to a SQL dump file in Google Cloud Storage from which the + slave instance is to be created. The URI is in the form + gs://bucketName/fileName. Compressed gzip files (.gz) are + also supported. Dumps should have the binlog co-ordinates from + which replication should begin. This can be accomplished by + setting --master-data to 1 when using mysqldump. + # TODO(nelsonjr): Is this needed or output only? + # | - !ruby/object:Api::Type::Constant + # | name: 'kind' + # | description: 'This is always sql#mysqlReplicaConfiguration.' + # | value: 'sql#mysqlReplicaConfiguration' + - !ruby/object:Api::Type::Integer + name: 'masterHeartbeatPeriod' + description: | + Interval in milliseconds between replication heartbeats. + - !ruby/object:Api::Type::String + name: 'password' + description: | + The password for the replication connection. + - !ruby/object:Api::Type::String + name: 'sslCipher' + description: | + A list of permissible ciphers to use for SSL encryption. + - !ruby/object:Api::Type::String + name: 'username' + description: | + The username for the replication connection. + - !ruby/object:Api::Type::Boolean + name: 'verifyServerCertificate' + description: | + Whether or not to check the master's Common Name value in the + certificate that it sends during the SSL handshake. + - !ruby/object:Api::Type::Array + name: 'replicaNames' + description: | + The replicas of the instance. + item_type: Api::Type::String + # TODO(nelsonjr): Parameter is unclear. Review this property when + # http://b/62686412 is addressed. + # | - !ruby/object:Api::Type::NestedObject + # | name: 'serverCaCert' + # | description: 'SSL configuration.' + - !ruby/object:Api::Type::String + name: 'serviceAccountEmailAddress' + description: | + The service account email address assigned to the instance. This + property is applicable only to Second Generation instances. + # TODO(nelsonjr): Add other settings properties + - !ruby/object:Api::Type::NestedObject + name: 'settings' + description: 'The user settings.' + properties: + #- !ruby/object:Api::Type::Constant + # name: 'kind' + # value: 'sql#settings' + - !ruby/object:Api::Type::NestedObject + name: 'ipConfiguration' + description: | + The settings for IP Management. This allows to enable or disable + the instance IP and manage which external networks can connect to + the instance. The IPv4 address cannot be disabled for Second + Generation instances. + properties: + - !ruby/object:Api::Type::Boolean + name: 'ipv4Enabled' + description: | + Whether the instance should be assigned an IP address or not. + - !ruby/object:Api::Type::Array + name: 'authorizedNetworks' + description: | + The list of external networks that are allowed to connect to + the instance using the IP. In CIDR notation, also known as + 'slash' notation (e.g. 192.168.100.0/24). + item_type: !ruby/object:Api::Type::NestedObject + properties: + #- !ruby/object:Api::Type::Constant + # name: 'kind' + # value: 'sql#aclEntry' + - !ruby/object:Api::Type::Time + name: 'expirationTime' + description: | + The time when this access control entry expires in RFC + 3339 format, for example 2012-11-15T16:19:00.094Z. + - !ruby/object:Api::Type::String + name: 'name' + description: 'An optional label to identify this entry.' + - !ruby/object:Api::Type::String + name: 'value' + description: | + The whitelisted value for the access control list. For + example, to grant access to a client from an external IP + (IPv4 or IPv6) address or subnet, use that address or + subnet here. + - !ruby/object:Api::Type::Boolean + name: 'requireSsl' + description: | + Whether the mysqld should default to 'REQUIRE X509' for + users connecting over IP. + - !ruby/object:Api::Type::String + name: 'tier' + description: | + The tier or machine type for this instance, for + example db-n1-standard-1. For MySQL instances, this field + determines whether the instance is Second Generation (recommended) + or First Generation. + # TODO(nelsonjr): Add missing properties such as state. + - !ruby/object:Api::Resource + name: 'Database' + kind: 'sql#database' + base_url: projects/{{project}}/instances/{{instance}}/databases + description: | + Represents a SQL database inside the Cloud SQL instance, hosted in + Google's cloud. +<%= indent(compile('templates/async.yaml.erb'), 4) %> + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'instance' + description: | + The name of the Cloud SQL instance. This does not include the project + ID. + resource: 'Instance' + imports: 'name' + required: true + properties: + - !ruby/object:Api::Type::String + name: 'charset' + description: 'The MySQL charset value.' + - !ruby/object:Api::Type::String + name: 'collation' + description: 'The MySQL collation value.' + - !ruby/object:Api::Type::String + name: 'name' + description: | + The name of the database in the Cloud SQL instance. + This does not include the project ID or instance name. + - !ruby/object:Api::Resource + name: 'User' + kind: 'sql#user' + base_url: projects/{{project}}/instances/{{instance}}/users + self_link: | + projects/{{project}}/instances/{{instance}}/users + ?name={{name}}&host={{host}} + self_link_query: !ruby/object:Api::Resource::ResponseList + kind: 'sql#usersList' + items: 'items' + identity: + - name + - host + description: | + The Users resource represents a database user in a Cloud SQL instance. +<%= indent(compile('templates/async.yaml.erb'), 4) %> + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'instance' + description: | + The name of the Cloud SQL instance. This does not include the project + ID. + resource: 'Instance' + imports: 'name' + required: true + - !ruby/object:Api::Type::String + name: 'password' + description: 'The password for the user.' + input: true + properties: + - !ruby/object:Api::Type::String + name: 'host' + description: | + The host name from which the user can connect. For insert operations, + host defaults to an empty string. For update operations, host is + specified as part of the request URL. The host name cannot be updated + after insertion. + required: true + - !ruby/object:Api::Type::String + name: 'name' + description: 'The name of the user in the Cloud SQL instance.' + required: true + - !ruby/object:Api::Resource + name: 'SslCert' + kind: 'sql#sslCert' + base_url: projects/{{project}}/instances/{{instance}}/sslCerts + self_link: | + projects/{{project}}/instances/{{instance}}/sslCerts/ + {{sha1_fingerprint}} + virtual: true # we're not enforcing state as it is all server-side driven. + description: | + Represents an SSL certificate created for a Cloud SQL instance. To use the + SSL certificate you must have the SSL Client Certificate and the + associated SSL Client Key. The Client Key can be downloaded only when the + SSL certificate is created with the insert method. + identity: + - sha1Fingerprint + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'instance' + description: | + The name of the Cloud SQL instance. This does not include the project + ID. + resource: 'Instance' + imports: 'name' + required: true + - !ruby/object:Api::Type::String + name: 'sha1Fingerprint' + description: 'The SHA-1 of the certificate.' + required: true + properties: + - !ruby/object:Api::Type::String + name: 'cert' + description: 'PEM representation of the X.509 certificate.' + - !ruby/object:Api::Type::String + name: 'certSerialNumber' + description: 'Serial number, as extracted from the certificate.' + - !ruby/object:Api::Type::String + name: 'commonName' + description: 'User supplied name. Constrained to [a-zA-Z.-_ ]+.' + - !ruby/object:Api::Type::Time + name: 'createTime' + description: | + The time when the certificate was created in RFC 3339 format, for + example 2012-11-15T16:19:00.094Z. + - !ruby/object:Api::Type::Time + name: 'expirationTime' + description: | + The time when the certificate expires in RFC 3339 format, for example + 2012-11-15T16:19:00.094Z. + - !ruby/object:Api::Resource + name: 'Flag' + kind: 'sql#flag' + description: + 'Represents a flag that can be configured for a Cloud SQL instance.' + base_url: flags + self_link: flags + self_link_query: !ruby/object:Api::Resource::ResponseList + kind: 'sql#flagsList' + items: 'items' + virtual: true + properties: + - !ruby/object:Api::Type::Array + name: 'allowedStringValues' + item_type: Api::Type::String + description: + 'For STRING flags, List of strings that the value can be set to.' + output: true + - !ruby/object:Api::Type::Array + name: 'appliesTo' + item_type: Api::Type::String + description: 'The database versions this flag is supported for.' + output: true + - !ruby/object:Api::Type::Integer + name: 'maxValue' + description: 'For INTEGER flags, the maximum allowed value.' + output: true + - !ruby/object:Api::Type::Integer + name: 'minValue' + description: 'For INTEGER flags, the minimum allowed value.' + output: true + - !ruby/object:Api::Type::String + name: 'name' + description: | + This is the name of the flag. Flag names always use underscores, not + hyphens, e.g. max_allowed_packet + - !ruby/object:Api::Type::Boolean + name: 'requiresRestart' + description: | + Indicates whether changing this flag will trigger a database restart. + Only applicable to Second Generation instances. + output: true + - !ruby/object:Api::Type::String + name: 'type' + description: | + The type of the flag. Flags are typed to being BOOLEAN, STRING, + INTEGER or NONE. NONE is used for flags which do not take a value, + such as skip_grant_tables. + output: true + - !ruby/object:Api::Resource + name: 'Tier' + kind: 'sql#tier' + description: | + The Tiers resource represents a service configuration that can be used to + define a Cloud SQL instance. Each tier has an associated RAM, maximum + storage, and list of regions in which the tier can be used. Available + tiers vary depending on whether you use PostgreSQL, MySQL Second + Generation, or MySQL First Generation instances. + base_url: projects/{{project}}/tiers + self_link: projects/{{project}}/tiers + self_link_query: !ruby/object:Api::Resource::ResponseList + kind: 'sql#tiersList' + items: 'items' + identity: + - tier + virtual: true + parameters: + - !ruby/object:Api::Type::String + name: 'tier' + description: | + An identifier for the service tier or machine type, for example, + db-n1-standard-1. For related information. + required: true + properties: + - !ruby/object:Api::Type::Integer + name: 'DiskQuota' + description: 'The maximum disk size of this tier in bytes.' + output: true + - !ruby/object:Api::Type::Integer + name: 'RAM' + description: 'The maximum RAM usage of this tier in bytes.' + output: true + - !ruby/object:Api::Type::Array + name: 'region' + item_type: Api::Type::String + description: 'The applicable regions for this tier.' + output: true + # 'Operation' is not idempotent and will not be covered. + # (it is used internally to assert the state of operations being performed) + # | - !ruby/object:Api::Resource + # | name: 'Operation' + # | kind: 'sql#operation' + # | base_url: projects/{{project}}/operations diff --git a/products/sql/chef-e2e.yaml b/products/sql/chef-e2e.yaml new file mode 100644 index 000000000000..78f55c126a1f --- /dev/null +++ b/products/sql/chef-e2e.yaml @@ -0,0 +1,120 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Chef::Tester + product: 'SQL' + tests: + - !ruby/object:Chef::StandardTest + name: 'Instance' + module: 'gsql' + env: + sql_instance_suffix: end2end-instance-test-{{run_id}} + verifiers: + - phase: ALL + command: | + gcloud sql instances describe + chef-e2e-sql-test-end2end-instance-test-{{run_id}} + - !ruby/object:Chef::StandardTest + name: 'Database' + module: 'gsql' + resource_count: 3 + affected_count: 1 + env: + sql_instance_suffix: end2end-database-test-{{run_id}} + verifiers: + - phase: ALL + command: | + gcloud sql databases describe + --instance=chef-e2e-sql-test-end2end-database-test-{{run_id}} + chef-e2e-webstore + pre: + - name: 'startup{pre}' # our test needs the SQL isntance. create it. + apply: + - run: 'google-gsql::tests~instance' + output: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'startup{pre}{again}' + apply: + - run: 'google-gsql::tests~instance' + output: + - - 'Chef Client finished, 1/2 resources updated' + post: + - name: 'cleanup{post}' # our test creates the SQL isntance. delete it. + apply: + - run: 'google-gsql::tests~delete_instance' + output: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'cleanup{post}{again}' + apply: + - run: 'google-gsql::tests~delete_instance' + output: + - - 'Chef Client finished, 1/2 resources updated' + - !ruby/object:Chef::StandardTest + name: 'User' + module: 'gsql' + resource_count: 3 + affected_count: 1 + env: + sql_instance_suffix: end2end-user-test-{{run_id}} + # TODO(nelsonjr): No verifiers. Check with SQL Team if there is a way to + # verify if the user exists (did not exist in 'gcloud' today) + pre: + - name: 'startup{pre}' # our test needs the SQL isntance. create it. + apply: + - run: 'google-gsql::tests~instance' + output: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'startup{pre}{again}' + apply: + - run: 'google-gsql::tests~instance' + output: + - - 'Chef Client finished, 1/2 resources updated' + post: + - name: 'cleanup{post}' # our test creates the SQL isntance. delete it. + apply: + - run: 'google-gsql::tests~delete_instance' + output: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'cleanup{post}{again}' + apply: + - run: 'google-gsql::tests~delete_instance' + output: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'Instance{Network}' + verifiers: + - phase: create + command: | + gcloud sql instances describe + chef-e2e-sql-test-end2end-network-test-{{run_id}} + | grep -A 3 authorizedNetworks: + | grep '8.8.8.8/32' + phases: + - name: 'create' # our test needs the SQL isntance. create it. + apply: + - run: 'google-gsql::tests~instance' + env: + sql_instance_suffix: end2end-network-test-{{run_id}} + output: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'delete' + apply: + - run: 'google-gsql::tests~delete_instance' + env: + sql_instance_suffix: end2end-network-test-{{run_id}} + output: + - - 'Chef Client finished, 1/2 resources updated' diff --git a/products/sql/chef.yaml b/products/sql/chef.yaml new file mode 100644 index 000000000000..9d61af841e18 --- /dev/null +++ b/products/sql/chef.yaml @@ -0,0 +1,207 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Chef::Config +manifest: !ruby/object:Provider::Chef::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/chef-google-compute' + issues: 'https://github.com/GoogleCloudPlatform/chef-google-compute/issues' + summary: 'A Chef cookbook to manage Google Cloud Compute resources' + description: | + This cookbook provides the built-in types and services for Chef to manage + Google Cloud Compute resources, as native Chef types. + depends: + - !ruby/object:Provider::Config::Requirements + name: 'google-gauth' + versions: '< 0.2.0' + operating_systems: +<%= indent(include('provider/chef/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + Database: + # TODO(nelsonjr): Once bugs in the body are resolved remove this override. + return_if_object: | + # rubocop:disable Metrics/CyclomaticComplexity + def self.return_if_object(response, kind) + raise "Bad response: #{response}" \ + unless response.is_a?(Net::HTTPResponse) + return if response.is_a?(Net::HTTPNotFound) + return if response.is_a?(Net::HTTPNoContent) + # TODO(nelsonjr): Remove return of Net::HTTPForbidden from + # return_if_object once Cloud SQL bug http://b/62635365 is resolved. + # Currently the API returns 403 for objects that do not exist, even + # when the user has access to the project. This is being changed to + # return 404 as it is supposed to be. + # Once 404 is the correct response, the temporary workaround should + # be removed. + return if response.is_a?(Net::HTTPForbidden) + result = JSON.parse(response.body) + raise_if_errors result, %w[error errors], 'message' + raise "Bad response: #{response}" unless response.is_a?(Net::HTTPOK) + # TODO(nelsonjr): Revert this check back to standard once Cloud SQL + # bug http://b/62841551 is resolved. + # Currently the sql#operation#targetLink for create returns a + # sql#database while for a delete it returns a sql#instance. + # | raise "Incorrect result: #{result['kind']} (expecting #{kind})" \ + # | unless result['kind'] == kind + raise "Incorrect result: #{result['kind']} (expecting #{kind})" \ + unless [kind, 'sql#instance'].include?(result['kind']) + result + end + # rubocop:enable Metrics/CyclomaticComplexity + + def return_if_object(response, kind) + self.class.return_if_object(response, kind) + end + Flag: + flush: | + raise('Cloud SQL flag does not match expectations.') + Instance: + # TODO(nelsonjr): Once bugs in the body are resolved remove this override. + return_if_object: | + # rubocop:disable Metrics/CyclomaticComplexity + def self.return_if_object(response, kind) + raise "Bad response: #{response}" \ + unless response.is_a?(Net::HTTPResponse) + return if response.is_a?(Net::HTTPNotFound) + return if response.is_a?(Net::HTTPNoContent) + # TODO(nelsonjr): Remove return of Net::HTTPForbidden from + # return_if_object once Cloud SQL bug http://b/62635365 is resolved. + # Currently the API returns 403 for objects that do not exist, even + # when the user has access to the project. This is being changed to + # return 404 as it is supposed to be. + # Once 404 is the correct response, the temporary workaround should + # be removed. + return if response.is_a?(Net::HTTPForbidden) + result = JSON.parse(response.body) + raise_if_errors result, %w[error errors], 'message' + raise "Bad response: #{response}" unless response.is_a?(Net::HTTPOK) + raise "Incorrect result: #{result['kind']} (expecting #{kind})" \ + unless result['kind'] == kind + result + end + # rubocop:enable Metrics/CyclomaticComplexity + + def return_if_object(response, kind) + self.class.return_if_object(response, kind) + end + resource_to_request_patch: | + unless @__fetched.nil? + # Convert to pure JSON + request = JSON.parse(request.to_json) + request['settings']['settingsVersion'] = + @__fetched['settings']['settingsVersion'] + end + SslCert: + flush: | + raise('Cloud SQL SSL certificate mismatch.') + Tier: + flush: | + raise('Cloud SQL tier does not match expectations.') +examples: !ruby/object:Api::Resource::HashArray + Database: + - database.rb + - delete_database.rb + Flag: + - flag.rb + Instance: + - instance.rb + - delete_instance.rb + SslCert: + - ssl_cert.rb + Tier: + - tier.rb + User: + - user.rb + - delete_user.rb +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/chef/common~copy.yaml'), 4) %> + compile: + lib/google/object_store.rb: google/object_store.rb +<%= indent(include('provider/chef/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/chef/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData + network: !ruby/object:Api::Resource::HashArray + User: + - success1~name + - success1~title + - success2~name + - success2~title + - success3~name + - success3~title +style: + - !ruby/object:Provider::Config::StyleException + name: libraries/google/sql/property/instance_mysql_replica_configuration.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - function: compare_fields + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/instance.rb + pinpoints: + - class: Google::GSQL::Instance + exceptions: + - Metrics/ClassLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - Metrics/AbcSize + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/database.rb + pinpoints: + - class: Google::GSQL::Database + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/ssl_cert.rb + pinpoints: + - class: Google::GSQL::SslCert + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/flag.rb + pinpoints: + - class: Google::GSQL::Flag + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/tier.rb + pinpoints: + - class: Google::GSQL::Tier + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: resources/user.rb + pinpoints: + - class: Google::GSQL::User + exceptions: + - Metrics/ClassLength + - !ruby/object:Provider::Config::StyleException + name: spec/gsql_instance + pinpoints: + - test: Instance > present > not_exist > success > title_eq_name > before + exceptions: + - Metrics/LineLength + - !ruby/object:Provider::Config::StyleException + name: spec/gsql_instance + pinpoints: + - test: Instance > present > not_exist > success > title_and_name > before + exceptions: + - Metrics/LineLength diff --git a/products/sql/files/examples~cookbook~database.rb b/products/sql/files/examples~cookbook~database.rb new file mode 100644 index 000000000000..f4a70219f9fc --- /dev/null +++ b/products/sql/files/examples~cookbook~database.rb @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% res_name = 'sql-test-#{ENV[\'sql_instance_suffix\']}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise ['For this example to run you need to define a env. variable named', + '"sql_instance_suffix". Please refer to the documentation inside', + 'the example file "<%= name -%>"'].join(' ') \ + unless ENV.key?('sql_instance_suffix') + +gsql_instance <%= example_resource_name(res_name) -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Tip: Remember to define gsql_instance to match the 'instance' property. +<% end -%> +gsql_database <%= example_resource_name('webstore') -%> do + action :create + charset 'utf8' + instance <%= example_resource_name(res_name) %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/sql/files/examples~cookbook~delete_database.rb b/products/sql/files/examples~cookbook~delete_database.rb new file mode 100644 index 000000000000..967998ccc2d4 --- /dev/null +++ b/products/sql/files/examples~cookbook~delete_database.rb @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% res_name = 'sql-test-#{ENV[\'sql_instance_suffix\']}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise ['For this example to run you need to define a env. variable named', + '"sql_instance_suffix". Please refer to the documentation inside', + 'the example file', + '"<%= name -%>"'].join(' ') \ + unless ENV.key?('sql_instance_suffix') + +gsql_instance <%= example_resource_name(res_name) -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Tip: Remember to define gsql_instance to match the 'instance' property. +<% end -%> +gsql_database <%= example_resource_name('webstore') -%> do + action :delete + instance <%= example_resource_name(res_name) %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/sql/files/examples~cookbook~delete_instance.rb b/products/sql/files/examples~cookbook~delete_instance.rb new file mode 100644 index 000000000000..6e5c5100ddf5 --- /dev/null +++ b/products/sql/files/examples~cookbook~delete_instance.rb @@ -0,0 +1,41 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +# Cloud SQL cannot reuse instance names. Add a random suffix so they are always +# unique. +# +# To be able to delete the instance via Chef make sure the instance ID matches +# the ID used during creation. If you used the create example and specified the +# 'sql_instance_suffix', you should match it as well during deletion. +raise ['For this example to run you need to define a env. variable named', + '"sql_instance_suffix". Please refer to the documentation inside', + 'the example file', + '"<%= name -%>"'].join(' ') \ + unless ENV.key?('sql_instance_suffix') + +<% end -%> +<% res_name = 'sql-test-#{ENV[\'sql_instance_suffix\']}' -%> +gsql_instance <%= example_resource_name(res_name) -%> do + action :delete + region 'us-central1' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/sql/files/examples~cookbook~delete_user.rb b/products/sql/files/examples~cookbook~delete_user.rb new file mode 100644 index 000000000000..110a08467b98 --- /dev/null +++ b/products/sql/files/examples~cookbook~delete_user.rb @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% res_name = 'sql-test-#{ENV[\'sql_instance_suffix\']}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise ['For this example to run you need to define a env. variable named', + '"sql_instance_suffix". Please refer to the documentation inside', + 'the example file "<%= name -%>"'].join(' ') \ + unless ENV.key?('sql_instance_suffix') + +gsql_instance <%= example_resource_name(res_name) -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Tip: Remember to define gsql_instance to match the 'instance' property. +<% end -%> +gsql_user 'john.doe' do + action :delete + host '10.1.2.3' + instance <%= example_resource_name(res_name) %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/sql/files/examples~cookbook~flag.rb b/products/sql/files/examples~cookbook~flag.rb new file mode 100644 index 000000000000..7466064b4dc9 --- /dev/null +++ b/products/sql/files/examples~cookbook~flag.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gsql_flag <%= example_resource_name('group_concat_max_len') -%> do + min_value 4 + max_value 4294967295 + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/sql/files/examples~cookbook~instance.rb b/products/sql/files/examples~cookbook~instance.rb new file mode 100644 index 000000000000..3d84ee9602bd --- /dev/null +++ b/products/sql/files/examples~cookbook~instance.rb @@ -0,0 +1,53 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +# Cloud SQL cannot reuse instance names. Add a random suffix so they are always +# unique. +# +# To be able to delete the instance via Chef make sure the instance ID matches +# the ID used during creation. If you used the create example and specified the +# 'sql_instance_suffix', you should match it as well during deletion. +raise ['For this example to run you need to define a env. variable named', + '"sql_instance_suffix". Please refer to the documentation inside', + 'the example file "<%= name -%>"'].join(' ') \ + unless ENV.key?('sql_instance_suffix') + +<% end -%> +<% res_name = 'sql-test-#{ENV[\'sql_instance_suffix\']}' -%> +gsql_instance <%= example_resource_name(res_name) -%> do + action :create + database_version 'MYSQL_5_7' + settings({ + tier: 'db-n1-standard-1', + ip_configuration: { + authorized_networks: [ + # The ACL below is for example only. (do NOT use in production as-is) + { + name: 'google dns server', + value: '8.8.8.8/32' + } + ] + } + }) + region 'us-central1' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/sql/files/examples~cookbook~readme.rb b/products/sql/files/examples~cookbook~readme.rb new file mode 100644 index 000000000000..8bda21ecf009 --- /dev/null +++ b/products/sql/files/examples~cookbook~readme.rb @@ -0,0 +1,36 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% res_name = 'sql-test-#{ENV[\'sql_instance_suffix\']}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% end -%> +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gsql_instance <%= example_resource_name(res_name) -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +gsql_database <%= example_resource_name('webstore') -%> do + action :create + charset 'utf8' + instance <%= example_resource_name(res_name) %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/sql/files/examples~cookbook~ssl_cert.rb b/products/sql/files/examples~cookbook~ssl_cert.rb new file mode 100644 index 000000000000..8fe64e17c01d --- /dev/null +++ b/products/sql/files/examples~cookbook~ssl_cert.rb @@ -0,0 +1,44 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% res_name = 'sql-test-#{ENV[\'sql_instance_suffix\']}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise ['For this example to run you need to define a env. variable named', + '"sql_instance_suffix". Please refer to the documentation inside', + 'the example file "<%= name -%>"'].join(' ') \ + unless ENV.key?('sql_instance_suffix') + +gsql_instance <%= example_resource_name(res_name) -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Tip: Remember to define gsql_instance to match the 'instance' property. +<% end -%> +gsql_ssl_cert <%= example_resource_name('server-certificate') -%> do + cert_serial_number '729335786' + common_name 'CN=www.mydb.com,O=Acme' + sha1_fingerprint '8fc295bf77a002db5182e04d92c48258cbc1117a' + instance <%= example_resource_name(res_name) %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/sql/files/examples~cookbook~tier.rb b/products/sql/files/examples~cookbook~tier.rb new file mode 100644 index 000000000000..78f5b75cc5b5 --- /dev/null +++ b/products/sql/files/examples~cookbook~tier.rb @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end -%> +gsql_tier <%= example_resource_name('D0') -%> do + ram 134217728 # we'll confirm that tier has enough RAM for us + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/sql/files/examples~cookbook~user.rb b/products/sql/files/examples~cookbook~user.rb new file mode 100644 index 000000000000..028b3108ef7d --- /dev/null +++ b/products/sql/files/examples~cookbook~user.rb @@ -0,0 +1,44 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% res_name = 'sql-test-#{ENV[\'sql_instance_suffix\']}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +raise ['For this example to run you need to define a env. variable named', + '"sql_instance_suffix". Please refer to the documentation inside', + 'the example file "<%= name -%>"'].join(' ') \ + unless ENV.key?('sql_instance_suffix') + +gsql_instance <%= example_resource_name(res_name) -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else -%> +# Tip: Remember to define gsql_instance to match the 'instance' property. +<% end -%> +gsql_user 'john.doe' do + action :create + password 'secret-password' + host '10.1.2.3' + instance <%= example_resource_name(res_name) %> + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/sql/files/examples~database.pp b/products/sql/files/examples~database.pp new file mode 100644 index 000000000000..be61993ddf2e --- /dev/null +++ b/products/sql/files/examples~database.pp @@ -0,0 +1,57 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% instance_name = 'sql-test-${sql_instance_suffix}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# TODO(alexstephen): Change this warning and remove the "requires to to exists" +# once a resource reference is added (it will enforce that automatically). +# +# This example requires an instance to exist. You should set +# FACTER_sql_instance_suffix, or use any other Puppet # supported way, to set a +# global variable $sql_instance_suffix. +# +# For example you can define the fact to be an always increasing value: +# +# $ FACTER_sql_instance_suffix=100 puppet apply examples/database.pp +# +# If that instance does not exist in your project run the examples/instance.pp +# to create it, with the same $sql_instance_suffix. +if !defined('$sql_instance_suffix') { + fail('For this example to run you need to define a fact named + "sql_instance_suffix". Please refer to the documentation inside + the example file "<%= name -%>"') +} + +gsql_instance { <%= example_resource_name(instance_name) -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else -%> +# Tip: Remember to define gsql_instance to match the 'instance' property. +<% end -%> +gsql_database { <%= example_resource_name('webstore') -%>: + ensure => present, + charset => 'utf8', + instance => <%= example_resource_name(instance_name) -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/sql/files/examples~delete_database.pp b/products/sql/files/examples~delete_database.pp new file mode 100644 index 000000000000..220ef8063fa1 --- /dev/null +++ b/products/sql/files/examples~delete_database.pp @@ -0,0 +1,56 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% instance_name = 'sql-test-${sql_instance_suffix}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# TODO(alexstephen): Change this warning and remove the "requires to to exists" +# once a resource reference is added (it will enforce that automatically). +# +# This example requires an instance to exist. You should set +# FACTER_sql_instance_suffix, or use any other Puppet # supported way, to set a +# global variable $sql_instance_suffix. +# +# For example you can define the fact to be an always increasing value: +# +# $ FACTER_sql_instance_suffix=100 puppet apply examples/database.pp +# +# If that instance does not exist in your project run the examples/instance.pp +# to create it, with the same $sql_instance_suffix. +if !defined('$sql_instance_suffix') { + fail('For this example to run you need to define a fact named + "sql_instance_suffix". Please refer to the documentation inside + the example file "<%= name -%>"') +} + +gsql_instance { <%= example_resource_name(instance_name) -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else -%> +# Tip: Remember to define gsql_instance to match the 'instance' property. +<% end -%> +gsql_database { <%= example_resource_name('webstore') -%>: + ensure => absent, + instance => <%= example_resource_name(instance_name) -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/sql/files/examples~delete_instance.pp b/products/sql/files/examples~delete_instance.pp new file mode 100644 index 000000000000..471a4700c330 --- /dev/null +++ b/products/sql/files/examples~delete_instance.pp @@ -0,0 +1,45 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# Cloud SQL cannot reuse instance names. Add a random suffix so they are always +# unique. You should set FACTER_sql_instance_suffix, or use any other Puppet +# supported way, to set a global variable $sql_instance_suffix. +# +# For example you can define the fact to be an always increasing value: +# +# $ FACTER_sql_instance_suffix=$(date +%s) puppet apply examples/instance.pp +# +# To be able to delete the instance via Puppet make sure the instance ID matches +# the ID used during creation. If you used the create example and specified the +# 'sql_instance_suffix', you should match it as well during deletion. +if !defined('$sql_instance_suffix') { + fail('For this example to run you need to define a fact named + "sql_instance_suffix". Please refer to the documentation inside + the example file "<%= name -%>"') +} + +<% end -%> +<% instance_name = 'sql-test-${sql_instance_suffix}' -%> +gsql_instance { <%= example_resource_name(instance_name) -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/sql/files/examples~delete_user.pp b/products/sql/files/examples~delete_user.pp new file mode 100644 index 000000000000..136bb27788ba --- /dev/null +++ b/products/sql/files/examples~delete_user.pp @@ -0,0 +1,60 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% instance_name = 'sql-test-${sql_instance_suffix}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# TODO(alexstephen): Change this warning and remove the "requires to to exists" +# once a resource reference is added (it will enforce that automatically). +# +# This example requires an instance to exist. You should set +# FACTER_sql_instance_suffix, or use any other Puppet # supported way, to set a +# global variable $sql_instance_suffix. +# +# For example you can define the fact to be an always increasing value: +# +# $ FACTER_sql_instance_suffix=100 puppet apply examples/database.pp +# +# If that instance does not exist in your project run the examples/instance.pp +# to create it, with the same $sql_instance_suffix. +if !defined('$sql_instance_suffix') { + fail('For this example to run you need to define a fact named + "sql_instance_suffix". Please refer to the documentation inside + the example file "<%= name -%>"') +} + +gsql_instance { <%= example_resource_name(instance_name) -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else -%> +# Tip: Remember to define gsql_instance to match the 'instance' property. +<% end -%> +<%# User name can't have a prefix or it'll be too long + # Username is scoped to a prefixed instance, so this doesn't matter. +-%> +gsql_user { 'john.doe': + ensure => absent, + host => '10.1.2.3', + instance => <%= example_resource_name(instance_name) -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/sql/files/examples~flag.pp b/products/sql/files/examples~flag.pp new file mode 100644 index 000000000000..5874dcca0623 --- /dev/null +++ b/products/sql/files/examples~flag.pp @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end -%> +gsql_flag { <%= example_resource_name('group_concat_max_len') -%>: + min_value => 4, + max_value => 4294967295, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/sql/files/examples~instance.pp b/products/sql/files/examples~instance.pp new file mode 100644 index 000000000000..3fa17595829d --- /dev/null +++ b/products/sql/files/examples~instance.pp @@ -0,0 +1,59 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# Cloud SQL cannot reuse instance names. Add a random suffix so they are always +# unique. You should set FACTER_sql_instance_suffix, or use any other Puppet +# supported way, to set a global variable $sql_instance_suffix. +# +# For example you can define the fact to be an always increasing value: +# +# $ FACTER_sql_instance_suffix=$(date +%s) puppet apply examples/instance.pp +# +# To be able to delete the instance via Puppet make sure the instance ID matches +# the ID used during creation. If you used the create example and specified the +# 'sql_instance_suffix', you should match it as well during deletion. +if !defined('$sql_instance_suffix') { + fail('For this example to run you need to define a fact named + "sql_instance_suffix". Please refer to the documentation inside + the example file "<%= name -%>"') +} + +<% end -%> +<% instance_name = "sql-test-${sql_instance_suffix}" -%> +gsql_instance { <%= example_resource_name(instance_name) -%>: + ensure => present, + database_version => 'MYSQL_5_7', + settings => { + ip_configuration => { + authorized_networks => [ + # The ACL below is for example only. (do NOT use in production as-is) + { + name => 'google dns server', + value => '8.8.8.8/32' + }, + ], + }, + tier => 'db-n1-standard-1' + }, + region => 'us-central1', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/sql/files/examples~ssl_cert.pp b/products/sql/files/examples~ssl_cert.pp new file mode 100644 index 000000000000..5b70297963a7 --- /dev/null +++ b/products/sql/files/examples~ssl_cert.pp @@ -0,0 +1,58 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% instance_name = 'sql-test-${sql_instance_suffix}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# TODO(alexstephen): Change this warning and remove the "requires to to exists" +# once a resource reference is added (it will enforce that automatically). +# +# This example requires an instance to exist. You should set +# FACTER_sql_instance_suffix, or use any other Puppet # supported way, to set a +# global variable $sql_instance_suffix. +# +# For example you can define the fact to be an always increasing value: +# +# $ FACTER_sql_instance_suffix=100 puppet apply examples/user.pp +# +# If that instance does not exist in your project run the examples/instance.pp +# to create it, with the same $sql_instance_suffix. +if !defined('$sql_instance_suffix') { + fail('For this example to run you need to define a fact named + "sql_instance_suffix". Please refer to the documentation inside + the example file "<%= name -%>"') +} + +gsql_instance { <%= example_resource_name(instance_name) -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else -%> +# Tip: Remember to define gsql_instance to match the 'instance' property. +<% end -%> +gsql_ssl_cert { <%= example_resource_name('server-certificate') -%>: + cert_serial_number => '729335786', + common_name => 'CN=www.mydb.com,O=Acme', + sha1_fingerprint => '8fc295bf77a002db5182e04d92c48258cbc1117a', + instance => <%= example_resource_name(instance_name) -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/sql/files/examples~tier.pp b/products/sql/files/examples~tier.pp new file mode 100644 index 000000000000..c63deed54858 --- /dev/null +++ b/products/sql/files/examples~tier.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end -%> +gsql_tier { <%= example_resource_name('D0') -%>: + ram => 134217728, # we'll confirm that tier has enough RAM for us + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/sql/files/examples~user.pp b/products/sql/files/examples~user.pp new file mode 100644 index 000000000000..9a92c079906a --- /dev/null +++ b/products/sql/files/examples~user.pp @@ -0,0 +1,61 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% instance_name = 'sql-test-${sql_instance_suffix}' -%> +<% if name != 'README.md' -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +# TODO(alexstephen): Change this warning and remove the "requires to to exists" +# once a resource reference is added (it will enforce that automatically). +# +# This example requires an instance to exist. You should set +# FACTER_sql_instance_suffix, or use any other Puppet # supported way, to set a +# global variable $sql_instance_suffix. +# +# For example you can define the fact to be an always increasing value: +# +# $ FACTER_sql_instance_suffix=100 puppet apply examples/user.pp +# +# If that instance does not exist in your project run the examples/instance.pp +# to create it, with the same $sql_instance_suffix. +if !defined('$sql_instance_suffix') { + fail('For this example to run you need to define a fact named + "sql_instance_suffix". Please refer to the documentation inside + the example file "<%= name -%>"') +} + +gsql_instance { <%= example_resource_name(instance_name) -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else -%> +# Tip: Remember to define gsql_instance to match the 'instance' property. +<% end -%> +<%# User name can't have a prefix or it'll be too long + # Username is scoped to a prefixed instance, so this doesn't matter. +-%> +gsql_user { 'john.doe': + ensure => present, + password => 'secret-password', + host => '10.1.2.3', + instance => <%= example_resource_name(instance_name) -%>, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/sql/files/spec~user~success1~name.yaml b/products/sql/files/spec~user~success1~name.yaml new file mode 100644 index 000000000000..26823a98286d --- /dev/null +++ b/products/sql/files/spec~user~success1~name.yaml @@ -0,0 +1,24 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: sql#usersList +items: + - kind: sql#user + host: 'test host#0 data' + name: 'test name#0 data' diff --git a/products/sql/files/spec~user~success1~title.yaml b/products/sql/files/spec~user~success1~title.yaml new file mode 100644 index 000000000000..2b9412dc9008 --- /dev/null +++ b/products/sql/files/spec~user~success1~title.yaml @@ -0,0 +1,24 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: sql#usersList +items: + - kind: sql#user + host: 'test host#0 data' + name: title0 diff --git a/products/sql/files/spec~user~success2~name.yaml b/products/sql/files/spec~user~success2~name.yaml new file mode 100644 index 000000000000..72d859f6ab1f --- /dev/null +++ b/products/sql/files/spec~user~success2~name.yaml @@ -0,0 +1,24 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: sql#usersList +items: + - kind: sql#user + host: 'test host#1 data' + name: 'test name#1 data' diff --git a/products/sql/files/spec~user~success2~title.yaml b/products/sql/files/spec~user~success2~title.yaml new file mode 100644 index 000000000000..5cf6d7a932df --- /dev/null +++ b/products/sql/files/spec~user~success2~title.yaml @@ -0,0 +1,24 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: sql#usersList +items: + - kind: sql#user + host: 'test host#1 data' + name: title1 diff --git a/products/sql/files/spec~user~success3~name.yaml b/products/sql/files/spec~user~success3~name.yaml new file mode 100644 index 000000000000..5b8d60e8575c --- /dev/null +++ b/products/sql/files/spec~user~success3~name.yaml @@ -0,0 +1,24 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: sql#usersList +items: + - kind: sql#user + host: 'test host#2 data' + name: 'test name#2 data' diff --git a/products/sql/files/spec~user~success3~title.yaml b/products/sql/files/spec~user~success3~title.yaml new file mode 100644 index 000000000000..7a6e04481a37 --- /dev/null +++ b/products/sql/files/spec~user~success3~title.yaml @@ -0,0 +1,24 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +--- +kind: sql#usersList +items: + - kind: sql#user + host: 'test host#2 data' + name: title2 diff --git a/products/sql/helpers/api_gsql_instance.rb b/products/sql/helpers/api_gsql_instance.rb new file mode 100644 index 000000000000..3cc8d6e3185c --- /dev/null +++ b/products/sql/helpers/api_gsql_instance.rb @@ -0,0 +1,55 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/sql/network/post' + +module Google + module Sql + module Api + # A helper class to provide access to (some) Google Cloud SQL API. + class Instance + def initialize(name, project, cred) + @name = name + @project = project + @cred = cred + end + + # TODO(nelsonjr): Make this function wait for the operation to complete + def clone(target) + clone_request = ::Google::Sql::Network::Post.new( + gsql_instance_clone, @cred, 'application/json', { + cloneContext: { kind: 'sql#cloneContext', + destinationInstanceName: target } + }.to_json + ) + response = JSON.parse(clone_request.send.body) + raise Puppet::Error, response['error']['errors'][0]['message'] \ + if response['error'] + end + + private + + def gsql_instance_clone + URI.parse( + format( + '%s/%s', + Puppet::Type.type(:gsql_instance).provider(:google).self_link( + name: @name, project: @project + ), 'clone' + ) + ) + end + end + end + end +end diff --git a/products/sql/helpers/api_gsql_user.rb b/products/sql/helpers/api_gsql_user.rb new file mode 100644 index 000000000000..5f05250aa5e6 --- /dev/null +++ b/products/sql/helpers/api_gsql_user.rb @@ -0,0 +1,56 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/sql/network/post' + +module Google + module Sql + module Api + # A helper class to provide access to (some) Google Cloud SQL API. + class User + def initialize(name, instance, project, cred) + @name = name + @instance = instance + @project = project + @cred = cred + end + + # TODO(ody): This function is the same as gsql_user if you ignore + # idempotency. Is there a way to create the resource and call create on + # it? + def passwd(host, password) + # TODO(ody): If not the above this is resource_to_request. Can we call + # from the provider? + request = { name: @name, host: host, password: password }.to_json + post_request = ::Google::Sql::Network::Post.new( + gsql_user_collection, @cred, 'application/json', request + ) + response = JSON.parse(post_request.send.body) + raise Puppet::Error, response['error']['errors'][0]['message'] \ + if response['error'] + + # TODO(nelsonjr): Make this function wait for the operation to + # complete + end + + private + + def gsql_user_collection + Puppet::Type.type(:gsql_user).provider(:google).collection( + instance: @instance, project: @project + ) + end + end + end + end +end diff --git a/products/sql/puppet-e2e.yaml b/products/sql/puppet-e2e.yaml new file mode 100644 index 000000000000..0356de5dc9fb --- /dev/null +++ b/products/sql/puppet-e2e.yaml @@ -0,0 +1,106 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Puppet::Tester + product: 'SQL' + tests: + - !ruby/object:Puppet::StandardTest + name: 'Instance' + module: 'gsql' + env: + sql_instance_suffix: end2end-instance-test-{{run_id}} + verifiers: + - phase: ALL + command: | + gcloud sql instances describe + puppet-e2e-sql-test-end2end-instance-test-{{run_id}} + - !ruby/object:Puppet::StandardTest + name: 'Database' + module: 'gsql' + env: + sql_instance_suffix: end2end-database-test-{{run_id}} + verifiers: + - phase: ALL + command: | + gcloud sql databases describe + --instance=puppet-e2e-sql-test-end2end-database-test-{{run_id}} + puppet-e2e-webstore + pre: + - name: 'startup{pre}' # our test needs the SQL isntance. create it. + apply: + - run: 'instance.pp' + exits: 2 + - name: 'startup{pre}{again}' + apply: + - run: 'instance.pp' + exits: 0 + post: + - name: 'cleanup{post}' # our test creates the SQL isntance. delete it. + apply: + - run: 'delete_instance.pp' + exits: 2 + - name: 'cleanup{post}{again}' + apply: + - run: 'delete_instance.pp' + exits: 0 + - !ruby/object:Puppet::StandardTest + name: 'User' + module: 'gsql' + env: + sql_instance_suffix: end2end-user-test-{{run_id}} + # TODO(nelsonjr): No verifiers. Check with SQL Team if there is a way to + # verify if the user exists (did not exist in 'gcloud' today) + pre: + - name: 'startup{pre}' # our test needs the SQL isntance. create it. + apply: + - run: 'instance.pp' + exits: 2 + - name: 'startup{pre}{again}' + apply: + - run: 'instance.pp' + exits: 0 + post: + - name: 'cleanup{post}' # our test creates the SQL isntance. delete it. + apply: + - run: 'delete_instance.pp' + exits: 2 + - name: 'cleanup{post}{again}' + apply: + - run: 'delete_instance.pp' + exits: 0 + - name: 'Instance{Network}' + verifiers: + - phase: create + command: | + gcloud sql instances describe + puppet-e2e-sql-test-end2end-network-test-{{run_id}} + | grep -A 3 authorizedNetworks + | grep '8.8.8.8/32' + phases: + - name: 'create' + apply: + - run: 'instance.pp' + env: + sql_instance_suffix: end2end-network-test-{{run_id}} + exits: 2 + - name: 'delete' + apply: + - run: 'delete_instance.pp' + env: + sql_instance_suffix: end2end-network-test-{{run_id}} + exits: 2 diff --git a/products/sql/puppet.yaml b/products/sql/puppet.yaml new file mode 100644 index 000000000000..f77972f52a9e --- /dev/null +++ b/products/sql/puppet.yaml @@ -0,0 +1,348 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.2.0' + source: 'https://github.com/GoogleCloudPlatform/puppet-google-sql' + homepage: 'https://github.com/GoogleCloudPlatform/puppet-google-sql' + issues: + 'https://github.com/GoogleCloudPlatform/puppet-google-sql/issues' + summary: 'A Puppet module to manage Google Cloud SQL resources' + tags: + - google + - cloud + - sql + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google/gauth' + versions: '>= 0.2.0 < 0.3.0' + operating_systems: +<%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + Database: + # TODO(nelsonjr): Once bugs in the body are resolved remove this override. + return_if_object: | + # rubocop:disable Metrics/CyclomaticComplexity + def self.return_if_object(response, kind) + raise "Bad response: #{response}" \ + unless response.is_a?(Net::HTTPResponse) + return if response.is_a?(Net::HTTPNotFound) + return if response.is_a?(Net::HTTPNoContent) + # TODO(nelsonjr): Remove return of Net::HTTPForbidden from + # return_if_object once Cloud SQL bug http://b/62635365 is resolved. + # Currently the API returns 403 for objects that do not exist, even when + # the user has access to the project. This is being changed to return + # 404 as it is supposed to be. Once 404 is the correct response the + # temporary workaround should be removed. + return if response.is_a?(Net::HTTPForbidden) + result = JSON.parse(response.body) + raise_if_errors result, %w[error errors], 'message' + raise "Bad response: #{response}" unless response.is_a?(Net::HTTPOK) + # TODO(nelsonjr): Revert this check back to standard once Cloud SQL + # bug http://b/62841551 is resolved. + # Currently the sql#operation#targetLink for create returns a + # sql#database while for a delete it returns a sql#instance. + # | raise "Incorrect result: #{result['kind']} (expecting #{kind})" \ + # | unless result['kind'] == kind + raise "Incorrect result: #{result['kind']} (expecting #{kind})" \ + unless [kind, 'sql#instance'].include?(result['kind']) + result + end + # rubocop:enable Metrics/CyclomaticComplexity + + def return_if_object(response, kind) + self.class.return_if_object(response, kind) + end + Flag: + flush: | + raise('Cloud SQL flag does not match expectations.') + Instance: + # TODO(alexstephen): Document the access_api_results field. + access_api_results: true + # TODO(nelsonjr): Once bugs in the body are resolved remove this override. + return_if_object: | + # rubocop:disable Metrics/CyclomaticComplexity + def self.return_if_object(response, kind) + raise "Bad response: #{response}" \ + unless response.is_a?(Net::HTTPResponse) + return if response.is_a?(Net::HTTPNotFound) + return if response.is_a?(Net::HTTPNoContent) + # TODO(nelsonjr): Remove return of Net::HTTPForbidden from + # return_if_object once Cloud SQL Bug http://b/62635365 is resolved. + # Currently the API returns 403 for objects that do not exist, even when + # the user has access to the project. This is being changed to return + # 404 as it is supposed to be. Once 404 is the correct response the + # temporary workaround should be removed. + return if response.is_a?(Net::HTTPForbidden) + result = JSON.parse(response.body) + raise_if_errors result, %w[error errors], 'message' + raise "Bad response: #{response}" unless response.is_a?(Net::HTTPOK) + raise "Incorrect result: #{result['kind']} (expecting #{kind})" \ + unless result['kind'] == kind + result + end + # rubocop:enable Metrics/CyclomaticComplexity + + def return_if_object(response, kind) + self.class.return_if_object(response, kind) + end + resource_to_request_patch: | + unless @fetched.nil? + # Convert to pure JSON + request = JSON.parse(request.to_json) + request['settings']['settingsVersion'] = + @fetched['settings']['settingsVersion'] + end + SslCert: + flush: | + raise('Cloud SQL SSL certificate mismatch.') + Tier: + flush: | + raise('Cloud SQL tier does not match expectations.') +functions: + - !ruby/object:Provider::Config::Function + name: 'gsql_instance_ip' + description: | + Returns the IP address associated with the SQL instance managed by a + `gsql_instance` resource. + arguments: + - !ruby/object:Provider::Config::Function::Argument + name: name + type: Api::Type::String + description: 'the name of the SQL instance resource' + - !ruby/object:Provider::Config::Function::Argument + name: project + type: Api::Type::String + description: 'the project name where resource is allocated' + - !ruby/object:Provider::Config::Function::Argument + name: cred + type: Google::Authorization + description: | + the credential to use to authorize the information request + requires: + - json + - google/authorization + - google/sql/network/get + code: | + get_request = ::Google::Compute::Network::Get.new( + gsql_instance_self_link(name, project), cred + ) + response = JSON.parse(get_request.send.body) + response['ipAddresses'][0]['ipAddress'] if response.key?('ipAddresses') + helpers: | + def gsql_instance_self_link(name, project) + URI.join( + 'https://www.googleapis.com/sql/v1beta4/', + "projects/#{project}/", + "instances/#{name}" + ) + end + examples: + - gsql_instance_ip('my-db', 'my-project', $fn_auth) + notes: | + The credential parameter should be allocated with a + `gauth_credential_*_for_function` call. +bolt_tasks: + - !ruby/object:Provider::Puppet::BoltTask + name: 'clone' + description: | + Clone a CloudSQL database (requires backups and binary logs to be + previously enabled) + style: :ruby + input: :stdin + arguments: + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: name + type: Api::Type::String + description: Name of the instance to clone + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: target + type: Api::Type::String + description: Name of new instance + default: !ruby/object:Provider::Puppet::BoltTask::Argument::Default + code: '"#{name}-clone-#{Time.now.to_i}"' + display: '-clone-' + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: project + type: Api::Type::String + description: 'The project that hosts the Cloud SQL instance' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: credential + type: Api::Type::String + description: 'Path to a service account credentials file' + required: true + requires: + - google/auth/gauth_credential + - google/sql/api/gsql_instance + code: | + cred = Google::Auth::GAuthCredential \ + .serviceaccount_for_function(credential, SQL_ADM_SCOPES) + sql_instance = Google::Sql::Api::Instance.new(name, project, cred) + + begin + sql_instance.clone(target) + puts({ status: 'success' }.to_json) + exit 0 + rescue Puppet::Error => e + puts({ status: 'failure', error: e }.to_json) + exit 1 + end + - !ruby/object:Provider::Puppet::BoltTask + name: 'passwd' + description: | + Allow resetting Cloud SQL password for existing users + style: :ruby + input: :stdin + arguments: + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: name + type: Api::Type::String + description: The user that will have its password reset + default: !ruby/object:Provider::Puppet::BoltTask::Argument::Default + code: "'root'" + display: 'root' + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: host + type: Api::Type::String + description: Host ACL for user + default: !ruby/object:Provider::Puppet::BoltTask::Argument::Default + code: "'%'" + display: "'%' [all hosts]" + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: password + type: Api::Type::String + description: Password to set the user to + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: instance + type: Api::Type::String + description: Name of the SQL instance to manipulate users on + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: project + type: Api::Type::String + description: 'The project that hosts the Cloud SQL instance' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: credential + type: Api::Type::String + description: 'Path to a service account credentials file' + required: true + requires: + - google/auth/gauth_credential + - google/sql/api/gsql_user + code: | + cred = Google::Auth::GAuthCredential \ + .serviceaccount_for_function(credential, SQL_ADM_SCOPES) + sql_user = Google::Sql::Api::User.new(name, instance, project, cred) + + begin + sql_user.passwd(host, password) + puts({ status: 'success' }.to_json) + exit 0 + rescue Puppet::Error => e + puts({ status: 'failure', error: e }.to_json) + exit 1 + end +examples: !ruby/object:Api::Resource::HashArray + Database: + - database.pp + - delete_database.pp + Flag: + - flag.pp + Instance: + - instance.pp + - delete_instance.pp + SslCert: + - ssl_cert.pp + Tier: + - tier.pp + User: + - user.pp + - delete_user.pp +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/puppet/common~copy.yaml'), 4) %> + # Client-side functions require 'google/authorization' + spec/stubs/google/authorization.rb: templates/spec_lib_stub.rb.erb + compile: + lib/google/object_store.rb: google/object_store.rb + lib/google/sql/api/gsql_instance.rb: + products/sql/helpers/api_gsql_instance.rb + lib/google/sql/api/gsql_user.rb: products/sql/helpers/api_gsql_user.rb +<%= indent(include('provider/puppet/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/puppet/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData + network: !ruby/object:Api::Resource::HashArray + User: + - success1~name + - success1~title + - success2~name + - success2~title + - success3~name + - success3~title +style: + - !ruby/object:Provider::Config::StyleException + name: lib/google/sql/property/instance_mysql_replica_configuration.rb + pinpoints: + - function: initialize + exceptions: + - Metrics/MethodLength + - function: compare_fields + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gsql_instance/google.rb + pinpoints: + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - function: self.fetch_to_hash + exceptions: + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: self.return_if_object + exceptions: + - Metrics/CyclomaticComplexity + - !ruby/object:Provider::Config::StyleException + name: spec/gsql_instance + pinpoints: + - test: Instance > present > not_exist > success > title_eq_name > before + exceptions: + - Metrics/LineLength + - !ruby/object:Provider::Config::StyleException + name: spec/gsql_instance + pinpoints: + - test: Instance > present > not_exist > success > title_and_name > before + exceptions: + - Metrics/LineLength +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.2.0' + date: 2017-10-10T06:00:00-0700 + features: + - Added Bolt task `passwd` to change SQL user password. + - Added Bolt task `clone` to clone a SQL instance. + - Added `gsql_instance_ip` client function. + fixes: + - Improved validation of required parameter references + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-08-22T09:00:00-0700 + general: 'Initial release' diff --git a/products/storage/api.yaml b/products/storage/api.yaml new file mode 100644 index 000000000000..cd930845d0a4 --- /dev/null +++ b/products/storage/api.yaml @@ -0,0 +1,317 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: Google Cloud Storage +prefix: gstorage +base_url: https://www.googleapis.com/storage/v1/ +scopes: + - https://www.googleapis.com/auth/devstorage.full_control +objects: + - !ruby/object:Api::Resource + name: 'Bucket' + kind: 'storage#bucket' + base_url: b?project={{project}} + self_link: b/{{name}}?projection=full + exports: + - name + description: | + The Buckets resource represents a bucket in Google Cloud Storage. There is + a single global namespace shared by all buckets. For more information, see + Bucket Name Requirements. + + Buckets contain objects which can be accessed by their own methods. In + addition to the acl property, buckets contain bucketAccessControls, for + use in fine-grained manipulation of an existing bucket's access controls. + + A bucket is always owned by the project team owners group. + properties: + - !ruby/object:Api::Type::Array + name: 'acl' + item_type: Api::Type::String + description: 'Access controls on the bucket.' + item_type: !ruby/object:Api::Type::NestedObject + properties: +<%= indent(compile('products/storage/bucket_access_control.yaml'), 12) %> + - !ruby/object:Api::Type::Array + name: 'cors' + description: | + The bucket's Cross-Origin Resource Sharing (CORS) configuration. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::Integer + name: 'maxAgeSeconds' + description: | + The value, in seconds, to return in the Access-Control-Max-Age + header used in preflight responses. + - !ruby/object:Api::Type::Array + name: 'method' + description: | + The list of HTTP methods on which to include CORS response + headers, (GET, OPTIONS, POST, etc) Note: "*" is permitted in the + list of methods, and means "any method". + item_type: Api::Type::String + - !ruby/object:Api::Type::Array + name: 'origin' + description: | + The list of Origins eligible to receive CORS response headers. + Note: "*" is permitted in the list of origins, and means "any + Origin". + item_type: Api::Type::String + - !ruby/object:Api::Type::Array + name: 'responseHeader' + description: | + The list of HTTP headers other than the simple response headers + to give permission for the user-agent to share across domains. + item_type: Api::Type::String + # TODO(nelsonjr): Implement default object ACL. To be implemented when + # ObjectAccessControl is implemented. + #- !ruby/object:Api::Type::Array + # name: 'defaultObjectAcl' + # item_type: Api::Type::NestedObject + # description: | + # Default access controls to apply to new objects when no ACL is + # provided. + # input: true + # properties: + # | 'etag' is not applicable for state convergene. + - !ruby/object:Api::Type::String + name: 'id' + description: | + The ID of the bucket. For buckets, the id and name properities are the + same. + output: true + - !ruby/object:Api::Type::NestedObject + name: 'lifecycle' + description: | + The bucket's lifecycle configuration. + + See https://developers.google.com/storage/docs/lifecycle for more + information. + properties: + - !ruby/object:Api::Type::Array + name: 'rule' + description: | + A lifecycle management rule, which is made of an action to take + and the condition(s) under which the action will be taken. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::NestedObject + name: 'action' + description: 'The action to take.' + properties: + - !ruby/object:Api::Type::String + name: 'storageClass' + description: | + Target storage class. Required iff the type of the + action is SetStorageClass. + - !ruby/object:Api::Type::Enum + name: 'type' + description: | + Type of the action. Currently, only Delete and + SetStorageClass are supported. + values: + - 'Delete' + - 'SetStorageClass' + - !ruby/object:Api::Type::NestedObject + name: 'condition' + description: | + The condition(s) under which the action will be taken. + properties: + - !ruby/object:Api::Type::Integer + name: 'ageDays' + field: 'age' + description: | + Age of an object (in days). This condition is satisfied + when an object reaches the specified age. + - !ruby/object:Api::Type::Time + name: 'createdBefore' + description: | + A date in RFC 3339 format with only the date part (for + instance, "2013-01-15"). This condition is satisfied + when an object is created before midnight of the + specified date in UTC. + - !ruby/object:Api::Type::Boolean + name: 'isLive' + description: | + Relevant only for versioned objects. If the value is + true, this condition matches live objects; if the value + is false, it matches archived objects. + - !ruby/object:Api::Type::Array + name: 'matchesStorageClass' + description: | + Objects having any of the storage classes specified by + this condition will be matched. Values include + MULTI_REGIONAL, REGIONAL, NEARLINE, COLDLINE, STANDARD, + and DURABLE_REDUCED_AVAILABILITY. + item_type: Api::Type::String + - !ruby/object:Api::Type::Integer + name: 'numNewerVersions' + description: | + Relevant only for versioned objects. If the value is N, + this condition is satisfied when there are at least N + versions (including the live version) newer than this + version of the object. + - !ruby/object:Api::Type::String + name: 'location' + description: | + The location of the bucket. Object data for objects in the bucket + resides in physical storage within this region. Defaults to US. See + the developer's guide for the authoritative list. + - !ruby/object:Api::Type::NestedObject + name: 'logging' + description: | + The bucket's logging configuration, which defines the destination + bucket and optional name prefix for the current bucket's logs. + properties: + - !ruby/object:Api::Type::String + name: 'logBucket' + description: | + The destination bucket where the current bucket's logs should be + placed. + - !ruby/object:Api::Type::String + name: 'logObjectPrefix' + description: 'A prefix for log object names.' + - !ruby/object:Api::Type::Integer + name: 'metageneration' + description: 'The metadata generation of this bucket.' + - !ruby/object:Api::Type::String + name: 'name' + description: 'The name of the bucket' + - !ruby/object:Api::Type::NestedObject + name: 'owner' + description: | + The owner of the bucket. This is always the project team's owner + group. + properties: + - !ruby/object:Api::Type::String + name: 'entity' + description: 'The entity, in the form project-owner-projectId.' + - !ruby/object:Api::Type::String + name: 'entityId' + description: 'The ID for the entity.' + output: true + - !ruby/object:Api::Type::Integer + name: 'projectNumber' + description: 'The project number of the project the bucket belongs to.' + output: true + - !ruby/object:Api::Type::Enum + name: 'storageClass' + description: | + The bucket's default storage class, used whenever no storageClass is + specified for a newly-created object. This defines how objects in the + bucket are stored and determines the SLA and the cost of storage. + Values include MULTI_REGIONAL, REGIONAL, STANDARD, NEARLINE, COLDLINE, + and DURABLE_REDUCED_AVAILABILITY. If this value is not specified when + the bucket is created, it will default to STANDARD. For more + information, see storage classes. + values: + - :MULTI_REGIONAL + - :REGIONAL + - :STANDARD + - :NEARLINE + - :COLDLINE + - :DURABLE_REDUCED_AVAILABILITY + - !ruby/object:Api::Type::Time + name: 'timeCreated' + description: 'The creation time of the bucket in RFC 3339 format.' + output: true + - !ruby/object:Api::Type::Time + name: 'updated' + description: 'The modification time of the bucket in RFC 3339 format.' + output: true + - !ruby/object:Api::Type::NestedObject + name: 'versioning' + description: "The bucket's versioning configuration." + properties: + - !ruby/object:Api::Type::Boolean + name: 'enabled' + description: | + While set to true, versioning is fully enabled for this bucket. + - !ruby/object:Api::Type::NestedObject + name: 'website' + description: | + The bucket's website configuration, controlling how the service + behaves when accessing bucket contents as a web site. See the Static + Website Examples for more information. + properties: + - !ruby/object:Api::Type::String + name: 'mainPageSuffix' + description: | + If the requested object path is missing, the service will ensure + the path has a trailing '/', append this suffix, and attempt to + retrieve the resulting object. This allows the creation of + index.html objects to represent directory pages. + - !ruby/object:Api::Type::String + name: 'notFoundPage' + description: | + If the requested object path is missing, and any mainPageSuffix + object is missing, if applicable, the service will return the + named object from this bucket as the content for a 404 Not Found + result. + parameters: + - !ruby/object:Api::Type::String + name: 'project' + description: 'A valid API project identifier.' + input: true + - !ruby/object:Api::Type::Enum + name: 'predefinedDefaultObjectAcl' + description: | + Apply a predefined set of default object access controls to this + bucket. + + Acceptable values are: + - "authenticatedRead": Object owner gets OWNER access, and + allAuthenticatedUsers get READER access. + - "bucketOwnerFullControl": Object owner gets OWNER access, and + project team owners get OWNER access. + - "bucketOwnerRead": Object owner gets OWNER access, and project + team owners get READER access. + - "private": Object owner gets OWNER access. + - "projectPrivate": Object owner gets OWNER access, and project team + members get access according to their roles. + - "publicRead": Object owner gets OWNER access, and allUsers get + READER access. + values: + - :authenticatedRead + - :bucketOwnerFullControl + - :bucketOwnerRead + - :private + - :projectPrivate + - :publicRead + input: true + - !ruby/object:Api::Resource + name: 'BucketAccessControl' + kind: 'storage#bucketAccessControl' + base_url: b/{{bucket}}/acl + self_link: b/{{bucket}}/acl/{{entity}} + description: | + The BucketAccessControls resource represents the Access Control Lists + (ACLs) for buckets within Google Cloud Storage. ACLs let you specify who + has access to your data and to what extent. + + There are three roles that can be assigned to an entity: + + READERs can get the bucket, though no acl property will be returned, and + list the bucket's objects. WRITERs are READERs, and they can insert + objects into the bucket and delete the bucket's objects. OWNERs are + WRITERs, and they can get the acl property of a bucket, update a bucket, + and call all BucketAccessControls methods on the bucket. For more + information, see Access Control, with the caveat that this API uses + READER, WRITER, and OWNER instead of READ, WRITE, and FULL_CONTROL. + properties: +<%= indent(compile('products/storage/bucket_access_control.yaml'), 6) %> + # | - !ruby/object:Api::Resource + # | name: 'ObjectAccessControl' + # TODO(nelsonjr): Update Bucket defaultObjectAcl once ObjectAccessControl is + # implemented diff --git a/products/storage/bucket_access_control.yaml b/products/storage/bucket_access_control.yaml new file mode 100644 index 000000000000..f699200a6a36 --- /dev/null +++ b/products/storage/bucket_access_control.yaml @@ -0,0 +1,75 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +- !ruby/object:Api::Type::ResourceRef + name: 'bucket' + resource: 'Bucket' + imports: 'name' + description: 'The name of the bucket.' + required: true +- !ruby/object:Api::Type::String + name: 'domain' + description: 'The domain associated with the entity.' + output: true +- !ruby/object:Api::Type::String + name: 'email' + description: 'The email address associated with the entity.' + output: true +- !ruby/object:Api::Type::String + name: 'entity' + description: | + The entity holding the permission, in one of the following forms: + user-userId + user-email + group-groupId + group-email + domain-domain + project-team-projectId + allUsers + allAuthenticatedUsers + Examples: + The user liz@example.com would be user-liz@example.com. + The group example@googlegroups.com would be + group-example@googlegroups.com. + To refer to all members of the Google Apps for Business domain + example.com, the entity would be domain-example.com. + required: true +- !ruby/object:Api::Type::String + name: 'entityId' + description: 'The ID for the entity' +# | 'etag' is not applicable for state convergence. +- !ruby/object:Api::Type::String + name: 'id' + description: 'The ID of the access-control entry.' + output: true +- !ruby/object:Api::Type::NestedObject + name: 'projectTeam' + description: 'The project team associated with the entity' + properties: + - !ruby/object:Api::Type::String + name: 'projectNumber' + description: 'The project team associated with the entity' + - !ruby/object:Api::Type::Enum + name: 'team' + description: 'The team.' + values: + - :editors + - :owners + - :viewers +- !ruby/object:Api::Type::Enum + name: 'role' + description: 'The access permission for the entity.' + values: + - :OWNER + - :READER + - :WRITER diff --git a/products/storage/chef-e2e.yaml b/products/storage/chef-e2e.yaml new file mode 100644 index 000000000000..b7be04c78f53 --- /dev/null +++ b/products/storage/chef-e2e.yaml @@ -0,0 +1,59 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +# TODO(alexstephen): Separate plan.yaml and move to products/ +- !ruby/object:Chef::Tester + product: 'Storage' + tests: + - name: 'BucketAccessControl' + phases: + - name: 'cleanup' + apply: + - run: 'google-gstorage::tests~delete_bucket' + exits: [0, 2] + outputs: + - - 'Chef Client finished, 0/2 resources updated' + - 'Chef Client finished, 1/2 resources updated' + - name: 'create' + apply: + - run: 'google-gstorage::tests~bucket_access_control' + exits: 0 + outputs: + - - 'Chef Client finished, 2/3 resources updated' + - name: 'create{again}' + apply: + - run: 'google-gstorage::tests~bucket_access_control' + exits: 0 + outputs: + - - 'Chef Client finished, 0/3 resources updated' + - name: 'delete' + apply: + - run: 'google-gstorage::tests~delete_bucket' + exits: 0 + outputs: + - - 'Chef Client finished, 1/2 resources updated' + - name: 'delete{again}' + apply: + - run: 'google-gstorage::tests~delete_bucket' + exits: 0 + outputs: + - - 'Chef Client finished, 0/2 resources updated' + - 'Chef Client finished, 1/2 resources updated' + - !ruby/object:Chef::StandardTest + name: 'Bucket' + module: 'gstorage' diff --git a/products/storage/chef.yaml b/products/storage/chef.yaml new file mode 100644 index 000000000000..7961638d2e78 --- /dev/null +++ b/products/storage/chef.yaml @@ -0,0 +1,77 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(alexstephen): Match all objects w/ Puppet. +# TODO(alexstephen): Match all special behavior Puppet <=> Chef, e.g. cannot +# edit InstanceGroup +--- !ruby/object:Provider::Chef::Config +manifest: !ruby/object:Provider::Chef::Manifest + version: '0.1.0' + source: 'https://github.com/GoogleCloudPlatform/chef-google-storage' + issues: 'https://github.com/GoogleCloudPlatform/chef-google-storage/issues' + summary: 'A Chef cookbook to manage Google Cloud Storage resources' + description: | + This cookbook provides the built-in types and services for Chef to manage + Google Cloud Storage resources, as native Chef types. + depends: + - !ruby/object:Provider::Config::Requirements + name: 'google-gauth' + versions: '< 0.2.0' + operating_systems: +<%= indent(include('provider/chef/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray + Bucket: + overrides: + updated: _updated +examples: !ruby/object:Api::Resource::HashArray + Bucket: + - bucket.rb + - bucket~acl.rb + - delete_bucket.rb + BucketAccessControl: + - bucket_access_control.rb +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/chef/common~copy.yaml'), 4) %> + compile: +<%= indent(include('provider/chef/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/chef/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'Test Data will be automatically generated' +style: + - !ruby/object:Provider::Config::StyleException + name: libraries/google/storage/property/bucket_acl.rb + pinpoints: + - function: BucketAclCatalog.initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/bucket.rb + pinpoints: + - class: Google::GSTORAGE::Bucket + exceptions: + - Metrics/ClassLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength + - Metric/AbcSize + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: resources/bucket_access_control.rb + pinpoints: + - class: Google::GSTORAGE::BucketAccessControl + exceptions: + - Metrics/ClassLength diff --git a/products/storage/files/examples~bucket.pp b/products/storage/files/examples~bucket.pp new file mode 100644 index 000000000000..6b086d8464d9 --- /dev/null +++ b/products/storage/files/examples~bucket.pp @@ -0,0 +1,30 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +# This is a simple example of a bucket creation/ensure existence. If you want a +# more thorough setup of its ACL please refer to 'examples/bucket~acl.pp' +# manifest. +gstorage_bucket { <%= example_resource_name('puppet-storage-module-test') -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/storage/files/examples~bucket_access_control.pp b/products/storage/files/examples~bucket_access_control.pp new file mode 100644 index 000000000000..eb86e68b4747 --- /dev/null +++ b/products/storage/files/examples~bucket_access_control.pp @@ -0,0 +1,40 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% bucket_name = 'puppet-storage-module-test' -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +gstorage_bucket { <%= example_resource_name(bucket_name) -%>: + ensure => present, + project => 'google.com:graphite-playground', + credential => 'mycred', +} + +<% else # name == README.md -%> +# Bucket Access Control requires a bucket. Please ensure its existence with +# the gstorage_bucket { ... } resource +<% end # name == README.md -%> +<% res_name = 'user-nelsona@google.com' -%> +gstorage_bucket_access_control { <%= example_resource_name(res_name) -%>: + bucket => <%= example_resource_name(bucket_name) -%>, + entity => 'user-nelsona@google.com', + role => 'WRITER', + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/storage/files/examples~bucket~acl.pp b/products/storage/files/examples~bucket~acl.pp new file mode 100644 index 000000000000..fff14c72413a --- /dev/null +++ b/products/storage/files/examples~bucket~acl.pp @@ -0,0 +1,41 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gstorage_bucket { <%= example_resource_name('puppet-storage-module-test') -%>: + ensure => present, + acl => [ + { + entity => 'user-nelsona@google.com', + role => 'OWNER', + }, + { + entity => 'allAuthenticatedUsers', + role => 'READER', + }, + { + entity => 'project-owners-1068887641754', + role => 'OWNER', + }, + ], + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/storage/files/examples~cookbook~bucket.rb b/products/storage/files/examples~cookbook~bucket.rb new file mode 100644 index 000000000000..95e094f05580 --- /dev/null +++ b/products/storage/files/examples~cookbook~bucket.rb @@ -0,0 +1,30 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end # name == README.md -%> +# This is a simple example of a bucket creation/ensure existence. If you want a +# more thorough setup of its ACL please refer to 'examples/bucket~acl.pp' +# manifest. +gstorage_bucket <%= example_resource_name('storage-module-test') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/storage/files/examples~cookbook~bucket_access_control.rb b/products/storage/files/examples~cookbook~bucket_access_control.rb new file mode 100644 index 000000000000..2cce70a3c843 --- /dev/null +++ b/products/storage/files/examples~cookbook~bucket_access_control.rb @@ -0,0 +1,40 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gstorage_bucket <%= example_resource_name('storage-module-test') -%> do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end + +<% else # name == README.md -%> +# Bucket Access Control requires a bucket. Please ensure its existence with +# the gstorage_bucket { ... } resource +<% end # name == README.md -%> +<% res_name = 'user-nelsona@google.com' -%> +gstorage_bucket_access_control <%= example_resource_name(res_name) -%> do + action :create + bucket <%= example_resource_name('storage-module-test') %> + entity 'user-nelsona@google.com' + role 'WRITER' + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/storage/files/examples~cookbook~bucket~acl.rb b/products/storage/files/examples~cookbook~bucket~acl.rb new file mode 100644 index 000000000000..8a057f01ef63 --- /dev/null +++ b/products/storage/files/examples~cookbook~bucket~acl.rb @@ -0,0 +1,41 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end # name == README.md -%> +gstorage_bucket <%= example_resource_name('storage-module-test') -%> do + action :create + acl [ + { + entity: 'user-nelsona@google.com', + role: 'OWNER' + }, + { + entity: 'allAuthenticatedUsers', + role: 'READER' + }, + { + entity: 'project-owners-1068887641754', + role: 'OWNER' + } + ] + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/storage/files/examples~cookbook~delete_bucket.rb b/products/storage/files/examples~cookbook~delete_bucket.rb new file mode 100644 index 000000000000..92f2773deb9b --- /dev/null +++ b/products/storage/files/examples~cookbook~delete_bucket.rb @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +<% end # name == README.md -%> +gstorage_bucket <%= example_resource_name('storage-module-test') -%> do + action :delete + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/storage/files/examples~cookbook~readme.rb b/products/storage/files/examples~cookbook~readme.rb new file mode 100644 index 000000000000..1d2f56873ce4 --- /dev/null +++ b/products/storage/files/examples~cookbook~readme.rb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == 'README.md' -%> + +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% end -%> +<%= compile 'templates/chef/example~auth.rb.erb' -%> + +gstorage_bucket 'storage-bucket' do + action :create + project 'google.com:graphite-playground' + credential 'mycred' +end diff --git a/products/storage/files/examples~delete_bucket.pp b/products/storage/files/examples~delete_bucket.pp new file mode 100644 index 000000000000..9d382ac92ffb --- /dev/null +++ b/products/storage/files/examples~delete_bucket.pp @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% unless name == "README.md" -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> + +<% end # name == README.md -%> +gstorage_bucket { <%= example_resource_name('puppet-storage-module-test') -%>: + ensure => absent, + project => 'google.com:graphite-playground', + credential => 'mycred', +} diff --git a/products/storage/helpers/api_gstorage_object.rb b/products/storage/helpers/api_gstorage_object.rb new file mode 100644 index 000000000000..9cebd6b353ef --- /dev/null +++ b/products/storage/helpers/api_gstorage_object.rb @@ -0,0 +1,57 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/storage/network/post' + +module Google + module Storage + module Api + # A helper class to provide access to (some) Google Compute Storage API. + class Object + def initialize(name, bucket, project, cred) + @name = name + @bucket = bucket + @project = project + @cred = cred + end + + # TODO(nelsonjr): Implement this as gstorage_object { } + # TODO(nelsonjr): Make this function wait for the operation to complete + def upload(source, type) + upload_request = ::Google::Compute::Network::Post.new( + gstorage_object_upload, @cred, type, IO.read(source) + ) + response = JSON.parse(upload_request.send.body) + raise Puppet::Error, response['error']['errors'][0]['message'] \ + if response['error'] + end + + private + + STORAGE_UPLOAD_URI = + 'https://www.googleapis.com/upload/storage/v1/b'.freeze + + def gstorage_object_upload + URI.parse([ + [STORAGE_UPLOAD_URI, @bucket, 'o'].join('/'), + '?', + { + 'uploadType' => 'media', + 'name' => @name + }.map { |k, v| "#{k}=#{v}" }.join('&') + ].join) + end + end + end + end +end diff --git a/products/storage/puppet-e2e.yaml b/products/storage/puppet-e2e.yaml new file mode 100644 index 000000000000..d056ee5ef16d --- /dev/null +++ b/products/storage/puppet-e2e.yaml @@ -0,0 +1,45 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# TODO(nelsonjr): Change all objects so each test run in parallel: between same +# provider (e.g. DNS managed zone vs. record set) and across provides (e.g. +# Puppet compute address vs. Chef compute address). Once this is done make all +# tests run completely in parallel. + +- !ruby/object:Puppet::Tester + product: 'Storage' + tests: + - name: 'BucketAccessControl' + phases: + - name: 'cleanup' + apply: + - run: 'delete_bucket.pp' + exits: [0, 2] + - name: 'create' + apply: + - run: 'bucket_access_control.pp' + exits: 2 + - name: 'create{again}' + apply: + - run: 'bucket_access_control.pp' + exits: 0 + - name: 'delete' + apply: + - run: 'delete_bucket.pp' + exits: 2 + - name: 'delete{again}' + apply: + - run: 'delete_bucket.pp' + exits: 0 + - !ruby/object:Puppet::StandardTest + name: 'Bucket' diff --git a/products/storage/puppet.yaml b/products/storage/puppet.yaml new file mode 100644 index 000000000000..adcce1a2c24a --- /dev/null +++ b/products/storage/puppet.yaml @@ -0,0 +1,145 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +manifest: !ruby/object:Provider::Puppet::Manifest + version: '0.2.0' + source: 'https://github.com/GoogleCloudPlatform/puppet-google-storage' + homepage: 'https://github.com/GoogleCloudPlatform/puppet-google-storage' + issues: + 'https://github.com/GoogleCloudPlatform/puppet-google-storage/issues' + summary: 'A Puppet module to manage Google Compute Storage resources' + tags: + - google + - cloud + - storage + requires: + - !ruby/object:Provider::Config::Requirements + name: 'google/gauth' + versions: '>= 0.2.0 < 0.3.0' + operating_systems: +<%= indent(include('provider/puppet/common~operating_systems.yaml'), 4) %> +objects: !ruby/object:Api::Resource::HashArray::NONE + reason: 'No configuration needed for objects.' +bolt_tasks: + - !ruby/object:Provider::Puppet::BoltTask + name: 'upload' + description: 'Uploads a local file to Google Cloud Storage' + style: :ruby + input: :stdin + arguments: + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: name + type: Api::Type::String + description: 'The name of the remote file to upload' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: type + type: Api::Type::String + description: 'The type of the remote file (in MIME notation)' + default: "'application/octet-stream'" + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: source + type: Api::Type::String + description: 'The path to a local file to upload' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: bucket + type: Api::Type::String + description: 'The target bucket to write the file to' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: project + type: Api::Type::String + description: 'The project that owns the bucket' + required: true + - !ruby/object:Provider::Puppet::BoltTask::Argument + name: credential + type: Api::Type::String + description: 'Path to a service account credentials file' + required: true + requires: + - google/auth/gauth_credential + - google/storage/api/gstorage_object + code: | + cred = Google::Auth::GAuthCredential \ + .serviceaccount_for_function(credential, STORAGE_ADM_SCOPES) + object = Google::Storage::Api::Object.new(name, bucket, project, cred) + + begin + object.upload(source, type) + puts({ status: 'success' }.to_json) + exit 0 + rescue Puppet::Error => e + puts({ status: 'failure', error: e }.to_json) + exit 1 + end +examples: !ruby/object:Api::Resource::HashArray + Bucket: + - bucket.pp + - bucket~acl.pp + - delete_bucket.pp + BucketAccessControl: + - bucket_access_control.pp +files: !ruby/object:Provider::Config::Files + copy: +<%= indent(include('provider/puppet/common~copy.yaml'), 4) %> + compile: + lib/google/object_store.rb: google/object_store.rb + lib/google/storage/api/gstorage_object.rb: + products/storage/helpers/api_gstorage_object.rb +<%= indent(include('provider/puppet/common~compile~before.yaml'), 4) %> +<%= indent(include('provider/puppet/common~compile~after.yaml'), 4) %> +<% # common~compile~after.yaml should be the last line of compile: section %> +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'Test Data will be automatically generated' +style: + - !ruby/object:Provider::Config::StyleException + name: lib/google/storage/property/bucket_acl.rb + pinpoints: + - function: BucketAclCatalog.initialize + exceptions: + - Metrics/MethodLength + - !ruby/object:Provider::Config::StyleException + name: lib/puppet/provider/gstorage_bucket/google.rb + pinpoints: + - function: self.fetch_to_hash + exceptions: + - Metrics/AbcSize + - Metrics/MethodLength + - function: self.resource_to_hash + exceptions: + - Metrics/MethodLength + - function: resource_to_request + exceptions: + - Metrics/MethodLength +changelog: + - !ruby/object:Provider::Config::Changelog + version: '0.2.0' + date: 2017-10-10T13:00:00-0700 + features: + - Added Bolt task `upload` to upload a file to a bucket. + - !ruby/object:Provider::Config::Changelog + version: '0.1.2' + date: 2017-10-10T06:00:00-0700 + fixes: + - Improved validation of required parameter references + - !ruby/object:Provider::Config::Changelog + version: '0.1.1' + date: 2017-08-22T13:24:39-0700 + fixes: + - 'Fix typo on product name' + - !ruby/object:Provider::Config::Changelog + version: '0.1.0' + date: 2017-08-22T09:00:00-0700 + general: 'Initial release' diff --git a/provider/ansible.rb b/provider/ansible.rb new file mode 100644 index 000000000000..0e01ea2e99c1 --- /dev/null +++ b/provider/ansible.rb @@ -0,0 +1,88 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/config' +require 'provider/core' +require 'provider/ansible/manifest' + +module Provider + # Code generator for Ansible Cookbooks that manage Google Cloud Platform + # resources. + class Ansible < Provider::Core + # Settings for the provider + class Config < Provider::Config + attr_reader :manifest + def provider + Provider::Ansible + end + + def validate + super + check_property :manifest, Provider::Ansible::Manifest \ + unless manifest.nil? + end + end + + # Takes in a string and returns a multi-line string, where each line + # is less than max_length characters long and all subsequent lines are + # indented in by spaces characters + # + # Example: + # - This is a sentence + # that wraps under + # the bullet properly + def bullet_lines(line, spaces) + # - 2 for "- " + indented = wrap_field(line, spaces - 2) + indented = indented.split("\n") + indented[0] = indented[0].sub(/^../, '- ') + indented + end + + # Returns a string representation of the corresponding Python type + # for a MM type. + def python_type(type) + return 'list' if type.is_a? Api::Type::Array + return 'bool' if type.is_a? Api::Type::Boolean + return 'int' if type.is_a? Api::Type::Integer + 'str' + end + + private + + def generate_resource(data) + target_folder = data[:output_folder] + FileUtils.mkpath target_folder + name = Google::StringUtils.underscore(data[:name]) + generate_resource_file data.clone.merge( + default_template: 'templates/ansible/resource.erb', + out_file: File.join(target_folder, + "lib/ansible/modules/cloud/google/#{name}.py") + ) + end + + def generate_resource_tests(data) end + + def generate_network_datas(data, object) end + + def generate_base_property(data) end + + def generate_simple_property(type, data) end + + def generate_typed_array(type, data) end + + def emit_nested_object(data) end + + def emit_resourceref_object(data) end + end +end diff --git a/provider/ansible/manifest.rb b/provider/ansible/manifest.rb new file mode 100644 index 000000000000..8d988ef69f3d --- /dev/null +++ b/provider/ansible/manifest.rb @@ -0,0 +1,36 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + class Ansible < Provider::Core + # Metadata for manifest.json + class Manifest < Api::Object + attr_reader :metadata_version + attr_reader :status + attr_reader :supported_by + attr_reader :requirements + attr_reader :version_added + attr_reader :author + + def validate + check_property :metadata_version, String + check_property :status, Array + check_property :supported_by, String + check_property :requirements, Array + check_property_list :requirements, @requirements, String + check_property :version_added, String + check_property :author, String + end + end + end +end diff --git a/provider/chef.rb b/provider/chef.rb new file mode 100644 index 000000000000..2ebbfffdd8a1 --- /dev/null +++ b/provider/chef.rb @@ -0,0 +1,361 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/config' +require 'provider/core' +require 'provider/chef/manifest' +require 'provider/chef/test_catalog' + +module Provider + # Code generator for Chef Cookbooks that manage Google Cloud Platform + # resources. + class Chef < Provider::Core + TEST_FOLDER = 'recipes'.freeze + # Settings for the provider + class Config < Provider::Config + attr_reader :manifest + attr_reader :operating_systems + # TODO(alexstephen): Convert this to a regular function generator + # like Puppet. + attr_reader :functions + + def provider + Provider::Chef + end + + def validate + super + check_property :manifest, Provider::Chef::Manifest \ + unless manifest.nil? + check_property_list :operating_systems, @operating_systems, + Provider::Config::OperatingSystem + end + end + + # A custom client side function for Chef + class Function < Provider::Config::Function + attr_reader :search_paths + + def validate + super + check_property_list :search_paths, @search_paths, + Provider::Chef::SearchPath + end + end + + # A search path for client side functions in Chef. + class SearchPath < Api::Object::Named + attr_reader :path + attr_reader :comment + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def prop_decl(prop) + return 'kind_of: [TrueClass, FalseClass]' if prop.type == 'Boolean' + return 'Float' if prop.type == 'Double' + + # Chef manifest files show Nested Objects as Hash, or Google::Property + # They will be immediately coerced into a Google::Property in the end + return "[Hash, ::#{prop.property_type.gsub(':Property:', ':Data:')}]" \ + if prop.is_a? Api::Type::NestedObject + + return "[String, ::#{prop.property_type.gsub(':Property:', ':Data:')}]" \ + if prop.is_a? Api::Type::ResourceRef + + if prop.type == 'Enum' + return format([ + ["equal_to: %w[#{prop.values.join(' ')}]"], + ((1..prop.values.length - 1).to_a.map do |i| + ["equal_to: %w[#{prop.values.slice(0, i).join(' ')}", + indent("#{prop.values.slice(i, prop.values.length).join(' ')}]", + 13)] + end) + ].flatten(1), 0, 16) + end + + prop.type + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + def label_name(product) + return product.label_override unless product.label_override.nil? + Google::StringUtils.underscore(product.name) + .split('_') + .map { |x| x[0] } + .join + .concat('_label') + end + + # Returns a list of all resource types being tested + # ChefSpec requires this list to include all ResourceRefs + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def step_into_list(object, indent, start_indent) + steps = [object.out_name].concat(object.all_resourcerefs + .map(&:resource_ref) + .map(&:out_name).reverse).uniq + + return indent("step_into: '#{steps[0]}',", indent) if steps.length == 1 + + format( + [ + ["step_into: %w[#{steps.join(' ')}],"], + ["step_into: %w[#{steps.slice(0..-2).join(' ')}", + indent("#{steps.last(1).join(' ')}],", 14)], # 14 = step_into: %w[ + ["step_into: %w[#{steps.slice(0..-3).join(' ')}", + indent("#{steps.last(2).join(' ')}],", 14)], # 14 = step_into: %w[ + [ + "step_into: %w[#{steps[0]}", + indent(steps.slice(1..-2), 14), # 14 = step_into: %w[ + indent("#{steps.last(1).join}],", 14) + ] + ], indent, start_indent + ) + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + def generate_user_agent(product, file_name) + emit_user_agent( + product, nil, + [ + 'TODO(alexstephen): Check how to get the original Chef user agent.', + 'TODO(alexstephen): Check how to fetch cookbook version.' + ], + file_name + ) + end + + # rubocop:disable Metrics/MethodLength + def emit_coerce(product_ns, class_name, spaces_used = 0) + type = "::Google::#{product_ns}::Property::#{class_name}" + lines(format([ + [ + 'def self.coerce', + indent("->(x) { #{type}.catalog_parse(x) }", 2), + 'end' + ], + [ + 'def self.coerce', + indent('lambda do |x|', 2), + indent(indent("#{type}.catalog_parse(x)", 2), 2), + indent('end', 2), + 'end' + ], + [ + 'def self.coerce', + indent('lambda do |x|', 2), + indent("type = #{type}", 4), + indent('type.catalog_parse(x)', 4), + indent('end', 2), + 'end' + ] + ], spaces_used), 1) + end + # rubocop:enable Metrics/MethodLength + + def property_out_name(prop) + return label_name(prop.__resource) if prop.name == 'name' + override = Google::HashUtils.navigate(@config.objects, + [prop.__resource.name, + 'overrides', prop.name]) + override || prop.out_name + end + + def compile_end2end_tests(output_folder) + compile_file_map( + output_folder, + @config.examples, + lambda do |_object, file| + # Tests go into hidden folder because we don't need to expose + # to regular Chef users. + ["recipes/tests~#{file}", + "products/#{@api.prefix[1..-1]}/files/examples~cookbook~#{file}"] + end + ) + end + + private + + def generate_simple_property(type, data) + { + source: File.join('templates', 'chef', 'property', "#{type}.rb.erb"), + target: File.join('libraries', 'google', data[:product_name], + 'property', "#{type}.rb") + } + end + + def generate_base_property(data) end + + def emit_resourceref_object(data) + target = data[:property].property_file + { + source: File.join('templates', 'chef', 'property', + 'resourceref.rb.erb'), + target: "libraries/#{target}.rb", + overrides: data.clone.merge( + class_name: data[:property].property_class.last + ) + } + end + + def generate_typed_array(data, prop) + type = Module.const_get(prop.item_type).new(prop.name).type + file = Google::StringUtils.underscore(type) + prop_map = [] + prop_map << { + source: File.join('templates', 'chef', 'property', + 'array_typed.rb.erb'), + target: File.join('libraries', 'google', data[:product_name], + 'property', "#{file}_array.rb"), + overrides: { type: type } + } + prop_map << generate_base_array(data) + prop_map + end + + def generate_base_array(data) + { + source: File.join('templates', 'chef', 'property', 'array.rb.erb'), + target: File.join('libraries', 'google', data[:product_name], + 'property', 'array.rb') + } + end + + def emit_nested_object(data) + target = if data[:emit_array] + data[:property].item_type.property_file + else + data[:property].property_file + end + { + source: File.join('templates', 'chef', 'property', + 'nested_object.rb.erb'), + target: "libraries/#{target}.rb", + overrides: emit_nested_object_overrides(data) + } + end + + def emit_nested_object_overrides(data) + data.clone.merge( + field_name: Google::StringUtils.camelize(data[:field], :upper), + object_type: Google::StringUtils.camelize(data[:obj_name], :upper), + product_ns: Google::StringUtils.camelize(data[:product_name], :upper), + class_name: if data[:emit_array] + data[:property].item_type.property_class.last + else + data[:property].property_class.last + end + ) + end + + def generate_resource(data) + target_folder = File.join(data[:output_folder], 'resources') + FileUtils.mkpath target_folder + name = Google::StringUtils.underscore(data[:object].name) + generate_resource_file data.clone.merge( + default_template: 'templates/chef/resource.erb', + out_file: File.join(target_folder, "#{name}.rb") + ) + end + + def generate_resource_tests(data) + target_folder = File.join(data[:output_folder], 'spec') + FileUtils.mkpath target_folder + name = Google::StringUtils.underscore(data[:object].name) + generate_resource_file data.clone.merge( + default_template: 'templates/chef/resource_spec.erb', + out_file: File.join(target_folder, "#{name}_spec.rb") + ) + end + + def compile_examples(output_folder) + compile_file_map( + output_folder, + @config.examples, + lambda do |_object, file| + ["recipes/examples~#{file}", + "products/#{@api.prefix[1..-1]}/files/examples~cookbook~#{file}"] + end + ) + end + + def google_lib_basic(file, product_ns) + google_lib_basic_files(file, product_ns, 'libraries', 'google') + end + + def google_lib_network(file, product_ns) + google_lib_network_files(file, product_ns, 'libraries', 'google') + end + + def example_resource_name_prefix + 'chef-e2e-' + end + + def test_file?(file) + file.include? 'tests~' + end + + # Builds the properties for a nested object of any depth + # This returns an arrays of strings that represent Markdown formatted + # properties for the nested object and all nested objects beneath it + # Requires: + # prop: A property of type nested object. + # current_path: A string representing all layers above this current + # property. This string will usually be the output names of + # all properties above the current property joined by + # '/' (ex. first_level/second_level) or an array denoted + # by [] (ex. array_of_nested_props[]) + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def build_nested_object(prop, current_path) + object_lines = [] + prop.properties.each do |nested_prop| + next_path = "#{current_path}/#{nested_prop.out_name}" + object_lines << lines(["* `#{next_path}`"].join(' ')) + + object_lines << lines(wrap_field([ + ('Required.' if nested_prop.required), + ('Output only.' if nested_prop.output), + nested_prop.description + ].compact.join(' '), 0), 1) + + if nested_prop.is_a? Api::Type::NestedObject + object_lines.concat(build_nested_object(nested_prop, next_path)) + elsif nested_prop.is_a?(Api::Type::Array) && + nested_prop.item_type.is_a?(Api::Type::NestedObject) + object_lines.concat(build_nested_object(nested_prop.item_type, + "#{next_path}[]")) + end + end + object_lines + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + # Emits all the Chef client functions available for use by end users. + def generate_client_function(output_folder, fn) + target_folder = File.join(output_folder, 'libraries', 'google', + 'functions') + { + fn: fn, + target_folder: target_folder, + template: 'templates/chef/function.erb', + output_folder: output_folder, + out_file: File.join(target_folder, "#{fn.name}.rb") + } + end + end +end diff --git a/provider/chef/bundle.rb b/provider/chef/bundle.rb new file mode 100644 index 000000000000..d48b979e1cfe --- /dev/null +++ b/provider/chef/bundle.rb @@ -0,0 +1,82 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'json' +require 'provider/config' +require 'provider/core' + +module Provider + # A provider to generate the "bundle" module. + class ChefBundle < Provider::Core + # The configuration for the "bundle" module (in chef.yaml) + class Config < Provider::Config + attr_reader :manifest + + def provider + Provider::ChefBundle + end + + def validate + check_property :manifest, Provider::ChefBundle::Manifest + end + end + + # A manifest for the "bundle" module + class Manifest < Provider::Chef::Manifest + def validate + @requires = [] + super + end + end + + def generate(output_folder, _types) + # Let's build all the dependencies off of the products we found on our + # path and has the corresponding provider.yaml file + @config.manifest.depends.concat( + products.map do |k, v| + Provider::Config::Requirements.create( + "google-#{k.prefix}", + "< #{next_version(v.manifest.version)}" + ) + end + ) + + copy_files(output_folder) + compile_files(output_folder) + end + + def products + @products ||= begin + prod_map = Dir['products/**/chef.yaml'] + .reject { |f| f.include?('bundle') } + .map do |product_config| + product = Api::Compiler.new( + File.join(File.dirname(product_config), 'api.yaml') + ).run + product.validate + config = Provider::Config.parse(product_config, product) + config.validate + + [product, config] + end + Hash[prod_map.sort_by { |p| p[0].prefix }] + end + end + + private + + def next_version(version) + [Gem::Version.new(version).bump, 0].join('.') + end + end +end diff --git a/provider/chef/common~compile~after.yaml b/provider/chef/common~compile~after.yaml new file mode 100644 index 000000000000..d3633c48cef2 --- /dev/null +++ b/provider/chef/common~compile~after.yaml @@ -0,0 +1,19 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# These files contains code that needs to be compiled before being delivered to +# the final module tree structure: + +# Contributing file has to be last, as it has references to other +# automatically generated files. +'CONTRIBUTING.md': 'templates/CONTRIBUTING.md.erb' diff --git a/provider/chef/common~compile~before.yaml b/provider/chef/common~compile~before.yaml new file mode 100644 index 000000000000..dcf18ad8b77b --- /dev/null +++ b/provider/chef/common~compile~before.yaml @@ -0,0 +1,34 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# These files contains code that needs to be compiled before being delivered to +# the final module tree structure: + +'.gitignore': 'templates/dot~gitignore' +'.rubocop.yml': 'templates/chef/dot~rubocop~root.yml' +'Berksfile': 'templates/chef/Berksfile.erb' +'Gemfile': 'templates/chef/Gemfile.erb' +'LICENSE': 'templates/LICENSE' +'README.md': 'templates/chef/README.md.erb' +'chefignore': 'templates/chef/chefignore.erb' +'libraries/__init__.rb': 'templates/chef/init_library_path.rb.erb' +'metadata.rb': 'templates/chef/metadata.rb.erb' +'spec/bundle.rb': 'templates/bundle.rb.erb' +'spec/cookbooks/google-gauth/metadata.rb': + 'templates/chef/google-gauth~metadata.rb.erb' +'spec/fake_cred.rb': 'templates/chef/credential.erb' +'spec/fake_auth.rb': 'templates/fake_auth.erb' +'spec/foodcritic_spec.rb': 'templates/chef/foodcritic_spec.rb.erb' +'spec/spec_helper.rb': 'templates/chef/spec_helper.rb.erb' +'spec/test_constants.rb': 'templates/test_constants.rb.erb' +'recipes/README.md': 'templates/chef/recipe_README.md.erb' diff --git a/provider/chef/common~copy.yaml b/provider/chef/common~copy.yaml new file mode 100644 index 000000000000..71e5ac7461ec --- /dev/null +++ b/provider/chef/common~copy.yaml @@ -0,0 +1,23 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# These files are okay to be copied "AS-IS" to the module's final destination: + +'Gemfile.lock': 'templates/chef/Gemfile.lock' +'LICENSE': 'templates/LICENSE' +'libraries/google/hash_utils.rb': 'google/hash_utils.rb' +'libraries/google/string_utils.rb': 'google/string_utils.rb' +'spec/data/poor_recipe.rb': 'templates/chef/poor_recipe.rb' +'spec/hash_utils_spec.rb': 'spec/hash_utils_spec.rb' +'spec/hash_utils_spec.rb': 'spec/hash_utils_spec.rb' +'spec/string_utils_spec.rb': 'spec/string_utils_spec.rb' diff --git a/provider/chef/common~operating_systems.yaml b/provider/chef/common~operating_systems.yaml new file mode 100644 index 000000000000..4724bd077803 --- /dev/null +++ b/provider/chef/common~operating_systems.yaml @@ -0,0 +1,54 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# List of operating systems that are (allegedly) supported by all modules + +- !ruby/object:Provider::Config::OperatingSystem + name: RedHat + versions: + - '6' + - '7' +- !ruby/object:Provider::Config::OperatingSystem + name: CentOS + versions: + - '6' + - '7' +- !ruby/object:Provider::Config::OperatingSystem + name: Debian + versions: + - '7' + - '8' +- !ruby/object:Provider::Config::OperatingSystem + name: Ubuntu + versions: + - '12.04' + - '14.04' + - '16.04' + - '16.10' +- !ruby/object:Provider::Config::OperatingSystem + name: SLES + versions: + - '11-sp4' + - '12-sp2' +- !ruby/object:Provider::Config::OperatingSystem + name: openSUSE + versions: + - '13' +- !ruby/object:Provider::Config::OperatingSystem + name: Windows Server + versions: + - '2008 R2' + - '2012 R2' + - '2012 R2 Core' + - '2016 R2' + - '2016 R2 Core' diff --git a/provider/chef/manifest.rb b/provider/chef/manifest.rb new file mode 100644 index 000000000000..3280c5735730 --- /dev/null +++ b/provider/chef/manifest.rb @@ -0,0 +1,42 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/core' + +module Provider + class Chef < Provider::Core + # Metadata for manifest.json + class Manifest < Api::Object + attr_reader :depends + attr_reader :description + attr_reader :issues + attr_reader :operating_systems + attr_reader :source + attr_reader :summary + attr_reader :version + + def validate + check_property :depends, Array + check_property :description, String + check_property :issues, String + check_property :operating_systems, Array + check_property :source, String + check_property :summary, String + check_property :version, String + check_property_list :depends, @depends, Provider::Config::Requirements + check_property_list :operating_systems, @operating_systems, + Provider::Config::OperatingSystem + end + end + end +end diff --git a/provider/chef/network_request.yaml b/provider/chef/network_request.yaml new file mode 100644 index 000000000000..c2f472d6f4c5 --- /dev/null +++ b/provider/chef/network_request.yaml @@ -0,0 +1,19 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +libraries/google/request/post.rb: google/request/post.rb +libraries/google/request/delete.rb: google/request/delete.rb +libraries/google/request/put.rb: google/request/put.rb +spec/request_post_spec.rb: spec/request_post_spec.rb +spec/request_delete_spec.rb: spec/request_delete_spec.rb +spec/request_put.rb: spec/request_put.rb diff --git a/provider/chef/outline.rb b/provider/chef/outline.rb new file mode 100644 index 000000000000..cfb18ea5a056 --- /dev/null +++ b/provider/chef/outline.rb @@ -0,0 +1,123 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + # Formats objects into README-style outlines that are puppet-lint compliant. + class ChefOutline + def initialize(provider) + @provider = provider + end + + def generate(object) + extra = { + project: 'string', + credential: 'reference to gauth_credential' + } + + [ + "#{object.out_name} 'id-for-resource' do", + @provider.indent_array(emit_manifest_block(object.all_user_properties, + extra), 2), + 'end' + ] + end + + private + + def emit_manifest_block(props, extra) + max_key = max_key_length(props, extra) + + props.map { |p| formatter(p) } + .to_h + .merge(extra.map { |k, v| [k.id2name, v] }.to_h) + .sort_by { |k, _v| sort_by_manifest_key(k) } + .map do |k, v| + [k, ' ' * [0, max_key - k.length].max, ' ', + v].join + end + end + + def sort_by_manifest_key(key) + if key.start_with?('action') + '_aaa_action' # forces ensure to be the first item + elsif key.start_with?('credential') + 'zzz_credential' # forces credential to be the last item + elsif key.start_with?('project') + 'zzy_project' # forces project to be the next-to-last item + else + key + end + end + + def formatter(p) + return p if p.is_a? String + [p.out_name, handlers.fetch(p.class, ->(v) { v.type.downcase }).call(p)] + end + + def format_values(start, values, stop, last_comma = true) + [start, indent_list(values, 2, last_comma), stop].join("\n") + end + + def handlers + { + Api::Type::NestedObject => ->(v) { emit_nested(v) }, + Api::Type::Enum => ->(v) { emit_enum(v) }, + Api::Type::Array => ->(v) { emit_array(v) }, + Api::Type::ResourceRef => + ->(v) { "reference to #{v.resource_ref.out_name}" } + }.freeze + end + + def emit_enum(p) + return 'Enum' if p.values.empty? + return p.values[0].to_s if p.values.length == 1 + + values = p.values.map { |val| "'#{val}'" } + + "#{values.first(values.size - 1).join(', ')} or #{values.last}" + end + + def emit_nested(p) + format_values('{', + emit_manifest_block(p.properties, {}), + '}') + end + + def emit_array(p) + item = if p.item_type.is_a? Api::Type::NestedObject + emit_nested(p.item_type) + elsif p.item_type.is_a? Api::Type::ResourceRef + "reference to a #{p.item_type.resource_ref.out_name}" + else + p.item_type.split('::').last.downcase + end + + format_values('[', [item, '...'], ']', false) + end + + def quote_string(s) + @provider.quote_string(s) + end + + def indent_list(list, indent, last_comma = true) + @provider.indent_list(list, indent, last_comma) + end + + def max_key_length(props, extra) + max_key_prop = props.max_by { |p| p.out_name.length } + max_key = max_key_prop.nil? ? 0 : max_key_prop.out_name.length + return max_key if extra.empty? + [max_key, extra.max_by { |k, _v| k.length }.first.length].max + end + end +end diff --git a/provider/chef/test_catalog.rb b/provider/chef/test_catalog.rb new file mode 100644 index 000000000000..ca4e0cbd9435 --- /dev/null +++ b/provider/chef/test_catalog.rb @@ -0,0 +1,166 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/test_data/generator' +require 'provider/test_data/formatter' + +module Provider + # rubocop:disable Metrics/ClassLength + # Formats objects into Puppet manifests that are puppet-lint compliant. + class ChefTestCatalogFormatter < Provider::TestData::Formatter + def initialize(provider) + super(Provider::TestData::Generator.new) + @provider = provider + end + + def generate_all_objects(objects, base_name, kind, extra) + objects.map do |object| + if object.object.name == base_name + generate_object(object.object, "title#{object.seed}", kind, + object.seed, extra) + else + generate_ref(object.object, object.seed) + end + end + end + + def generate_object(object, title, kind, seed, extra) + props = select_properties(object.all_user_properties, kind, extra) + + extra = { + project: "'test project\##{seed} data'", + credential: "'mycred'" + }.merge(extra) + + [ + "#{object.out_name} '#{title}' do", + indent_array(emit_manifest_block(props, seed, extra, {}, + first_level: true), 2), + 'end' + ] + end + + # Generates a resource block for a resource ref. + # Requires the ResourceRef and an index. + def generate_ref(ref, index) + ref_name = Google::StringUtils.underscore(ref.name) + generate_object(ref, "resource(#{ref_name},#{index})", :resource, + index, action: ':create') + end + + # Generates a block of Chef recipe code + # Valid options: + # first level: Says if this is the first level being generated. + # rubocop:disable Metrics/AbcSize + def emit_manifest_block(props, seed, extra, ctx, opts = {}) + manifest = props.map do |p| + method = -> { @provider.property_out_name(p) } + emit_manifest_assign(p, seed, ctx, method, opts) + end + manifest.to_h + .merge(extra.map { |k, v| [k.id2name, v] }.to_h) + .sort_by { |k, _v| sort_by_manifest_key(k) } + .map do |k, v| + if v.is_a?(String) && v[0] == '{' && opts[:first_level] + # First level nested objects need a () instead of {} + [k, '(', [v].flatten.join("\n"), ')'].join + else + [k, ' ', [v].flatten.join("\n")].join + end + end + end + # rubocop:enable Metrics/AbcSize + + # Generates a key value pair for a property depending on its type + # Valid options: + # first_level: Says if this is the first level being generated + def emit_manifest_assign(prop, seed, ctx, prop_field_name, opts = {}) + # Chef name field must use label_name + if prop.name == 'name' && opts[:first_level] + [@provider.label_name(prop.__resource), + formatter(prop.class, @datagen.value(prop.class, prop, seed))] + else + super(prop, seed, ctx, prop_field_name) + end + end + + def sort_by_manifest_key(key) + if key.start_with?('action') + '_aaa_action' # forces ensure to be the first item + elsif key.start_with?('credential') + 'zzz_credential' # forces credential to be the last item + elsif key.start_with?('project') + 'zzy_project' # forces project to be the next-to-last item + else + key + end + end + + def formatter(type, value) + raise "Unknown type '#{type}'" unless handlers.key?(type) + handlers[type].call(value) + end + + def format_values(start, values, stop, commas = false) + if commas + [start, indent_array(values, 2), stop].join("\n") + else + [start, @provider.indent_list(values, 2), stop].join("\n") + end + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def handlers + { + Api::Type::Boolean => ->(v) { v ? 'true' : 'false' }, + Api::Type::Constant => ->(v) { quote_string(v.upcase) }, + Api::Type::Double => ->(v) { v }, + Api::Type::Enum => + ->(v) { quote_string(v.is_a?(Symbol) ? v.id2name : v.to_s) }, + Api::Type::Integer => ->(v) { v }, + Api::Type::String => ->(v) { quote_string(v) }, + Api::Type::Time => ->(v) { quote_string(v.iso8601) }, + Api::Type::Array => ->(v) { format_values('[', v, ']') }, + Api::Type::NestedObject => ->(v) { format_values('{', v, '}') }, + Api::Type::NameValues => ->(v) { format_values('{', v, '}') }, + Api::Type::ResourceRef => ->(v) { quote_string(v) }, + Api::Type::Array::STRING_ARRAY_TYPE => + ->(v) { ['[', v.map { |e| quote_string(e) }.join(', '), ']'].join }, + Api::Type::Array::RREF_ARRAY_TYPE => + ->(v) { ['[', v.call(exported_values: false).join(', '), ']'].join } + }.freeze + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + def quote_string(s) + @provider.quote_string(s) + end + + def indent_array(list, indent) + @provider.indent_array(list, indent).join("\n") + end + + def emit_nested(prop, seed, ctx) + props = prop.properties + props.map { |p| emit_manifest_assign(p, seed, ctx, p.method(:out_name)) } + .to_h + .sort_by { |k, _v| sort_by_manifest_key(k) } + .map do |k, v| + ["#{k}:", [v].flatten.join("\n")].join(' ') + end + end + end + # rubocop:enable Metrics/ClassLength +end diff --git a/provider/compiler.rb b/provider/compiler.rb new file mode 100644 index 000000000000..47c726896243 --- /dev/null +++ b/provider/compiler.rb @@ -0,0 +1,84 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'binding_of_caller' +require 'erb' + +module Provider + # Helper functions to aid compiling and including files + module Compiler + def compiler + "#{self.class.name.split('::').last.downcase}-codegen".freeze + end + + def include(file) + get_helper_file(file) + end + + def compile(file, caller_frame = 1) + ctx = binding.of_caller(caller_frame) + has_erbout = ctx.local_variables.include?(:_erbout) + content = ctx.local_variable_get(:_erbout) if has_erbout # save code + ctx.local_variable_set(:compiler, compiler) + Google::LOGGER.info "Compiling #{file}" + input = ERB.new get_helper_file(file), nil, '-%>' + compiled = input.result(ctx) + ctx.local_variable_set(:_erbout, content) if has_erbout # restore code + compiled + end + + def compile_if(config, node) + file = Google::HashUtils.navigate(config, node) + compile(file, 2) unless file.nil? + end + + def indent(text, spaces) + indent_array(text, spaces).join("\n") + end + + def indent_list(text, spaces) + indent_array(text, spaces).join(",\n") + end + + def indent_array(text, spaces) + return [] if text.nil? + lines = text.class <= Array ? text : text.split("\n") + lines.map do |line| + if line.class <= Array + indent(line, spaces) + elsif line.include?("\n") + indent(line.split("\n"), spaces) + elsif line.strip.empty? + '' + else + ' ' * spaces + line.gsub(/\n/, "\n" + ' ' * spaces) + end + end + end + + private + + def get_helper_file(file, remove_copyright_notice = true) + content = IO.read(file) + remove_copyright_notice ? strip_copyright_notice(content) : content + end + + def strip_copyright_notice(content, comment_marker = '#') + lines = content.split("\n") + return content unless lines[0].include?('Copyright 20') + lines = lines.drop(1) while lines[0].start_with?(comment_marker) + lines = lines.drop(1) while lines[0].strip.empty? + lines.join("\n") + end + end +end diff --git a/provider/config.rb b/provider/config.rb new file mode 100644 index 000000000000..238436264659 --- /dev/null +++ b/provider/config.rb @@ -0,0 +1,231 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'api/object' +require 'provider/compiler' +require 'compile/core' + +module Provider + # Settings for the provider + class Config < Api::Object + include Compile::Core + extend Compile::Core + + attr_reader :objects + attr_reader :examples + attr_reader :properties # TODO(nelsonjr): Remove this once bug 193 is fixed. + attr_reader :tests + attr_reader :test_data + attr_reader :files + attr_reader :style + attr_reader :changelog + attr_reader :functions + + # A custom client side function provided by the module. + class Function < Api::Object::Named + attr_reader :description + attr_reader :arguments + attr_reader :requires + attr_reader :code + attr_reader :helpers + attr_reader :examples + attr_reader :notes + + def validate + super + check_property_list :requires, @requires, String + check_property :code, String + check_property_list :arguments, @arguments, + Provider::Config::Function::Argument + check_property :helpers, String unless @helpers.nil? + end + + # An argument required by the function being provided by the module. + class Argument < Api::Object::Named + attr_reader :description + attr_reader :type + + def validate + super + check_property :description, String + check_property :type, String + end + end + end + + # Operating system supported by the module + class OperatingSystem < Api::Object::Named + attr_reader :versions + + def validate + super + check_property :versions + end + + def all_versions + [@name, @versions.join(', ')].join(' ') + end + end + + # Reference to a module required by the module + class Requirements < Api::Object::Named + attr_reader :versions + + def self.create(name, versions) + Requirements.new(name, versions) + end + + def validate + super + check_property :versions + end + + private + + def initialize(name, versions) + @name = name + @versions = versions + end + end + + # Reference to a module required by the module + class TestData < Api::Object + attr_reader :network + + # A dummy class that identifies the property as deliberately unused. + class NONE < TestData + include Api::Object::MissingObject + + def validate + @network = Api::Resource::HashArray.new + super + end + end + + def validate + super + check_property :network, Api::Resource::HashArray + end + end + + # List of files to copy or compile into target module + class Files < Api::Object + attr_reader :compile + attr_reader :copy + attr_reader :permissions + + # A dummy class that identifies the property as deliberately unused. + class NONE < Files + include Api::Object::MissingObject + end + + def validate + super + check_property :compile, Hash unless @compile.nil? + check_property :copy, Hash unless @copy.nil? + check_property_list \ + :permissions, @permissions, Provider::Config::Permission + end + end + + # Represents a permission to be set at the generated module + class Permission < Api::Object + attr_reader :path + attr_reader :acl + + def validate + super + check_property :path, String + check_property :acl, String + end + end + + # Identifies a location where a code style exception happened. This is used + # to guide the compiler to produce linter correct code, i.e. adding the + # necessary guards to avoid violations. + class StyleException < Api::Object::Named + attr_reader :pinpoints + + def validate + super + check_property :pinpoints, Array + check_property_list :pinpoints, @pinpoints, Hash + end + end + + # Identifies all changes releted to a release of the compiled artifact. + class Changelog < Api::Object + attr_reader :version + attr_reader :date + attr_reader :general + attr_reader :features + attr_reader :fixes + + def validate + super + check_property :version, String + check_property :date, Time + check_property :general, String unless @general.nil? + check_property_list :features, @features, String + check_property_list :fixes, @fixes, String + + raise "Required general/features/fixes for change #{@version}." \ + if @general.nil? && @features.nil? && @fixes.nil? + end + end + + def self.parse(cfg_file, api = nil) + # Compile step #1: compile with generic class to instantiate target class + source = compile(cfg_file) + config = Google::YamlValidator.parse(source) + raise "Config #{cfg_file}(#{config.class}) is not a Provider::Config" \ + unless config.class <= Provider::Config + # Compile step #2: Now that we have the target class, compile with that + # class features + source = config.compile(cfg_file, 0) + config = Google::YamlValidator.parse(source) + config.spread_api config, api, [], '' unless api.nil? + config.validate + config + end + + def provider + raise "#{self.class}#provider not implemented" + end + + def validate + super + check_property :examples, Api::Resource::HashArray + check_property :files, Provider::Config::Files + check_property :objects, Api::Resource::HashArray + check_property :test_data, Provider::Config::TestData + check_property :tests, Api::Resource::HashArray unless @tests.nil? + check_property_list :style, @style, Provider::Config::StyleException + check_property_list :changelog, @changelog, Provider::Config::Changelog + check_property_list :functions, @functions, Provider::Config::Function + end + + # Provides the API object to any type that requires, e.g. for validation + # purposes, such as Api::Resource::HashArray which enforces that the keys + # are necessarily objects defined in the API. + def spread_api(object, api, visited, indent) + object.instance_variables.each do |var| + var_value = object.instance_variable_get(var) + next if visited.include?(var_value) + visited << var_value + var_value.consume_api api if var_value.respond_to?(:consume_api) + spread_api(var_value, api, visited, indent) + end + end + end +end diff --git a/provider/core.rb b/provider/core.rb new file mode 100644 index 000000000000..8c3b61b690a3 --- /dev/null +++ b/provider/core.rb @@ -0,0 +1,726 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'compile/core' +require 'dependencies/dependency_graph' +require 'fileutils' +require 'google/logger' +require 'pathname' +require 'provider/properties' +require 'provider/end2end/core' +require 'provider/test_matrix' +require 'provider/test_data/spec_formatter' +require 'provider/test_data/constants' +require 'provider/test_data/property' +require 'provider/test_data/create_data' +require 'provider/test_data/expectations' + +module Provider + DEFAULT_FORMAT_OPTIONS = { + indent: 0, + start_indent: 0, + max_columns: 80, + quiet: false + }.freeze + + # Basic functionality for code generator providers. Provides basic services, + # such as compiling and including files, formatting data, etc. + class Core + include Compile::Core + include Provider::Properties + include Provider::End2End::Core + + attr_reader :test_data + + def initialize(config, api) + @config = config + @api = api + @property = Provider::TestData::Property.new(self) + @constants = Provider::TestData::Constants.new(self) + @data_gen = Provider::TestData::Generator.new + @create_data = Provider::TestData::CreateData.new(self, @data_gen) + @prop_data = Provider::TestData::Expectations.new(self, @data_gen) + @generated = [] + @sourced = [] + end + + # Main entry point for the compiler. As this method is simply invoking other + # generators, it is okay to ignore Rubocop warnings about method size and + # complexity. + # + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + def generate(output_folder, types) + generate_objects(output_folder, types) + generate_client_functions(output_folder) unless @config.functions.nil? + copy_files(output_folder) \ + unless @config.files.nil? || @config.files.copy.nil? + compile_examples(output_folder) unless @config.examples.nil? + compile_end2end_tests(output_folder) unless @config.examples.nil? + compile_network_data(output_folder) \ + unless @config.test_data.nil? || @config.test_data.network.nil? + compile_changelog(output_folder) unless @config.changelog.nil? + # Compilation has to be the last step, as some files (e.g. + # CONTRIBUTING.md) may depend on the list of all files previously copied + # or compiled. + compile_files(output_folder) \ + unless @config.files.nil? || @config.files.compile.nil? + apply_file_acls(output_folder) \ + unless @config.files.nil? || @config.files.permissions.nil? + verify_test_matrixes + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity + + def copy_files(output_folder) + @config.files.copy.each do |target, source| + target_file = File.join(output_folder, target) + target_dir = File.dirname(target_file) + @sourced << relative_path(target_file, output_folder) + Google::LOGGER.info "Copying #{source} => #{target}" + FileUtils.mkpath target_dir unless Dir.exist?(target_dir) + FileUtils.cp source, target_file + end + end + + def compile_files(output_folder) + compile_file_list(output_folder, @config.files.compile) + end + + def compile_examples(output_folder) + compile_file_map( + output_folder, + @config.examples, + lambda do |_object, file| + ["examples/#{file}", + "products/#{@api.prefix[1..-1]}/files/examples~#{file}"] + end + ) + end + + def compile_network_data(output_folder) + compile_file_map( + output_folder, + @config.test_data.network, + lambda do |object, file| + type = Google::StringUtils.underscore(object.name) + ["spec/data/network/#{object.out_name}/#{file}.yaml", + "products/#{@api.prefix[1..-1]}/files/spec~#{type}~#{file}.yaml"] + end + ) + end + + # Generate the CHANGELOG.md file with the history of the module. + def compile_changelog(output_folder) + FileUtils.mkpath output_folder + generate_file( + changes: @config.changelog, + template: 'templates/CHANGELOG.md.erb', + output_folder: output_folder, + out_file: File.join(output_folder, 'CHANGELOG.md') + ) + end + + def apply_file_acls(output_folder) + @config.files.permissions.each do |perm| + Google::LOGGER.info "Permission #{perm.path} => #{perm.acl}" + FileUtils.chmod perm.acl, File.join(output_folder, perm.path) + end + end + + def compile_file_map(output_folder, section, mapper) + create_object_list(section, mapper).each do |o| + compile_file_list( + output_folder, + o + ) + end + end + + # Creates an object list by calling a lambda + # This can be useful for converting a list of config values to something + # less human-centric. + def create_object_list(section, mapper) + @api.objects + .select { |o| section.key?(o.name) } + .map do |o| + Hash[section[o.name].map { |file| mapper.call(o, file) }] + end + end + + def list_manual_network_data + create_object_list( + @config.test_data.network, + lambda do |object, file| + type = Google::StringUtils.underscore(object.name) + ["spec/data/network/#{object.out_name}/#{file}.yaml", + "products/#{@api.prefix[1..-1]}/files/spec~#{type}~#{file}.yaml"] + end + ) + end + + # rubocop:disable Metrics/MethodLength + def compile_file_list(output_folder, files, data = {}) + files.each do |target, source| + Google::LOGGER.info "Compiling #{source} => #{target}" + target_file = File.join(output_folder, target) + .gsub('{{product_name}}', @api.prefix[1..-1]) + generate_file( + data.clone.merge( + name: target, + product: @api, + object: {}, + config: {}, + scopes: @api.scopes, + manifest: @config.manifest, + tests: '', + template: source, + generated_files: @generated, + sourced_files: @sourced, + compiler: compiler, + output_folder: output_folder, + out_file: target_file, + prop_ns_dir: @api.prefix[1..-1].downcase, + product_ns: Google::StringUtils.camelize(@api.prefix[1..-1], :upper) + ) + ) + end + end + # rubocop:enable Metrics/MethodLength + + def generate_objects(output_folder, types) + @api.objects.each do |object| + if !types.empty? && !types.include?(object.name) + Google::LOGGER.info "Excluding #{object.name} per user request" + elsif types.empty? && object.exclude + Google::LOGGER.info "Excluding #{object.name} per API catalog" + else + generate_object object, output_folder + end + end + end + + def generate_object(object, output_folder) + data = build_object_data(object, output_folder) + + generate_resource data + generate_resource_tests data + generate_properties data, object.all_user_properties + generate_network_datas data, object + end + + # rubocop:disable Metrics/MethodLength + # Generates all 6 network data files for a object. + # This includes all combinations of seeds [0-2] and title == / != name + # Each data file is a YAML file with all properties possible on an object. + # + # @config.test_data lists all files that are written by hand and will not + # be generated. + # + # Requires: + # object: The Api::Resource used as basis for the network data. + # data: A hash with values: + # output_folder: root folder for generated module + def generate_network_datas(data, object) + target_folder = File.join(data[:output_folder], + 'spec', 'data', 'network', object.out_name) + FileUtils.mkpath target_folder + + # Create list of compiled network data + manual = list_manual_network_data + 3.times.each do |id| + %w[name title].each do |name| + out_file = File.join(target_folder, "success#{id + 1}~#{name}.yaml") + next if manual.include? out_file + next if true?(Google::HashUtils.navigate(data[:config], %w[manual])) + + generate_network_data data.clone.merge( + out_file: File.join(target_folder, "success#{id + 1}~#{name}.yaml"), + id: id, + title: name, + object: object + ) + end + end + end + # rubocop:enable Metrics/MethodLength + + # Generates a single network data file for unit testing. + # Required values in data: + # out_file: path of data file to create + # id: a seed value + # title: The name of object who is unit tested with this spec file + # object: The Api::Resource used as basis for the network data + def generate_network_data(data) + formatter = Provider::TestData::SpecFormatter.new(self) + + name = "title#{data[:id]}" if data[:title] == 'title' + name = "test name##{data[:id]} data" if data[:title] == 'name' + generate_file data.clone.merge( + template: 'templates/network_spec.yaml.erb', + test_data: formatter.generate(data[:object], '', data[:object].kind, + data[:id], + name: name) + ) + end + + def build_object_data(object, output_folder) + { + name: object.out_name, + object: object, + config: (@config.objects || {}).select { |o, _v| o == object.name } + .fetch(object.name, {}), + tests: (@config.tests || {}).select { |o, _v| o == object.name } + .fetch(object.name, {}), + output_folder: output_folder, + product_name: object.__product.prefix[1..-1] + } + end + + def generate_resource_file(data) + generate_file(data.clone.merge( + # Override with provider specific template for this object, if needed + template: Google::HashUtils.navigate(data[:config], ['template', + data[:type]], + data[:default_template]), + product_ns: + Google::StringUtils.camelize(data[:object].__product.prefix[1..-1], + :upper) + )) + end + + def generate_client_functions(output_folder) + @config.functions.each do |fn| + info = generate_client_function(output_folder, fn) + FileUtils.mkpath info[:target_folder] + generate_file info.clone + end + end + + def format_expand_variables(obj_url) + obj_url = obj_url.split("\n") unless obj_url.is_a?(Array) + if obj_url.size > 1 + ['[', + indent_list(obj_url.map { |u| quote_string(u) }, 2), + '].join,'] + else + [obj_url.map { |u| quote_string(u) }[0] + ','] + end + end + + def build_url(product_url, obj_url, extra = false) + extra_arg = '' + extra_arg = ', extra_data' if obj_url.to_s.include?('<|extra|>') || extra + ['URI.join(', + indent([quote_string(product_url) + ',', + 'expand_variables(', + indent(format_expand_variables(obj_url), 2), + indent('data' + extra_arg, 2), + ')'], 2), + ')'].join("\n") + end + + def async_operation_url(resource) + build_url(resource.__product.base_url, resource.async.operation.base_url, + true) + end + + def collection_url(resource) + base_url = resource.base_url.split("\n").map(&:strip).compact + build_url(resource.__product.base_url, base_url) + end + + def self_link_raw_url(resource) + base_url = resource.__product.base_url.split("\n").map(&:strip).compact + if resource.self_link.nil? + [base_url, [resource.base_url, '{{name}}'].join('/')] + else + self_link = resource.self_link.split("\n").map(&:strip).compact + [base_url, self_link] + end + end + + def self_link_url(resource) + (product_url, resource_url) = self_link_raw_url(resource) + build_url(product_url, resource_url) + end + + def extract_variables(template) + template.scan(/{{[^}]*}}/) + .map { |v| v.gsub(/{{([^}]*)}}/, '\1') } + .map(&:to_sym) + end + + def variable_type(object, var) + return Api::Type::String::PROJECT if var == :project + return Api::Type::String::NAME if var == :name + v = object.all_user_properties + .select { |p| p.out_name.to_sym == var || p.name.to_sym == var } + .first + return v.property if v.is_a?(Api::Type::ResourceRef) + v + end + + def quote_string(value) + raise 'Invalid value' if value.nil? + if value.include?('#{') || value.include?("'") + ['"', value, '"'].join + else + ["'", value, "'"].join + end + end + + # Used to convert a string 'a b c' into a\ b\ c for use in %w[...] form + def str2warray(value) + unquote_string(value).gsub(/ /, '\\ ') + end + + def unquote_string(value) + return value.gsub(/"(.*)"/, '\1') if value.start_with?('"') + return value.gsub(/'(.*)'/, '\1') if value.start_with?("'") + value + end + + # TODO(alexstephen): Retire in favor of a real code object. + # No validation is possible on get_code_multiline + def get_code_multiline(config, node) + search = node.class <= Array ? node : [node] + Google::HashUtils.navigate(config, search) + end + + def lines(code, number = 0) + return if code.nil? || code.empty? + code = code.join("\n") if code.is_a?(Array) + code[-1] = '' while code[-1] == "\n" || code[-1] == "\r" + "#{code}#{"\n" * (number + 1)}" + end + + def lines_before(code, number = 0) + return if code.nil? || code.empty? + code = code.join("\n") if code.is_a?(Array) + code[0] = '' while code[0] == "\n" || code[0] == "\r" + "#{"\n" * (number + 1)}#{code}" + end + + def true?(obj) + obj.to_s.casecmp('true').zero? + end + + def false?(obj) + obj.to_s.casecmp('false').zero? + end + + def emit_method(name, args, code, file_name, opts = {}) + method_decl = "def #{name}" + method_decl << "(#{args.join(', ')})" unless args.empty? + (rubo_off, rubo_on) = emit_rubo_pair(file_name, name, opts) + [ + (rubo_off unless rubo_off.empty?), + method_decl, + indent(code, 2), + 'end', + (rubo_on unless rubo_on.empty?) + ].compact.join("\n") + end + + def emit_rubo_pair(file_name, name, opts = {}) + [ + emit_rubo_item(file_name, name, :disabled, opts), + emit_rubo_item(file_name, name, :enabled, opts) + ] + end + + def emit_rubo_item(file_name, name, state, opts = {}) + [ + (if opts.key?(:class_name) + get_rubocop_exceptions(file_name, :function, + [opts[:class_name], name].join('.'), state) + end), + get_rubocop_exceptions(file_name, :function, name, state) + ].compact.flatten + end + + def emit_rubocop(ctx, pinpoint, name, state) + get_rubocop_exceptions(ctx.local_variable_get(:file_relative), pinpoint, + name, state).join("\n") + end + + # TODO(nelsonjr): Track usage of exceptions and fail if some are + # left unused. E.g. we change the function/class name and the setting + # in the YAML file is now useless. + def get_rubocop_exceptions(file_name, pinpoint, name, state) + name = name.flatten.join(' > ') if pinpoint == :test + flags = get_style_exceptions(file_name, pinpoint, name) + flags = flags.reverse if state == :enabled + flags.map do |e| + "# rubocop:#{state == :enabled ? 'enable' : 'disable'} #{e}" + end + end + + def get_style_exceptions(file_name, type, name) + styles = @config.style + return [] if styles.nil? + styles.select { |s| s.name == file_name } + .map(&:pinpoints) + .flatten + .select { |ps| ps.any? { |k, v| k.to_sym == type && v == name } } + .map { |p| p['exceptions'] } + .flatten + .sort + end + + def emit_link(name, url, emit_self, extra_data = false) + (params, fn_args) = emit_link_var_args(url, extra_data) + code = ["def #{emit_self ? 'self.' : ''}#{name}(#{fn_args})", + indent(url, 2).gsub("'<|extra|>'", 'extra'), + 'end'] + + if emit_self + self_code = ['', "def #{name}(#{fn_args})", + " self.class.#{name}(#{params.join(', ')})", + 'end'] + end + + (code + (self_code || [])).join("\n") + end + + # Formats the code and returns the first candidate that fits the alloted + # column limit. + def format(sources, indent = 0, start_indent = 0, + max_columns = DEFAULT_FORMAT_OPTIONS[:max_columns]) + format2(sources, indent: indent, + start_indent: start_indent, + max_columns: max_columns) + end + + # TODO(nelsonjr): Make format2 into format and fix all references throughout + # the code base. + def format2(sources, overrides = {}) + options = DEFAULT_FORMAT_OPTIONS.merge(overrides) + output = '' + avail_columns = options[:max_columns] - options[:start_indent] + sources.each do |attempt| + output = indent(attempt, options[:indent]) + return output if format_fits?(output, options[:start_indent], + options[:max_columns]) + end + unless options[:on_misfit].nil? + (alt_fit, alt_output) = options[:on_misfit].call(sources, output, + options, avail_columns) + return alt_output if alt_fit + end + fail_and_log_format_error output, options, avail_columns \ + unless options[:quiet] + end + + def fail_and_log_format_error(output, options, avail_columns) + Google::LOGGER.info [ + ["No code option fits in #{avail_columns} columns", + "w/ #{options[:start_indent]} left indent:"].join(' '), + format_sources(output.split("\n"), options[:start_indent], + options[:max_columns]), + (unless options[:on_misfit].nil? + format_sources(alt_output.split("\n"), options[:start_indent], + options[:max_columns]) + end) + ].compact.join("\n") + raise ArgumentError, "No code fits in #{avail_columns}" + end + + def format_fits?(output, start_indent, + max_columns = DEFAULT_FORMAT_OPTIONS[:max_columns]) + output = output.flatten.join("\n") if output.is_a?(::Array) + output = output.split("\n") unless output.is_a?(::Array) + output.select { |l| l.length > (max_columns - start_indent) }.empty? + end + + def relative_path(target, base) + Pathname.new(target).relative_path_from(Pathname.new(base)) + end + + # TODO(nelsonjr): Review all object interfaces and move to private methods + # that should not be exposed outside the object hierarchy. + private + + def generate_requires(properties, requires = []) + requires.concat(properties.collect(&:requires)) + end + + def emit_requires(requires) + requires.flatten.sort.uniq.map { |r| "require '#{r}'" }.join("\n") + end + + def emit_link_var_args(url, extra_data) + params = emit_link_var_args_list(url, extra_data, + %w[data extra extra_data]) + defaults = emit_link_var_args_list(url, extra_data, + [nil, "''", '{}']) + [params.compact, params.zip(defaults) + .reject { |p| p[0].nil? } + .map { |p| p[1].nil? ? p[0] : "#{p[0]} = #{p[1]}" } + .join(', ')] + end + + def emit_link_var_args_list(url, extra_data, args_list) + [args_list[0], + (args_list[1] if url.include?('<|extra|>')), + (args_list[2] if url.include?('<|extra|>') || extra_data)] + end + + def generate_file(data) + file_folder = File.dirname(data[:out_file]) + file_relative = relative_path(data[:out_file], data[:output_folder]).to_s + FileUtils.mkpath file_folder unless Dir.exist?(file_folder) + @generated << relative_path(data[:out_file], data[:output_folder]) + ctx = binding + data.each { |name, value| ctx.local_variable_set(name, value) } + generate_file_write ctx, data + end + + def generate_file_write(ctx, data) + Google::LOGGER.info "Generating #{data[:name]} #{data[:type]}" + write_file data[:out_file], compile_file(ctx, data[:template]) + end + + def compile_file(ctx, source) + ERB.new(File.read(source), nil, '-%>').result(ctx).split("\n") + rescue StandardError => e + puts "Error compiling file: #{source}" + raise e + end + + # Write the output to a file. We write one line at a time so tests can + # reason about what's being written and validate the output. + def write_file(out_file, output) + File.open(out_file, 'w') { |f| output.each { |l| f.write("#{l}\n") } } + end + + def wrap_field(field, spaces) + avail_columns = DEFAULT_FORMAT_OPTIONS[:max_columns] - spaces - 5 + indent(field.scan(/\S.{0,#{avail_columns}}\S(?=\s|$)|\S+/), 2) + end + + def verify_test_matrixes + Provider::TestMatrix::Registry.instance.verify_all + end + + def format_section_ruler(size) + size_pad = (size - size.to_s.length - 4) # < + > + 2 spaces around number. + return unless size_pad > 0 + ['<', + '-' * (size_pad / 2), ' ', size.to_s, ' ', '-' * (size_pad / 2), + (size_pad.even? ? '' : '-'), + '>'].join + end + + def format_box(existing, size) + result = [] + result << ['+', '-' * size, '+'].join + result << ['|', format_section_ruler(existing), + format_section_ruler(size - existing), '|'].join + result << yield + result << ['+', '-' * size, '+'].join + result.join("\n") + end + + def format_sources(sources, existing, size) + format_box(existing, size) do + sources.map do |source| + source.split("\n").map do |l| + right_pad_len = size - existing - l.length + right_pad = right_pad_len > 0 ? ' ' * right_pad_len : '' + '|' + '.' * existing + l + right_pad + '|' + end + end + end + end + + def emit_user_agent(product, extra, notes, file_name) + prov_text = Google::StringUtils.camelize(self.class.name.split('::').last, + :upper) + prod_text = Google::StringUtils.camelize(product, :upper) + ua_generator = notes.map { |n| "# #{n}" }.concat( + [ + "version = '1.0.0'", + '[', + indent_list([ + "\"Google#{prov_text}#{prod_text}/\#{version}\"", + extra + ].compact, 2), + "].join(' ')" + ] + ) + emit_method('generate_user_agent', [], ua_generator.compact, file_name) + end + + def provider_name + self.class.name.split('::').last.downcase + end + + # Returns true if this module needs access to the saved API response + # This response is stored in the @fetched variable + # Requires: + # config: The config for an object + # object: An Api::Resource object + def save_api_results?(config, object) + fetched_props = object.exported_properties.select do |p| + p.is_a? Api::Type::FetchedExternal + end + Google::HashUtils.navigate(config, %w[access_api_results]) || \ + !fetched_props.empty? + end + + # Generates the documentation for the client side function to be + # included in the module. Call this function immediately before the function + # definition and the code generator will use data from api.yaml to build the + # documentation comment block. + # + # rubocop: Method returns a big array. Easier to read a single block + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def emit_function_doc(function) + [ + function.description.strip, + '', + 'Arguments:', + indent(function.arguments.map do |arg| + [ + "- #{arg.name}: #{arg.type.split('::').last.downcase}", + indent(arg.description.strip.split("\n"), 2) + ] + end, 2), + ( + unless function.examples.nil? + [ + '', + 'Examples:', + indent(function.examples.map { |eg| "- #{eg}" }, 2) + ] + end + ), + ( + unless function.notes.nil? + [ + '', + function.notes.strip + ] + end + ) + ].compact.flatten.join("\n").split("\n").map { |l| "# #{l}".strip } + end + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize + end +end diff --git a/provider/end2end/core.rb b/provider/end2end/core.rb new file mode 100644 index 000000000000..e3d506ef8d66 --- /dev/null +++ b/provider/end2end/core.rb @@ -0,0 +1,52 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'binding_of_caller' +require 'tests/end2end/constants' + +module Provider + module End2End + # Provides functionality for generating a set of parallelizable end-to-end + # tests. + module Core + include ::End2End::Constants + def compile_end2end_tests(output_folder) + compile_file_map( + output_folder, + @config.examples, + lambda do |_object, file| + # Tests go into hidden folder because we don't need to expose + # to regular users. + ["#{TEST_FOLDER}/#{file}", + "products/#{@api.prefix[1..-1]}/files/examples~#{file}"] + end + ) + end + + # Returns a parallelizable name for end-to-end test manifests + # Returns a normal name for example manifests + # Requires: + # * name - The name of the resource without any prefix + # * The prior stack frame with a variable "name" that represents + # the file name. + def example_resource_name(name) + filename = binding.of_caller(1).local_variable_get(:name) + + res_name = name + res_name = "#{provider_name}-e2e-#{name}" if filename =~ TEST_FILE_REGEX + + quote_string(res_name) + end + end + end +end diff --git a/provider/example.rb b/provider/example.rb new file mode 100644 index 000000000000..750461525836 --- /dev/null +++ b/provider/example.rb @@ -0,0 +1,70 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/config' +require 'provider/core' + +module Provider + # Code generator for Example Cookbooks that manage Google Cloud Platform + # resources. + class Example < Provider::Core + # Settings for the provider + class Config < Provider::Config + attr_reader :manifest + def provider + Provider::Example + end + end + + private + + # This function uses the resource.erb template to create one file + # per resource. The resource.erb template forms the basis of a single + # GCP Resource on Example. + def generate_resource(data) + target_folder = data[:output_folder] + FileUtils.mkpath target_folder + name = Google::StringUtils.underscore(data[:object].name) + generate_resource_file data.clone.merge( + default_template: 'templates/example/resource.erb', + out_file: File.join(target_folder, "#{name}.rb") + ) + end + + # This function would generate unit tests using a template + def generate_resource_tests(data) end + + # This function would automatically generate the files used for verifying + # network calls in unit tests. If you comment out the following line, + # a bunch of YAML files will be created under the spec/ folder. + def generate_network_datas(data, object) end + + # We build a lot of property classes to help validate + coerce types. + # The following functions would generate all of these properties. + # Some of these property classes help us handle Strings, Times, etc. + # + # Others (nested objects) ensure that all Hashes contain proper values + + # types for its nested properties. + # + # ResourceRef properties help ensure that links between different objects + # (Addresses + Instances for example) work properly, are abstracted away, + # and don't require the user to have a large knowledge base of how GCP + # works. + # rubocop:disable Layout/EmptyLineBetweenDefs + def generate_base_property(data) end + def generate_simple_property(type, data) end + def emit_nested_object(data) end + def emit_resourceref_object(data) end + # rubocop:enable Layout/EmptyLineBetweenDefs + end +end diff --git a/provider/properties.rb b/provider/properties.rb new file mode 100644 index 000000000000..f2fa2f473d39 --- /dev/null +++ b/provider/properties.rb @@ -0,0 +1,165 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + # A set of functions to generate properties of objects being compiled. This is + # a helper library to be included by Provider::Core. + # rubocop:disable Metrics/ModuleLength + module Properties + private + + def generate_properties(data, properties) + prop_map = [] + + prop_map << generate_base_property(data) unless properties.empty? + prop_map << generate_primitive_properties(data, properties) + prop_map << generate_array_properties(data, properties) + prop_map << generate_nested_object_properties(data, properties) + prop_map << generate_resourceref_properties(data, properties) + prop_map << generate_namevalues_properties(data, properties) + + generate_property_files(prop_map, data) + end + + # Generate the files for the properties + def generate_property_files(prop_map, data) + prop_map.flatten.compact.each do |prop| + compile_file_list( + data[:output_folder], + { prop[:target] => prop[:source] }, + { + product_ns: Google::StringUtils.camelize(data[:product_name], + :upper), + prop_ns_dir: data[:product_name].downcase + }.merge((prop[:overrides] || {})) + ) + end + end + + def generate_primitive_properties(data, properties) + properties.select { |p| p.is_a?(Api::Type::Primitive) } + .map { |p| generate_simple_property p.type.downcase, data } + end + + # rubocop:disable Metrics/AbcSize + def generate_array_properties(data, properties) + prop_map = [] + + prop_map << properties.select { |p| p.is_a?(Api::Type::Array) } + .select { |p| p.item_type.is_a?(String) } + .map { |p| generate_typed_array(data, p) } + + prop_map \ + << properties.select { |p| p.is_a?(Api::Type::Array) } + .select { |p| p.item_type.is_a?(Api::Type::NestedObject) } + .map { |p| generate_nested_object_array(data, p) } + + prop_map \ + << properties.select { |p| p.is_a?(Api::Type::Array) } + .select { |p| p.item_type.is_a?(Api::Type::ResourceRef) } + .map { |p| generate_resourceref_array(data, p.item_type) } + + prop_map + end + # rubocop:enable Metrics/AbcSize + + def generate_namevalues_properties(data, properties) + properties.select { |p| p.is_a?(Api::Type::NameValues) } + .map { |p| generate_simple_property p.type.downcase, data } + end + + def generate_nested_object_properties(data, properties) + properties.select { |p| p.is_a?(Api::Type::NestedObject) } + .map { |p| generate_nested_object(data, p) } + end + + def generate_nested_object(data, prop) + prop_map = [] + + prop_map << emit_nested_object( + data.clone.merge( + emit_array: false, + field: Google::StringUtils.underscore(prop.name), + property: prop, + nested_properties: prop.properties, + obj_name: Google::StringUtils.underscore(data[:object].name) + ) + ) + + prop_map << generate_properties(data, prop.properties) + + prop_map + end + + def generate_nested_object_array(data, prop) + prop_map = [] + + prop_map << emit_nested_object( + data.clone.merge( + emit_array: true, + field: Google::StringUtils.underscore(prop.name), + property: prop, + nested_properties: prop.item_type.properties, + obj_name: Google::StringUtils.underscore(data[:object].name) + ) + ) + + prop_map << generate_properties(data, prop.item_type.properties) + + prop_map + end + + def generate_resourceref_properties(data, properties) + properties.select { |p| p.is_a?(Api::Type::ResourceRef) } + .map { |p| generate_resourceref_object(data, p) } + end + + def generate_resourceref_object(data, prop) + resource = Google::StringUtils.underscore(prop.resource_ref.name) + imports = Google::StringUtils.underscore(prop.imports) + return if resourceref_tracker.key?([resource, imports]) + resourceref_tracker[[resource, imports]] = false + + emit_resourceref_object( + data.clone.merge( + emit_array: false, + property: prop, + resource: resource, + imports: imports + ) + ) + end + + def generate_resourceref_array(data, prop) + resource = Google::StringUtils.underscore(prop.resource_ref.name) + imports = Google::StringUtils.underscore(prop.imports) + return if resourceref_tracker.key?([resource, imports]) \ + && resourceref_tracker[[resource, imports]] == true + resourceref_tracker[[resource, imports]] = true + + emit_resourceref_object( + data.clone.merge( + emit_array: true, + property: prop, + resource: resource, + imports: imports + ) + ) + end + + def resourceref_tracker + @resourceref ||= {} + end + end + # rubocop:enable Metrics/ModuleLength +end diff --git a/provider/puppet.rb b/provider/puppet.rb new file mode 100644 index 000000000000..37ea0f1712a1 --- /dev/null +++ b/provider/puppet.rb @@ -0,0 +1,517 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'fileutils' +require 'google/hash_utils' +require 'google/string_utils' +require 'provider/config' +require 'provider/core' +require 'provider/puppet/codegen' +require 'provider/puppet/manifest' +require 'provider/puppet/test_manifest' +require 'provider/test_matrix' + +module Provider + # A code generator for Puppet modules + class Puppet < Provider::Core + STUBS_FOLDER = File.join('spec', 'stubs').freeze + BOLT_TASK_ACL = 'u=rwx,g=rx,o=rx'.freeze + BOLT_UNDEF_MAGIC = '<-undef->'.freeze + + include Provider::Puppet::Codegen + + # Settings for the provider + class Config < Provider::Config + attr_reader :manifest + attr_reader :functions + attr_reader :bolt_tasks + + def provider + Provider::Puppet + end + + def validate + super + check_property :manifest, Provider::Puppet::Manifest \ + unless manifest.nil? + check_property_list :functions, @functions, Provider::Config::Function + check_property_list :bolt_tasks, @bolt_tasks, Provider::Puppet::BoltTask + end + end + + # A Bolt task + class BoltTask < Provider::Config::Function + attr_reader :style + attr_reader :input + attr_reader :manifest + + INPUTS = %i[stdin].freeze + STYLES = %i[puppet ruby].freeze + + def description_display + @description.split("\n").map(&:strip).join(' ') + end + + def target_file + File.join('tasks', case @style + when :ruby + "#{name}.rb" + when :puppet + "#{name}.sh" + else + raise "Unknown task style #{task.style}" + end) + end + + def validate + super + check_property :style, Symbol + check_property :input, Symbol + check_property :manifest, String if @style == 'puppet' + raise "Unknown style #{@style}. Expected #{STYLES.join(', ')}" \ + unless STYLES.include?(@style) + raise "Unknown input #{@input}. Expected #{INPUTS.join(', ')}" \ + unless INPUTS.include?(@input) + raise 'Manifests can only be specified for :puppet style tasks' \ + unless @manifest.nil? || @style != 'puppet' + end + + # A user provided argument to a Bolt task + class Argument < Provider::Config::Function::Argument + attr_reader :required + attr_reader :default + attr_reader :comment + + def description_display + [ + @description.split("\n").map(&:strip).join(' '), + ("(default: #{@default.display})" if default?) + ].compact.join(' ') + end + + def type_metadata(_provider) + puppet_type = type.split('::').last + optional_wrapper(puppet_type == 'String' ? 'String[1]' : puppet_type) + end + + def validate + super + check_property :required, :boolean unless @required.nil? + check_property :default unless @default.nil? + check_property :comment unless @comment.nil? + @default = Default.new(@default) \ + if default? && !@default.is_a?(Default) + end + + def default? + !@default.nil? + end + + def required? + !@required.nil? || @required + end + + # Represents an argument can only accept form a preset list of values. + class Enum < Argument + attr_reader :values + + def validate + @type = Api::Type::Enum.name + super + check_property_list :values, @values, Symbol + end + + def type_metadata(provider) + vs = values.map { |v| provider.quote_string(v.id2name) }.join(', ') + optional_wrapper("Enum[#{vs}]") + end + end + + # Definitions of the default value for a Bolt task. + class Default < Api::Object + attr_reader :code + attr_reader :display + + def validate + super + check_property :display + end + + private + + def initialize(value) + @code = value + @display = value + end + end + + private + + def optional_wrapper(metadata) + required? ? metadata : "Optional[#{metadata}]" + end + end + end + + def generate(output_folder, types) + generate_client_functions output_folder unless @config.functions.nil? + generate_bolt_tasks output_folder unless @config.bolt_tasks.nil? + super(output_folder, types) + end + + def property_body(property) + prop_generator = property_map.select { |type, _| property.class <= type } + raise "Unknown property type: #{property}" if prop_generator.empty? + body = prop_generator.values[0].call(property) + "#{body}\n" unless body.nil? + end + + def format_description(object, spaces, container, suffix = '') + description = build_description object, suffix + # A single line description is of the form: + # [ newparam(....)] + # [ desc ''] + # [ end] + # + # So the description line has 11 extra characters other than the message + # itself. + # + # The indentation level of 'desc' attribute is 4, spaces=4 due to the + # template (newparam=2, desc=newparam+2), leaving 7 characters to + # "compensate" for. + format([ + ["#{container} #{quote_string(description)}"], + [ + "#{container} <<-DOC", + wrap_field(description, spaces), + 'DOC' + ] + ], spaces) + end + + def build_description(object, suffix) + [object.description, suffix].reject(&:empty?) + .join(' ').tr("\n", ' ') + .gsub(' ', ' ').strip + end + + def generate_user_agent(product, file_name) + emit_user_agent( + product, 'Puppet[:http_user_agent]', + ['TODO(nelsonjr): Check how to fetch module version.'], + file_name + ) + end + + def quote_string(value) + raise 'Invalid value' if value.nil? + # Puppet DSL uses '${' to string interpolation while Ruby uses #{ + if value.include?('${') + ['"', value, '"'].join + else + super(value) + end + end + + # Generates the documentation for the Puppet client side function to be + # included in the module. Call this function immediately before the function + # definition and the code generator will use data from api.yaml to build the + # documentation comment block. + # + # rubocop: Method returns a big array. Easier to read a single block + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def emit_function_doc(function) + [ + function.description.strip, + '', + 'Arguments:', + indent(function.arguments.map do |arg| + [ + "- #{arg.name}: #{arg.type.split('::').last.downcase}", + indent(arg.description.strip.split("\n"), 2) + ] + end, 2), + ( + unless function.examples.nil? + [ + '', + 'Examples:', + indent(function.examples.map do |eg| + "- #{expand_function_vars(function, eg)}" + end, 2) + ] + end + ), + ( + unless function.notes.nil? + [ + '', + expand_function_vars(function, function.notes.strip) + ] + end + ) + ].compact.flatten.join("\n").split("\n").map { |l| "# #{l}".strip } + end + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize + + # Returns true if any copy: or compile: entries there are any spec stub + # files defined. + def spec_stubs? + @config.files.copy.any? { |f, _| f.start_with?(STUBS_FOLDER) } \ + || @config.files.compile.any? { |f, _| f.start_with?(STUBS_FOLDER) } + end + + # rubocop:disable Metrics/MethodLength + def emit_bolt_params_ruby(task) + raise 'Only :stdin style supported' unless task.input == :stdin + [ + 'params = {}', + 'begin', + indent( + [ + 'Timeout.timeout(3) do', + indent('params = JSON.parse(STDIN.read)', 2), + 'end' + ], 2 + ), + 'rescue Timeout::Error', + indent( + [ + ['puts(', + "{ status: 'failure', error: 'Cannot read JSON from stdin' }", + '.to_json)'].join, + 'exit 1' + ], 2 + ), + 'end', + '' + ].concat( + task.arguments.map do |arg| + if arg.default? + "#{arg.name} = validate(params, :#{arg.name}, #{arg.default.code})" + else + "#{arg.name} = validate(params, :#{arg.name})" + end + end + ) + end + # rubocop:enable Metrics/MethodLength + + private + + def generate_resource(data) + generate_type data + generate_provider data + end + + def generate_provider_tests(data) + super(data) \ + unless true?(Google::HashUtils.navigate(data[:config], %w[manual])) + end + + def generate_simple_property(type, data) + { + source: File.join('templates', 'puppet', 'property', "#{type}.rb.erb"), + target: File.join('lib', 'google', data[:product_name], 'property', + "#{type}.rb") + } + end + + def generate_base_property(data) + { + source: File.join('templates', 'puppet', 'property', 'base.rb.erb'), + target: File.join('lib', 'google', data[:product_name], 'property', + 'base.rb') + } + end + + def generate_typed_array(data, prop) + type = Module.const_get(prop.item_type).new(prop.name).type + file = Google::StringUtils.underscore(type) + prop_map = [] + prop_map << { + source: File.join('templates', 'puppet', 'property', + 'array_typed.rb.erb'), + target: File.join('lib', 'google', data[:product_name], 'property', + "#{file}_array.rb"), + overrides: { type: type } + } + prop_map << generate_base_array(data) + prop_map + end + + def generate_base_array(data) + { + source: File.join('templates', 'puppet', 'property', 'array.rb.erb'), + target: File.join('lib', 'google', data[:product_name], 'property', + 'array.rb') + } + end + + def emit_nested_object(data) + target = if data[:emit_array] + data[:property].item_type.property_file + else + data[:property].property_file + end + result = [ + { + source: File.join('templates', 'puppet', 'property', + 'nested_object.rb.erb'), + target: "lib/#{target}.rb", + overrides: emit_nested_object_overrides(data) + } + ] + + result << generate_simple_property('array', data) if data[:emit_array] + + result + end + + def emit_nested_object_overrides(data) + data.clone.merge( + field_name: Google::StringUtils.camelize(data[:field], :upper), + object_type: Google::StringUtils.camelize(data[:obj_name], :upper), + product_ns: Google::StringUtils.camelize(data[:product_name], :upper), + class_name: if data[:emit_array] + data[:property].item_type.property_class.last + else + data[:property].property_class.last + end + ) + end + + def emit_resourceref_object(data) + target = data[:property].property_file + { + source: File.join('templates', 'puppet', 'property', + 'resourceref.rb.erb'), + target: "lib/#{target}.rb", + overrides: data.clone.merge( + class_name: data[:property].property_class.last + ) + } + end + + def property_map + { + Api::Type::Array => ->(_) {}, + Api::Type::Boolean => method(:generate_boolean_body), + Api::Type::Double => ->(_) {}, + Api::Type::Enum => method(:generate_enum_body), + Api::Type::Integer => ->(_) {}, + Api::Type::NameValues => ->(_) {}, + Api::Type::NestedObject => ->(_) {}, + Api::Type::ResourceRef => ->(_) {}, + Api::Type::String => ->(_) {}, + Api::Type::Time => ->(_) {} + } + end + + def generate_boolean_body(_property) + indent(['newvalue(:true)', 'newvalue(:false)'], 4) + end + + def generate_enum_body(property) + indent(property.values.collect do |value| + if value.is_a?(Symbol) + "newvalue(:#{value})" + elsif value.is_a?(String) + "newvalue(#{quote_string(value)})" + else + "#{value.class}newvalue(#{value})" + end + end, 4) + end + + def google_lib_basic(file, product_ns) + google_lib_basic_files(file, product_ns, 'lib', 'google') + end + + def google_lib_network(file, product_ns) + google_lib_network_files(file, product_ns, 'lib', 'google') + end + + # Emits all the Puppet client functions available for use by end users. + def generate_client_function(output_folder, fn) + target_folder = File.join(output_folder, 'lib', 'puppet', 'functions') + { + fn: fn, + target_folder: target_folder, + template: 'templates/puppet/function.erb', + output_folder: output_folder, + out_file: File.join(target_folder, "#{fn.name}.rb") + } + end + + # Emits all the Bolt tasks + def generate_bolt_tasks(output_folder) + target_folder = File.join(output_folder, 'tasks') + FileUtils.mkpath target_folder + + generate_bolt_readme(output_folder) + + @config.bolt_tasks.each do |task| + generate_file( + name: "Bolt task #{task.name} (json)", + task: task, + template: 'templates/puppet/bolt~task.json.erb', + output_folder: output_folder, + out_file: File.join(target_folder, "#{task.name}.json") + ) + + generate_bolt_tasks_code(task, output_folder) + end + end + + def generate_bolt_readme(output_folder) + generate_file( + name: 'Bolt task README.md', + template: 'templates/puppet/bolt~README.md.erb', + output_folder: output_folder, + out_file: File.join(output_folder, 'tasks/README.md') + ) + end + + # rubocop:disable Metrics/MethodLength + def generate_bolt_tasks_code(task, output_folder) + style = task.style + template = File.join('templates', 'puppet', case style + when :ruby + 'bolt~task.rb.erb' + when :puppet + 'bolt~task.pp.erb' + else + raise "No style #{style}" + end) + out_file = File.join(output_folder, task.target_file) + + generate_file( + name: "Bolt task #{task.name}", + task: task, + template: template, + output_folder: output_folder, + out_file: out_file + ) + + FileUtils.chmod BOLT_TASK_ACL, out_file + end + # rubocop:enable Metrics/MethodLength + + def expand_function_vars(fn_config, data) + data.gsub('{{function:name}}', fn_config.name) + end + end +end diff --git a/provider/puppet/bundle.rb b/provider/puppet/bundle.rb new file mode 100644 index 000000000000..bc6860d19188 --- /dev/null +++ b/provider/puppet/bundle.rb @@ -0,0 +1,97 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'json' +require 'provider/config' +require 'provider/core' + +module Provider + # A provider to generate the "bundle" module. + class PuppetBundle < Provider::Core + # A manifest for the "bundle" module + class Manifest < Puppet::Manifest + def validate + @requires = [] if @requires.nil? + super + end + end + + # The configuration for the "bundle" module (in puppet.yaml) + class Config < Provider::Config + attr_reader :manifest + + def provider + ::Provider::PuppetBundle + end + + def validate + check_property :manifest, Provider::PuppetBundle::Manifest + end + end + + def generate(output_folder, _types) + # Let's build all the dependencies off of the products we found on our + # path and has the corresponding provider.yaml file + generate_requirements + + # Always include authentication module + @config.manifest.requires \ + << Provider::Config::Requirements \ + .create('google/gauth', ">= #{auth_ver} < #{next_version(auth_ver)}") + + compile_changelog(output_folder) + copy_files(output_folder) + compile_files(output_folder) + end + + def products + @products ||= begin + prod_map = Dir['products/**/puppet.yaml'] + .reject { |f| f.include?('bundle') } + .map do |product_config| + product = Api::Compiler.new( + File.join(File.dirname(product_config), 'api.yaml') + ).run + product.validate + config = Provider::Config.parse(product_config, product) + config.validate + + [product, config] + end + Hash[prod_map.sort_by { |p| p[0].prefix }] + end + end + + private + + def auth_ver + auth_meta = JSON.parse(IO.read('build/puppet/auth/metadata.json')) + auth_meta['version'] + end + + def next_version(version) + [Gem::Version.new(version).bump, 0].join('.') + end + + def generate_requirements + @config.manifest.requires.concat( + products.map do |k, v| + Provider::Config::Requirements.create( + "google/#{k.prefix}", + ">= #{v.manifest.version} < #{next_version(v.manifest.version)}" + ) + end + ).sort_by!(&:name) + end + end +end diff --git a/provider/puppet/codegen.rb b/provider/puppet/codegen.rb new file mode 100644 index 000000000000..347c7bcdf610 --- /dev/null +++ b/provider/puppet/codegen.rb @@ -0,0 +1,70 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/hash_utils' +require 'provider/core' + +module Provider + class Puppet < Provider::Core + # Functions that generate code for Puppet modules + module Codegen + private + + def generate_type(data) + target_folder = File.join(data[:output_folder], 'lib', 'puppet', 'type') + FileUtils.mkpath target_folder + generate_resource_file data.clone.merge( + type: 'type', + default_template: 'templates/puppet/type.erb', + out_file: File.join(target_folder, "#{data[:name]}.rb") + ) + end + + def generate_provider(data) + target_folder = File.join(data[:output_folder], 'lib', 'puppet', + 'provider', data[:name]) + FileUtils.mkpath target_folder + generate_resource_file data.clone.merge( + type: 'provider', + default_template: provider_template_source(data), + out_file: File.join(target_folder, 'google.rb') + ) + end + + def generate_resource_tests(data) + return if true?(Google::HashUtils.navigate(data[:config], %w[manual])) + generate_provider_tests data + end + + def generate_provider_tests(data) + generate_resource_file data.clone.merge( + type: 'provider_spec', + default_template: 'templates/puppet/provider_spec.erb', + out_file: File.join(data[:output_folder], 'spec', + "#{data[:name]}_provider_spec.rb") + ) + end + + def provider_template_source(data) + object_name = Google::StringUtils.underscore(data[:object].name) + if true?(Google::HashUtils.navigate(data[:config], + %w[manual])) + File.join('products', data[:product_name], 'files', + "provider~#{object_name}.rb") + else + 'templates/puppet/provider.erb' + end + end + end + end +end diff --git a/provider/puppet/common~compile~after.yaml b/provider/puppet/common~compile~after.yaml new file mode 100644 index 000000000000..d3633c48cef2 --- /dev/null +++ b/provider/puppet/common~compile~after.yaml @@ -0,0 +1,19 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# These files contains code that needs to be compiled before being delivered to +# the final module tree structure: + +# Contributing file has to be last, as it has references to other +# automatically generated files. +'CONTRIBUTING.md': 'templates/CONTRIBUTING.md.erb' diff --git a/provider/puppet/common~compile~before.yaml b/provider/puppet/common~compile~before.yaml new file mode 100644 index 000000000000..470382193010 --- /dev/null +++ b/provider/puppet/common~compile~before.yaml @@ -0,0 +1,37 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# These files contains code that needs to be compiled before being delivered to +# the final module tree structure: + +'.gitignore': 'templates/dot~gitignore' +'.rubocop.yml': 'templates/dot~rubocop~root.yml' +'Gemfile': 'templates/puppet/Gemfile' +'README.md': 'templates/puppet/README.md.erb' +'lib/google/hash_utils.rb': 'google/hash_utils.rb' +'lib/google/string_utils.rb': 'google/string_utils.rb' +'metadata.json': 'templates/puppet/metadata.json.erb' +'spec/.rubocop.yml': 'templates/dot~rubocop~spec.yml' +'spec/bundle.rb': 'templates/bundle.rb.erb' +'spec/copyright.rb': 'spec/copyright.rb' +'spec/copyright_spec.rb': 'spec/copyright_spec.rb' +'spec/data/copyright_bad1.rb': 'spec/data/copyright_bad1.rb' +'spec/data/copyright_bad2.rb': 'spec/data/copyright_bad2.rb' +'spec/data/copyright_good1.rb': 'spec/data/copyright_good1.rb' +'spec/data/copyright_good2.rb': 'spec/data/copyright_good2.rb' +'spec/fake_auth.rb': 'templates/fake_auth.erb' +'spec/hash_utils_spec.rb': 'spec/hash_utils_spec.rb' +'spec/puppetlint_spec.rb': 'templates/puppet/puppetlint_spec.rb.erb' +'spec/spec_helper.rb': 'templates/puppet/spec_helper.rb.erb' +'spec/string_utils_spec.rb': 'spec/string_utils_spec.rb' +'spec/test_constants.rb': 'templates/test_constants.rb.erb' diff --git a/provider/puppet/common~copy.yaml b/provider/puppet/common~copy.yaml new file mode 100644 index 000000000000..67f726f7e00d --- /dev/null +++ b/provider/puppet/common~copy.yaml @@ -0,0 +1,20 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# These files are okay to be copied "AS-IS" to the module's final destination: + +# TODO(nelsonjr): Move all objects out of google/ namespace into google/product/ +'LICENSE': 'templates/LICENSE' +'Gemfile.lock': 'templates/puppet/Gemfile.lock' +'spec/data/poor_example.pp': 'templates/puppet/poor_example.pp' +'.tests/README.md': 'templates/end2end_README.md' diff --git a/provider/puppet/common~operating_systems.yaml b/provider/puppet/common~operating_systems.yaml new file mode 100644 index 000000000000..7601ac47ba1b --- /dev/null +++ b/provider/puppet/common~operating_systems.yaml @@ -0,0 +1,55 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# List of operating systems that are (allegedly) supported by all modules + +# TODO(nelsonjr): Either test on Windows or add a ROADMAP.md file saying so. +- !ruby/object:Provider::Config::OperatingSystem + name: RedHat + versions: + - '6' + - '7' +- !ruby/object:Provider::Config::OperatingSystem + name: CentOS + versions: + - '6' + - '7' +- !ruby/object:Provider::Config::OperatingSystem + name: Debian + versions: + - '7' + - '8' +- !ruby/object:Provider::Config::OperatingSystem + name: Ubuntu + versions: + - '12.04' + - '14.04' + - '16.04' + - '16.10' +- !ruby/object:Provider::Config::OperatingSystem + name: SLES + versions: + - '11-sp4' + - '12-sp2' +- !ruby/object:Provider::Config::OperatingSystem + name: openSUSE + versions: + - '13' +- !ruby/object:Provider::Config::OperatingSystem + name: Windows Server + versions: + - '2008 R2' + - '2012 R2' + - '2012 R2 Core' + - '2016 R2' + - '2016 R2 Core' diff --git a/provider/puppet/manifest.rb b/provider/puppet/manifest.rb new file mode 100644 index 000000000000..a104fdd6f010 --- /dev/null +++ b/provider/puppet/manifest.rb @@ -0,0 +1,42 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + class Puppet < Provider::Core + # Metadata for manifest.json + class Manifest < Api::Object + attr_reader :version + attr_reader :summary + attr_reader :requires + attr_reader :operating_systems + attr_reader :source + attr_reader :homepage + attr_reader :issues + attr_reader :tags + + def validate + check_property :homepage, String + check_property :issues, String + check_property :operating_systems, Array + check_property :requires, Array + check_property :source, String + check_property :summary, String + check_property :tags, Array + check_property :version, String + check_property_list :requires, @requires, Provider::Config::Requirements + check_property_list :operating_systems, @operating_systems, + Provider::Config::OperatingSystem + end + end + end +end diff --git a/provider/puppet/outline.rb b/provider/puppet/outline.rb new file mode 100644 index 000000000000..9d2628bd8306 --- /dev/null +++ b/provider/puppet/outline.rb @@ -0,0 +1,125 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + # Formats objects into README-style outlines that are puppet-lint compliant. + class PuppetOutline + def initialize(provider) + @provider = provider + end + + def generate(object) + extra = { + project: 'string', + credential: 'reference to gauth_credential' + } + + [ + "#{object.out_name} { 'id-of-resource':", + @provider.indent_list( + emit_manifest_block(object.all_user_properties, + extra), 2, true + ), + '}' + ] + end + + private + + def emit_manifest_block(props, extra) + max_key = max_key_length(props, extra) + + props.map { |p| formatter(p) } + .to_h + .merge(extra.map { |k, v| [k.id2name, v] }.to_h) + .sort_by { |k, _v| sort_by_manifest_key(k) } + .map do |k, v| + [k, ' ' * [0, max_key - k.length].max, ' => ', + v].join + end + end + + def sort_by_manifest_key(key) + if key.start_with?('ensure') + 'aaa_ensure' # forces ensure to be the first item + elsif key.start_with?('credential') + 'zzz_credential' # forces credential to be the last item + elsif key.start_with?('project') + 'zzy_project' # forces project to be the next-to-last item + else + key + end + end + + def formatter(p) + return p if p.is_a? String + [p.out_name, handlers.fetch(p.class, ->(v) { v.type.downcase }).call(p)] + end + + def format_values(start, values, stop, last_comma = true) + [start, indent_list(values, 2, last_comma), stop].join("\n") + end + + def handlers + { + Api::Type::NestedObject => ->(v) { emit_nested(v) }, + Api::Type::Enum => ->(v) { emit_enum(v) }, + Api::Type::Array => ->(v) { emit_array(v) }, + Api::Type::ResourceRef => + ->(v) { "reference to #{v.resource_ref.out_name}" } + }.freeze + end + + def emit_enum(p) + return 'Enum' if p.values.empty? + return p.values[0].to_s if p.values.length == 1 + + values = p.values.map { |val| "'#{val}'" } + + "#{values.first(values.size - 1).join(', ')} or #{values.last}" + end + + def emit_nested(p) + format_values('{', + emit_manifest_block(p.properties, {}), + '}') + end + + def emit_array(p) + item = if p.item_type.is_a? Api::Type::NestedObject + emit_nested(p.item_type) + elsif p.item_type.is_a? Api::Type::ResourceRef + "reference to a #{p.item_type.resource_ref.out_name}" + else + p.item_type.split('::').last.downcase + end + + format_values('[', [item, '...'], ']', false) + end + + def quote_string(s) + @provider.quote_string(s) + end + + def indent_list(list, indent, last_comma = true) + @provider.indent_list(list, indent, last_comma) + end + + def max_key_length(props, extra) + max_key_prop = props.max_by { |p| p.out_name.length } + max_key = max_key_prop.nil? ? 0 : max_key_prop.out_name.length + return max_key if extra.empty? + [max_key, extra.max_by { |k, _v| k.length }.first.length].max + end + end +end diff --git a/provider/puppet/test_manifest.rb b/provider/puppet/test_manifest.rb new file mode 100644 index 000000000000..c7c03c70850d --- /dev/null +++ b/provider/puppet/test_manifest.rb @@ -0,0 +1,146 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/test_data/generator' +require 'provider/test_data/formatter' + +module Provider + # Formats objects into Puppet manifests that are puppet-lint compliant. + class PuppetTestManifestFormatter < Provider::TestData::Formatter + def initialize(provider) + super(Provider::TestData::Generator.new) + @provider = provider + end + + # Will generate a full Puppet manifest for an entire dependency graph. + # Parameters: + # graph - A DependencyGraph + # base_name - The name of the object that has all of the dependencies. This + # ensures that base object has all of its properties outputted. + def generate_all_objects(graph, base_name, kind, extra) + graph.map do |object| + if object.object.name == base_name + generate_object(object.object, "title#{object.seed}", kind, + object.seed, extra) + else + generate_ref(object.object, object.seed) + end + end + end + + # Will generate a Puppet block for a specific object + def generate_object(object, title, kind, seed, extra) + props = select_properties(object.all_user_properties, kind, extra) + + extra = { + project: "'test project\##{seed} data'", + credential: "'cred#{seed}'" + }.merge(extra) + + # Puppet does not like when virtual resources have an ensure property + extra.delete(:ensure) if object.virtual + + [ + "#{object.out_name} { '#{title}':", + @provider.indent_list( + emit_manifest_block(props, seed, extra, {}), 2, true + ), + '}' + ] + end + + # Generates a resource block for a resource ref. + # Requires the ResourceRef and an index. + def generate_ref(ref, index) + ref_name = Google::StringUtils.underscore(ref.name) + generate_object(ref, "resource(#{ref_name},#{index})", :resource, + index, ensure: 'present') + end + + def emit_manifest_block(props, seed, extra, ctx) + max_key = max_key_length(props, extra) + + props.map { |p| emit_manifest_assign(p, seed, ctx, p.method(:out_name)) } + .to_h + .merge(extra.map { |k, v| [k.id2name, v] }.to_h) + .sort_by { |k, _v| sort_by_manifest_key(k) } + .map do |k, v| + [k, ' ' * [0, max_key - k.length].max, ' => ', + [v].flatten.join("\n")].join + end + end + + private + + def sort_by_manifest_key(key) + if key.start_with?('ensure') + 'aaa_ensure' # forces ensure to be the first item + elsif key.start_with?('credential') + 'zzz_credential' # forces credential to be the last item + elsif key.start_with?('project') + 'zzy_project' # forces project to be the next-to-last item + else + key + end + end + + def formatter(type, value) + raise "Unknown type '#{type}'" unless handlers.key?(type) + handlers[type].call(value) + end + + def format_values(start, values, stop) + [start, indent_list(values, 2), stop].join("\n") + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def handlers + { + Api::Type::Boolean => ->(v) { v ? 'true' : 'false' }, + Api::Type::Constant => ->(v) { quote_string(v.upcase) }, + Api::Type::Double => ->(v) { v }, + Api::Type::Enum => + ->(v) { quote_string(v.is_a?(Symbol) ? v.id2name : v.to_s) }, + Api::Type::Integer => ->(v) { v }, + Api::Type::String => ->(v) { quote_string(v) }, + Api::Type::Time => ->(v) { quote_string(v.iso8601) }, + Api::Type::Array => ->(v) { format_values('[', v, ']') }, + Api::Type::NestedObject => ->(v) { format_values('{', v, '}') }, + Api::Type::ResourceRef => ->(v) { quote_string(v) }, + Api::Type::Array::STRING_ARRAY_TYPE => + ->(v) { ['[', v.map { |e| quote_string(e) }.join(', '), ']'].join }, + Api::Type::Array::RREF_ARRAY_TYPE => + ->(v) { ['[', v.call(exported_values: false).join(', '), ']'].join }, + Api::Type::NameValues => ->(v) { format_values('{', v, '}') } + }.freeze + end + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize + + def quote_string(s) + @provider.quote_string(s) + end + + def indent_list(list, indent) + @provider.indent_list(list, indent, true) + end + + def max_key_length(props, extra) + max_key_prop = props.max_by { |p| p.out_name.length } + max_key = max_key_prop.nil? ? 0 : max_key_prop.out_name.length + return max_key if extra.empty? + [max_key, extra.max_by { |k, _v| k.length }.first.length].max + end + end +end diff --git a/provider/test_data/constants.rb b/provider/test_data/constants.rb new file mode 100644 index 000000000000..f8af8d251d4a --- /dev/null +++ b/provider/test_data/constants.rb @@ -0,0 +1,138 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/test_data/generator' + +module Provider + module TestData + # Responsible for all actions involving the Unit Test Constant values + # This includes building arrays of constants and building hashes of + # references to those values. + class Constants + MAX_TEST_DATAS = 5 + + def initialize(provider) + @provider = provider + @data_gen = Provider::TestData::Generator.new + end + + # Generates a hash mapping a object value's constant and the various + # semi-random values that can be used for tests based on a seed (that will + # be used to index the array. + # + # Example: + # + # { + # A_PROJECT_DATA: [ + # "'test project#0 data'", + # "'test project#1 data'", + # "'test project#2 data'", + # "'test project#3 data'", + # "'test project#4 data'" + # ] + # } + # + # See how it is aggregated by aggregate_constants_by_name. + def value_arrays(product) + var_data = product.objects.map do |object| + next if object.exclude + self_link_variables(object).map do |v| + [ + [test_prefix(object), v.upcase, 'DATA'].join('_'), + [object.name, v], + value_arrays_create_values(object, v) + ] + end + end + + aggregate_constants_by_name(var_data.flatten(1).compact) + end + + # Generates a series of hash mappings that map a object's self link + # variables to the test constant file + # + # Used primarily for uri_data functions + # + # Example: + # project: GoogleTests::Constants::F_PROJECT_DATA[(id - 1) \ + # % GoogleTests::Constants::F_PROJECT_DATA.size], + def value_assign(object) + self_link_variables(object).map do |v| + name = ['GoogleTests', 'Constants', + "#{test_prefix(object)}_#{v.upcase}_DATA"].join('::') + @provider.format( + [ + ["#{v}: #{name}[(id - 1) \\", + @provider.indent("% #{name}.size]", 2)], + [ + "#{v}:", + @provider.indent(["#{name}[(id - 1) \\", + @provider.indent("% #{name}.size]", 2)], 2) + ] + ], 0, 6 + 1 + ) # 1 extra for trailing comma + end + end + + private + + # Takes an array and aggregate by constant name, but combine the values + # that have the same data name + # + # [ + # ['A', 'Animal', [....] + # ['A', 'Ant', [....] + # ] + # + # Becomes: + # + # { + # 'A': { + # source: [ + # 'Animal' + # 'Ant' + # ] + # data: [....] + # } + # } + def aggregate_constants_by_name(all_data) + all_data.each_with_object({}) do |data, result| + result[data[0]] = {} unless result.key?(data[0]) + result[data[0]][:source] = [] unless result[data[0]].key?(:source) + result[data[0]][:source] << data[1] + result[data[0]][:data] = data[2] + end + end + + def value_arrays_create_values(object, variable) + test_values = [] + (0..MAX_TEST_DATAS - 1).each do |index| + property = @provider.variable_type(object, variable) + test_values << "'#{@data_gen.value(property.class, property, index)}'" + end + test_values + end + + def test_prefix(object) + object.name.gsub(/[a-z]/, '') + end + + def self_link_variables(object) + @provider.extract_variables([@provider.self_link_raw_url(object) + .join('/'), + @provider.collection_url(object)] + .join("\n")).uniq + end + end + end +end diff --git a/provider/test_data/create_data.rb b/provider/test_data/create_data.rb new file mode 100644 index 000000000000..4543a08d3dba --- /dev/null +++ b/provider/test_data/create_data.rb @@ -0,0 +1,237 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + module TestData + # Creates an expectation body for POST / DELETE Unit Tests + # rubocop:disable Metrics/ClassLength + class CreateData + def initialize(provider, data_gen) + @provider = provider + @data_gen = data_gen + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + # + # Returns a hash representing the data expected in a POST call to GCP + # Used for unit tests + # Parameters: + # path: Array of strings representing a path in provider.yaml file with + # user-overriden create data. + # has_name: Boolean, true if title != name + # tests: Hash from provider.yaml that may have user-overriden tests + # (These tests would be at tests[path]) + # object: The object being tested. + # + def create_expect_data(path, has_name, tests, object) + cust_result = Google::HashUtils.navigate(tests, path) + if cust_result.nil? + name_prop = object.all_user_properties.select { |p| p.name == 'name' } + expect = [] + expect << "'kind' => '#{object.kind}'" if object.kind? + expect.concat( + object.properties.reject(&:output) + .map do |prop| + expect_hash(prop, name_prop, has_name) + end + ) + expect.concat( + (object.parameters || []).select(&:input) + .map do |prop| + expect_hash(prop, name_prop, has_name) + end + ) + @provider.indent_list(expect, 0) + else + "load_network_result(#{quote_string(cust_result)})" + end + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + private + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + def expect_hash(prop, name_prop, has_name, seed = 0) + if prop.name == 'name' && !has_name && !name_prop.empty? + "'#{name_prop[0].field_name}' => 'title#{seed}'" + elsif prop.class <= Api::Type::Array + expect_array_hash(prop) + elsif prop.class <= Api::Type::NestedObject + ["'#{prop.field_name}' => {", + @provider.indent_list( + prop.properties.map { |p| expect_hash(p, [], false, seed) }, 2 + ), + '}'].join("\n") + elsif prop.is_a? Api::Type::ResourceRef + # All ResourceRefs should expect the fetched value + # Without this, the JSON call will be expecting the title of the + # ResourceRef block, not a value within that block. + ["'#{prop.field_name}'", '=>', value(prop.property.class, + prop.property, + seed)].join(' ') + else + ["'#{prop.field_name}'", '=>', value(prop.class, prop, + seed)].join(' ') + end + end + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/AbcSize + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def expect_array_hash(prop, seed = 0) + if prop.item_type.class <= Api::Type::NestedObject + [ + ["'#{prop.field_name}'", '=>', '['].join(' '), + expect_array_item_hash(prop, seed), + ']' + ] + elsif prop.item_type.class <= Api::Type::ResourceRef + [ + ["'#{prop.field_name}'", '=>', '['].join(' '), + expect_array_item_rref(prop, seed), + ']' + ] + elsif prop.is_a? Api::Type::ResourceRef + "'#{prop.field_name}' => #{value(prop.property.class, + prop.property, seed)}" + else + ["'#{prop.field_name}'", '=>', value(prop.class, + prop, seed)].join(' ') + end + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + def expect_array_item_rref(item, seed = 0) + size = @data_gen.object_size(item, seed, true) + imports = Google::StringUtils.underscore(item.item_type.imports) + resource = Google::StringUtils.underscore(item.item_type.resource) + @provider.indent_list( + (0..size - 1).map do |index| + "'#{imports.tr('_', '')}(resource(#{resource},#{index}))'" + end, 2 + ) + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/BlockLength + # rubocop:disable Metrics/MethodLength + def expect_array_item_hash(item, seed = 0) + size = @data_gen.object_size(item, seed, true) + @provider.indent_list( + (1..size).map do |index| + ['{', + @provider.indent_list( + item.item_type.properties.map do |prop| + if prop.is_a? Api::Type::NestedObject + ["'#{prop.field_name}' => {", + @provider.indent_list( + prop.properties.map do |p| + expect_hash(p, [], false, seed + index - 1) + end, 2 + ), + '}'].join("\n") + elsif prop.is_a? Api::Type::ResourceRef + # All ResourceRefs should expect the fetched value + # Without the JSON call will be expecting the title of the + # ResourceRef block, not a value within that block. + [ + "'#{prop.field_name}' =>", + value(prop.property.class, + prop.property, (seed + index - 1) % MAX_ARRAY_SIZE) + ].join(' ') + elsif prop.is_a? Api::Type::Array + expect_array_hash(prop, (seed + index - 1)) + else + [ + "'#{prop.field_name}'", '=>', + value(prop.class, prop, seed + index - 1) + ].join(' ') + end + end, 2 + ), + '}'] + end, 2 + ) + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/BlockLength + # rubocop:enable Metrics/MethodLength + + # Returns a value formatted according to its class. + def value(prop_class, prop, seed) + val = @data_gen.value(prop_class, prop, seed) + format_value(val) + end + + # Formats a value according to its class. + def format_value(value) + types.each do |k, v| + return v.call(value) if value.is_a? k + end + value + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def types + { + Integer => ->(value) { Google::IntegerUtils.underscore(value) }, + String => ->(value) { return_quoted(value) }, + Symbol => ->(value) { return_quoted(value.to_s) }, + Time => ->(value) { return_quoted(value.iso8601) }, + Array => lambda do |value| + return "%w[#{value.join(' ')}]" if value[0].is_a? String + value + end, + Float => lambda do |value| + values = value.to_s.split('.') + + [Google::IntegerUtils.underscore(values[0].to_i), '.', + values[1]].join + end, + Hash => lambda do |value| + values = value.map do |k, v| + "'#{k}' => #{format_value(v)}" + end + @provider.format([ + [ + '{', + @provider.indent_list(values, 2), + '}' + ].flatten + ], 0, 0) + end + } + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + # Return a string formatted with quotes + def return_quoted(value) + is_quoted = value[0] == '\'' && value[-1] == '\'' + return value if is_quoted + "'#{value}'" + end + end + # rubocop:enable Metrics/ClassLength + end +end diff --git a/provider/test_data/expectations.rb b/provider/test_data/expectations.rb new file mode 100644 index 000000000000..9591051fd2ca --- /dev/null +++ b/provider/test_data/expectations.rb @@ -0,0 +1,277 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/integer_utils' +require 'json' +require 'time' +require 'zlib' + +module Provider + module TestData + # rubocop:disable Metrics/ClassLength + # Builds out network data expectations for unit tests + class Expectations + def initialize(provider, data_gen) + @provider = provider + @data_gen = data_gen + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + # + # Creates all of the network expectations for a given test that involves + # creating an object. + # This includes a failure for a GET request to fetch an object, a POST to + # create the object and all resource refs. + # + # Requires: + # tests: config object for overriden tests + # object: An Api::Resource object. + # test: A hash with the following test information: + # path - the path on the config object with a possible test override + # has_name - boolean, true if title != name + # expected_data - a hash describing the POST request for creating + # a new object. + # rrefs: A list of Api::Type::ResourceRefs + def create_before_data(tests, object, test, rrefs) + cust_before = @provider.get_code_multiline(tests, test[:path]) + if cust_before.nil? + name_prop = object.all_user_properties.select { |p| p.name == 'name' } + # Get failed logic + get_failed = create_expectation('expect_network_get_failed', + test[:has_name], object, 12, [], + 1) + + extra_props = [] + extra_props << "#{name_prop[0].field_name}: 'title0'" \ + unless test[:has_name] || name_prop.empty? + + rref_list = object.uri_properties.map do |ref| + # We need to verify that only resourcerefs directly belonging to + # this object are inserted into the expectation. + next unless ref.is_a? Api::Type::ResourceRef + name = Google::StringUtils.underscore(ref.resource_ref.name) + value = @data_gen.value(ref.property.class, ref.property, 0) + { name => value } + end + + extra_props.concat( + rref_list.flatten.compact.reduce({}, :merge) + .map { |k, v| "#{k}: '#{v}'" } + ) + + code = [get_failed, 'expect_network_create \\', + @provider.indent( + if extra_props.empty? + ['1,', test[:expected_data]] + else + ['1,', '{', + @provider.indent(test[:expected_data], 2), + '},', @provider.indent_list(extra_props, 0)] + end, + 2 + )] + + unless object.async.nil? + code << create_expectation('expect_network_get_async', + test[:has_name], object, 12, [], 1) + end + + code.concat(create_resource_ref_get_success(object, rrefs, 12)) + + add_style_exemptions code, object, test + + code.flatten.compact.uniq + else + # rubocop:disable Security/Eval + eval("\"#{cust_before}\"") + # rubocop:enable Security/Eval + end + end + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/PerceivedComplexity + # Creates all of the network expectations for a given test that involves + # deleting an object. + # This includes a success for a GET request to fetch an object, a DELETE + # to delete the object and all resource refs. + # + # Requires: + # tests: config object for overriden tests + # object: An Api::Resource object. + # test: A hash with the following test information: + # exists - boolean, true if object exists + # path - the path on the config object with a possible test override + # has_name - boolean, true if title != name + # rrefs: A list of Api::Type::ResourceRefs + def delete_before_data(tests, object, test, rrefs) + cust_before = @provider.get_code_multiline(tests, test[:path]) + if cust_before.nil? + get = "expect_network_get_#{test[:exists] ? 'success' : 'failed'}" + get_line = create_expectation(get, test[:has_name], object, 12) + code = [get_line] + + if test[:exists] + has_rrefs = !object.uri_properties + .select { |p| p.is_a? Api::Type::ResourceRef } + .empty? + # Delete specifies name as a parameter, not as part of data + params = [] + params = ['nil'] if test[:has_name] && has_rrefs + params = ['\'title0\''] unless test[:has_name] + + code << create_expectation('expect_network_delete', + true, object, 12, params, 1) + + unless object.async.nil? + code << create_expectation('expect_network_get_async', + test[:has_name], object, 12, [], + 1) + end + end + + rrefs.each do |ref| + next if ref.object == object + name = Google::StringUtils.underscore(ref.object.name) + # Puppet style refs include a seed + code << create_expectation("expect_network_get_success_#{name}", + true, ref.object, 12, [], + (ref.seed % MAX_ARRAY_SIZE) + 1) + end + + code.flatten.uniq + else + # rubocop:disable Security/Eval + eval("\"#{cust_before}\"") + # rubocop:enable Security/Eval + end + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/ParameterLists + # Given an expectation (i.e. "expect_network_get_success"), + # returns that expectation with all necessary parameters. + # + # Example input: "expect_network_get_success", false, _, 0, 2" + # Example output: "expect_network_get_success 2, name: 'title1' + # + # Requires: + # func_name - name of expectation (i.e. "expect_network_get_success") + # has_name - boolean, true if title != name + # space_used - number of spaces used. Used to calculate indentation + # prop_list - List of properties appended to network request. + # id - the id used for loading network data yaml files + # rrefs - list of ResourceRefs + def create_expectation(func_name, has_name, object, space_used, + prop_list = [], id = 1) + prop_list << "name: 'title#{id - 1}'" unless has_name + + rref_list = object.uri_properties.map do |ref| + # We need to verify that only resourcerefs directly belonging to this + # object are inserted into the expectation. + next unless ref.is_a? Api::Type::ResourceRef + name = Google::StringUtils.underscore(ref.resource_ref.name) + value = @data_gen.value(ref.property.class, ref.property, id - 1) + { name => value } + end + + prop_list.concat( + rref_list.flatten.compact.reduce({}, :merge) + .map { |k, v| "#{k}: '#{v}'" } + ) + + return "#{func_name} #{id}" if prop_list.empty? + + prop_list.unshift id.to_s + + @provider.format([ + ["#{func_name} #{prop_list.join(', ')}"], + [ + "#{func_name} #{prop_list[0]},", + @provider.indent_list(prop_list.drop(1), + func_name.length + 1) + ], + [ + "#{func_name} \\", + @provider.indent_list(prop_list, 2) + ] + ], 0, space_used) + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/ParameterLists + + # Creates a single "expect_network_get_success" request for a resourceref + def create_resource_ref_get_success(object, refs, inside_indent) + # Generate network expectations for collected resourcerefs + refs.map do |ref| + # If the object being tested references itself (or another object of + # the same kind) skip generation of dependencies to avoid colliding + # with same ID objects + next if ref.object == object + + ref_name = Google::StringUtils.underscore(ref.object.name) + + # Find the machine resource to safity the object's dependency. If an + # object required by the object being tested in turn requires 1+ other + # objects they were collected by the 'manifester.collect_refs' call + # earlier. + # + # We now need to find the necessary dependencies and bind them to the + # object being emitted. + [ + create_expectation("expect_network_get_success_#{ref_name}", true, + ref.object, inside_indent, [], + (ref.seed % MAX_ARRAY_SIZE) + 1) + ] + end.compact + end + + private + + def add_style_exemptions(code, object, test) + rubo_off = @provider.get_rubocop_exceptions( + "spec/#{object.out_name}", :test, [object.name, test[:path]], + :disabled + ) + code.unshift rubo_off unless rubo_off.nil? || rubo_off.empty? + + rubo_on = @provider.get_rubocop_exceptions( + "spec/#{object.out_name}", :test, [object.name, test[:path]], :enabled + ) + code.push rubo_on unless rubo_on.nil? || rubo_on.empty? + end + + def get_required_prop_data(object) + all_props = object.parameters || [] + all_props += + object.properties.select { |p| p.name == 'name' || p.required } + all_props + end + end + # rubocop:enable Metrics/ClassLength + end +end diff --git a/provider/test_data/formatter.rb b/provider/test_data/formatter.rb new file mode 100644 index 000000000000..f3ff6e50df4e --- /dev/null +++ b/provider/test_data/formatter.rb @@ -0,0 +1,185 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Provider + # Responsible for creating the various parts of autogenerated unit tests. + module TestData + # Base class for builing out unit test manifests. + # rubocop:disable Metrics/ClassLength + class Formatter + def initialize(datagen) + @datagen = datagen + end + + # Generates a manifest / recipe for a DependencyGraph worth of resources. + # Requires: + # collector - The DependencyGraph of resources + # base_name - The name of the base object. + # kind - :name vs :title + # extra - extra attributes + def generate_all_objects(collector, base_name, kind, extra) + collector.map do |object| + if object.object.name == base_name + generate_object(object.object, "title#{object.seed}", kind, + object.seed, extra) + else + generate_ref(object.object, object.seed) + end + end + end + + # Generates a resource block for a resource ref. + # Requires the ResourceRef and an index. + def generate_ref(_ref, _index) + raise 'Implement generate_ref to output a formatted block' + end + + private + + # Outputs an entire block of testing data + def generate_object(_object, _title, _kind, _seed, _extra) + raise 'Implement generate to output a formatted block' + end + + def emit_manifest_block(_props, _seed, _extra, _ctx) + raise 'Implement emit_manifest_block to output formatetd properties' + end + + def formatter(_type, _value) + raise 'Implement the formatter to shape final output.' + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/PerceivedComplexity + # prop_name_method is a method that returns the proper name of the object + # rref_value: If true, return the value being exported by the ref'd block + # If false, return the title of the block being referenced + def emit_manifest_assign(prop, seed, ctx, prop_name_method, + rref_value = false) + name = prop_name_method.call + type = prop.class + if prop.is_a?(Api::Type::Primitive) + [name, formatter(type, @datagen.value(type, prop, seed))] + elsif prop.is_a?(Api::Type::Array) + emit_manifest_array(type, prop, seed, ctx, prop_name_method) + elsif prop.is_a?(Api::Type::NestedObject) + [name, formatter(type, emit_nested(prop, seed, ctx))] + elsif prop.is_a?(Api::Type::ResourceRef) + if rref_value + [name, formatter(type, emit_resource_value(prop, seed, ctx))] + else + [name, formatter(type, emit_resource(prop, seed, ctx))] + end + elsif prop.is_a?(Api::Type::NameValues) + [name, formatter(type, emit_namevalues(prop, seed, ctx))] + else + raise "Unknown property type: #{prop.class}" + end + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity + + # rubocop:disable Metrics/MethodLength + # prop_name_method should be a valid method on a Api::Type::* + # Typically, this will be "out_name" or "field_name" + def emit_manifest_array(type, prop, seed, ctx, prop_name_method) + subtype = prop.item_type_class + name = prop_name_method.call + [name, + if prop.item_type.is_a?(Api::Type::NestedObject) + formatter( + type, + @datagen + .value([type, subtype], prop, seed) + .call do |index| + formatter(subtype, + emit_nested(prop.item_type, seed + index - 1, ctx)) + end + ) + else + formatter([type, subtype], @datagen.value([type, subtype], + prop, seed)) + end] + end + # rubocop:enable Metrics/MethodLength + + def emit_nested(prop, seed, ctx) + emit_manifest_block(prop.properties, seed, {}, ctx) + end + + # Returns the title of the block being referenced + def emit_resource(prop, seed, _ctx) + name = Google::StringUtils.underscore(prop.resource_ref.name) + "resource(#{name},#{seed % MAX_ARRAY_SIZE})" + end + + # Returns the value being exported by a ResourceRef + def emit_resource_value(prop, seed, _ctx) + @datagen.value(prop.property.class, prop.property, + seed % MAX_ARRAY_SIZE) + end + + def emit_namevalues(prop, seed, _ctx) + values = @datagen.value(prop.class, prop, seed) + max_key = values.max_by { |k, _v| k }.first.length + values.map do |k, v| + formatted = v + + # Non numbers should be in quotes + formatted = "'#{v}'" unless v.is_a?(::Integer) || v.is_a?(::Float) + [quote_string(k), ' ' * [0, max_key - k.length].max, ' => ', + formatted].join + end + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/PerceivedComplexity + def select_properties(props, kind, extra) + name_props = props.select { |p| p.name == 'name' } + props = props.reject(&:output) + + props = props.select { |p| p.required || p.name == 'name' } \ + if (extra.key?(:ensure) && extra[:ensure].to_sym == :absent) || + (extra.key?(:action) && extra[:action] == ':delete') + + if kind == :resource + props = props.select { |p| p.required || p.name == 'name' } + # Name property may be output only, but is still needed. + if props.select { |p| p.name == 'name' }.empty? + props << name_props[0] unless name_props.empty? + end + props + elsif kind == :title + props.reject { |p| p.name == 'name' } + else + props + end + end + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/AbcSize + end + + def quote_string(s) + @provider.quote_string(s) + end + # rubocop:enable Metrics/ClassLength + end +end diff --git a/provider/test_data/generator.rb b/provider/test_data/generator.rb new file mode 100644 index 000000000000..c3a1717c41e0 --- /dev/null +++ b/provider/test_data/generator.rb @@ -0,0 +1,226 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'json' +require 'time' +require 'zlib' + +module Provider + module TestData + # Arrays are of size 3 to ensure that only 3 network response YAML files + # are needed for ResourceRefs + MAX_ARRAY_SIZE = 3 + + # A helper class to format data for testing purposes + # rubocop:disable Metrics/ClassLength + class Generator + def comparator(property) + generator = comparators.select do |type, _| + property.class <= type + end.values + raise "Unknown property type: #{property.class}" if generator.empty? + generator[0] + end + + def value(for_type, property, seed) + if for_type == Api::Type::Array + for_type = [Api::Type::Array, property.item_type_class] + end + raise "Unknown property type: #{for_type} @ #{property}" \ + unless values.key?(for_type) + values[for_type].call(property, seed) + end + + # NameValues and Arrays require a size. + # This function returns the size of a property of arbitrary size. + # Use inside_array to manually specify that this object is being created + # inside of an array (for resourceref counting purposes) + def object_size(prop, seed, inside_array = false) + size = (2 + integer_value(prop, seed) % 4) + inside_array ||= prop.is_a?(Api::Type::Array) + + # This Nested Object may contain a resourceref. + # If so, we need to ensure that there are only 3 objects in the array. + # 3 is chosen because only 3 network response YAML files are written + # per object. + alt_size = 1 + (size % MAX_ARRAY_SIZE) + + # Array of ResourceRefs + return alt_size if inside_array && + prop.item_type.is_a?(Api::Type::ResourceRef) + + # Array of NestedObjects with ResourceRefs + return alt_size if inside_array && + prop.item_type.is_a?(Api::Type::NestedObject) && + contains_resourcerefs?(prop.item_type) + size + end + + private + + # rubocop:disable Metrics/MethodLength + def values + { + Api::Type::Boolean => method(:boolean_value), + Api::Type::Constant => method(:constant_value), + Api::Type::Double => method(:double_value), + Api::Type::Enum => method(:enum_value), + Api::Type::Integer => method(:integer_value), + Api::Type::SelfLink => method(:selflink_value), + Api::Type::FetchedExternal => method(:string_value), + Api::Type::String => method(:string_value), + Api::Type::Time => method(:time_value), + Api::Type::Array::STRING_ARRAY_TYPE => method(:array_string), + Api::Type::Array::NESTED_ARRAY_TYPE => method(:array_nested_cb), + Api::Type::Array::RREF_ARRAY_TYPE => method(:array_rref_cb), + Api::Type::NameValues => method(:name_values), + Api::Type::ResourceRef => method(:resource_value), + Api::Type::NestedObject => method(:nested_value) + } + end + # rubocop:enable Metrics/MethodLength + + def comparators + { + Api::Type::Array => 'match_array', + Api::Type::Boolean => 'is', + Api::Type::Double => 'eq', + Api::Type::Enum => 'eq', + Api::Type::Integer => 'eq', + Api::Type::NameValues => 'eq', + Api::Type::NestedObject => 'eq', + Api::Type::ResourceRef => 'eq', + Api::Type::String => 'eq', + Api::Type::Time => 'eq' + } + end + + def nested_value(prop, seed) + Hash[prop.properties.map do |p| + [p.out_name, if p.type == Api::Type::Integer + calc_integer_value(p, seed) + else + value(p.class, p, seed) + end] + end] + end + + def boolean_value(_prop, seed) + (seed % 2).zero? + end + + def constant_value(prop, seed) + string_value(prop, seed).tr(' ', '').upcase + end + + def enum_value(prop, seed) + prop.values[seed % prop.values.length] + end + + def calc_double_value(prop, seed) + Zlib.crc32(prop.name).to_i * (seed + 1) * 0.67 + end + + def double_value(prop, seed) + value = calc_double_value(prop, seed) + [(value / 100).to_i, '.', (value % 100).to_i].join.to_f + end + + def integer_value(prop, seed) + calc_double_value(prop, seed).to_i + end + + def string_value(prop, seed) + "test #{prop.out_name}##{seed} data" + end + + def selflink_value(prop, seed) + name = Google::StringUtils.underscore(prop.resource.name) + "selflink(resource(#{name},#{seed}))" + end + + def time_value(prop, seed) + Time.at(integer_value(prop, seed)) + end + + def resource_value(prop, seed) + name = Google::StringUtils.underscore(prop.resource_ref.name) + "'resource(#{name},#{seed})'" + end + + def name_values(prop, seed) + size = object_size(prop, seed) + Hash[(1..size).map do |i| + [string_value(prop, seed + i), + if i.even? + integer_value(prop, seed + i) + else + string_value(prop, seed + i) + end] + end] + end + + # Returns a callback to process string values + def array_string(prop, seed) + size = object_size(prop, seed) + start = 'a'.ord + (integer_value(prop, seed) - 3) % 26 + start = 'z'.ord - size if start + size > 'z'.ord + (1..size).map { |i| (start + i).chr * 2 } + end + + def array_nested_cb(prop, seed) + lambda do |&block| + size = object_size(prop, seed) + (1..size).map { |index| block.call index } + end + end + + def array_rref_cb(prop, seed) + lambda do |hash| + size = object_size(prop, seed) + (0..size - 1).map do |index| + if hash[:exported_values] + # Return the exported value. + imports = prop.item_type.imports.downcase + resource = Google::StringUtils.underscore(prop.item_type.resource) + "#{imports}(resource(#{resource},#{index}))" + else + resource_value(prop.item_type, index) + end + end + end + end + + # Returns true if a NestedObject property contains a resourceref + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + def contains_resourcerefs?(prop) + return false unless prop.is_a? Api::Type::NestedObject + prop.properties.each do |p| + return true if p.is_a? Api::Type::ResourceRef + + if p.is_a? Api::Type::NestedObject + return true if contains_resourcerefs?(p) + elsif p.is_a? Api::Type::Array + return true if p.item_type == 'Api::Type::ResourceRef' + return true if contains_resourcerefs?(p.item_type) + end + end + false + end + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity + end + # rubocop:enable Metrics/ClassLength + end +end diff --git a/provider/test_data/property.rb b/provider/test_data/property.rb new file mode 100644 index 000000000000..4e97a9ce865f --- /dev/null +++ b/provider/test_data/property.rb @@ -0,0 +1,206 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/integer_utils' + +module Provider + module TestData + # Class responsible for generating the per-property tests + # rubocop:disable Metrics/ClassLength + class Property + def initialize(provider) + @provider = provider + end + + # This returns a formatted string representing a single Rspec test + # The test will be of the form: + # it { is_expected.to have_attributes({prop.name}: #{expected value}) } + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/ParameterLists + # rubocop:disable Metrics/PerceivedComplexity + def property(prop, index, comparator, value, start_indent = 0, + name_override = nil) + Google::LOGGER.info \ + "Generating test #{prop.out_name}[#{index}] #{comparator} #{value}" + + if prop.class <= Api::Type::ResourceRef + resourceref_property(prop, value, name_override) + elsif prop.class <= Api::Type::NameValues + namevalues_property(prop, value, name_override) + elsif prop.class <= Api::Type::NestedObject + nested_property(prop, value, name_override) + elsif prop.class <= Api::Type::Array \ + && prop.item_type != 'Api::Type::String' + array_property(prop, value, name_override) + elsif prop.class <= Api::Type::NameValues + namevalue_property(prop, value, name_override) + else + single_property(prop, value, start_indent, name_override) + end + end + # rubocop:enable Metrics/PerceivedComplexity + # rubocop:enable Metrics/ParameterLists + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/AbcSize + + private + + # rubocop:disable Metrics/MethodLength # long but easier to read together + def single_property(prop, value, start_indent, name_override) + name = name_override || prop.out_name + value = format_value(value) + @provider.format( + [ + ["it { is_expected.to have_attributes(#{name}: #{value}) }"], + [ + 'it do', + @provider.indent( + "is_expected.to have_attributes(#{name}: #{value})", + 2 + ), + 'end' + ], + [ + 'it do', + @provider.indent( + [ + 'is_expected', + @provider.indent(".to have_attributes(#{name}: #{value})", 2) + ], 2 + ), + 'end' + ], + [ + 'it do', + @provider.indent( + ['is_expected', + @provider.indent( + ['.to have_attributes(', + @provider.indent("#{name}: #{value}", 2), + ')'], 2 + )], 2 + ), + 'end' + ], + [ + 'it do', + @provider.indent( + [ + 'is_expected', + @provider.indent( + [ + '.to have_attributes(', + @provider.indent( + [ + "#{name}:", + value.to_s + ], 2 + ), + ')' + ], 2 + ) + ], 2 + ), + 'end' + ] + ], 0, start_indent + ) + end + # rubocop:enable Metrics/MethodLength + + def array_property(prop, _value, name_override) + name = name_override || prop.name + [ + '# TODO(nelsonjr): Implement complex array object test.', + "# it '#{name}' do", + '# # Add test code here', + '# end' + ] + end + + def namevalues_property(prop, _value, name_override) + name = name_override || prop.name + [ + '# TODO(nelsonjr): Implement complex namevalues property test.', + "# it '#{name}' do", + '# # Add test code here', + '# end' + ] + end + + def nested_property(prop, _value, name_override) + name = name_override || prop.name + [ + '# TODO(nelsonjr): Implement complex nested property object test.', + "# it '#{name}' do", + '# # Add test code here', + '# end' + ] + end + + def resourceref_property(prop, _value, name_override) + name = name_override || prop.name + [ + '# TODO(alexstephen): Implement resourceref test.', + "# it '#{name}' do", + '# # Add test code here', + '# end' + ] + end + + def namevalue_property(prop, _value, name_override) + name = name_override || prop.name + [ + '# TODO(alexstephen): Implement name values test.', + "# it '#{name}' do", + '# # Add test code here', + '# end' + ] + end + + # Returns a value formatted according to its class. + def format_value(value) + types.each do |k, v| + return v.call(value) if value.is_a? k + end + value + end + + def types + { + Integer => ->(value) { Google::IntegerUtils.underscore(value) }, + String => ->(value) { quote_string(value) }, + Symbol => ->(value) { quote_string(value.to_s) }, + Time => ->(value) { "::Time.parse('#{value.iso8601}')" }, + Array => lambda do |value| + return "%w[#{value.join(' ')}]" if value[0].is_a? String + # Arrays of non-String types are not fully supported. + # All tests using non-String arrays will return a blank test. + # This function may not return the expected value for Arrays + # of non-String types. + # TODO(alexstephen): Add support for testing arrays. + raise 'Non-String arrays are not supported.' + end + } + end + + def quote_string(value) + @provider.quote_string(@provider.unquote_string(value)) + end + end + # rubocop:enable Metrics/ClassLength + end +end diff --git a/provider/test_data/spec_formatter.rb b/provider/test_data/spec_formatter.rb new file mode 100644 index 000000000000..0c9ff68a6722 --- /dev/null +++ b/provider/test_data/spec_formatter.rb @@ -0,0 +1,156 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'provider/test_data/formatter' +require 'provider/test_data/generator' + +module Provider + module TestData + # Represents the test data + class TestData + def initialize(block) + @block = block + end + + # Creates a YAML representation of the test data + # All keys will be sorted according to sort_by_manifest_key + def to_yaml(opts = {}) + YAML.quick_emit(@block, opts) do |out| + out.map(nil, to_yaml_style) do |map| + @block.keys.sort_by { |k| sort_by_manifest_key(k) } + .each do |k| + map.add(k, @block[k]) + end + end + end + end + + def sort_by_manifest_key(key) + if key == 'kind' + 'aaa_kind' # forces key to be the first item + elsif key == 'name' + 'aab_name' # forces name to be the second item + elsif key == 'id' + 'aac_id' # forces id to be the third item + elsif key == 'selfLink' + 'zzz_selfLink' # forces selfLink to be the last item + else + key + end + end + end + # Creates the network data YAML for unit tests + class SpecFormatter < Formatter + def initialize(provider) + super(Provider::TestData::Generator.new) + @provider = provider + end + + # rubocop:disable Metrics/MethodLength + def generate(object, _, kind, seed, extra) + props = object.all_user_properties + name = Google::StringUtils.underscore(object.name) + + extra = extra.merge( + project: "'test project\##{seed} data'", + selfLink: "selflink(resource(#{name},#{seed}))", + name: extra[:name] + ) + + extra = extra.merge(kind: kind) if object.kind? + + props_nodot = props.reject { |p| p.name.include? '.' } + block = emit_manifest_block(props_nodot, seed, extra, {}) + + # TODO(alexstephen): Delete once deprecated is a real nested object. + props_with_dot = props.select { |p| p.name.include? '.' } + unless props_with_dot.empty? + block.merge!(emit_fake_nested_block(props_with_dot, + seed, {}, {})) + end + TestData.new(block) + end + # rubocop:enable Metrics/MethodLength + + def emit_manifest_block(props, seed, extra, ctx) + props.map do |p| + emit_manifest_assign(p, seed, ctx, p.method(:field_name), true) + end + .to_h + .merge(extra.map { |k, v| [k.id2name, v] }.to_h) + .to_h + end + + # TODO(alexstephen): Delete once deprecated is a real nested object. + # Takes nested objects using a layer1.layer2 notation and converts to a + # Nested Object style hash. + def emit_fake_nested_block(props, seed, extra, ctx) + unnested = props.map do |p| + emit_manifest_assign(p, seed, ctx, p.method(:field_name), true) + end + .to_h + .merge(extra.map { |k, v| [k.id2name, v] }.to_h) + .to_h + nested = {} + unnested.each do |k, v| + parts = k.split('.') + nested[parts[0]] = {} unless nested.key?(parts[0]) + nested[parts[0]][parts[1]] = v + end + nested + end + + # Emits a name value + # Should be a standard Hash, unlike other formatters. + def emit_namevalues(prop, seed, _ctx) + @datagen.value(prop.class, prop, seed) + end + + private + + def quote_string(s) + @provider.quote_string(s) + end + + def formatter(type, value) + raise "Unknown type '#{type}'" unless handlers.key?(type) + handlers[type].call(value) + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def handlers + { + Api::Type::Boolean => ->(v) { v }, + Api::Type::Constant => ->(v) { v }, + Api::Type::Double => ->(v) { v }, + Api::Type::Enum => + ->(v) { v.is_a?(Symbol) ? v.id2name : v.to_s }, + Api::Type::Integer => ->(v) { v }, + Api::Type::String => ->(v) { v }, + Api::Type::Time => ->(v) { v.iso8601 }, + Api::Type::Array => ->(v) { v }, + Api::Type::NestedObject => ->(v) { v }, + Api::Type::ResourceRef => ->(v) { v }, + Api::Type::Array::STRING_ARRAY_TYPE => + ->(v) { v.map { |e| e } }, + Api::Type::Array::RREF_ARRAY_TYPE => + ->(v) { v.call(exported_values: true) }, + Api::Type::NameValues => ->(v) { v } + }.freeze + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + end + end +end diff --git a/provider/test_matrix.rb b/provider/test_matrix.rb new file mode 100644 index 000000000000..3d37f2cc8ba9 --- /dev/null +++ b/provider/test_matrix.rb @@ -0,0 +1,300 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'compile/core' +require 'google/logger' + +module Provider + # Helper to track and validate the test matrix for the code + class TestMatrix # rubocop:disable Metrics/ClassLength + include Compile::Core + + # Tracks TestMatrix and verify them. + class Collector + def initialize + @matrixes = [] + end + + def add(matrix, file, object) + Google::LOGGER.info \ + "Registering test matrix for #{object.name} @ #{file}" + @matrixes << matrix + end + + def verify_all + @matrixes.each(&:verify) + end + end + + # Tracks globally all TestMatrix objects. + class Registry < Collector + include Singleton + end + + attr_reader :level + + def initialize(file, object, provider, expected) + Registry.instance.add self, file, object + + @file = file + @object = object + @provider = provider + @expected = expected + + # A collector for test contexts being produced. It will be used at the end + # of the file generation to ensure that all necessary tests were properly + # created. + @actual = [] + @level = 0 + @hierarchy = [] + end + + def push(ensurable, exists = :none, changes = :none, has_name = :none, + success = :none) + data = { + ensurable: ensurable, exists: exists, changes: changes, + has_name: has_name, success: success + } + + bundled = has_name.class <= Array + if bundled + push(ensurable, exists, changes, has_name[0]) + data[:has_name] = has_name[0] + data[:success] = has_name[1] + end + + update_level data, bundled + @hierarchy.push data + emit_push data + end + + def pop(ensurable, exists = :none, changes = :none, has_name = :none, + success = :none) + bundled = has_name.class <= Array + if bundled + bundle = has_name + has_name = bundle[0] + success = bundle[1] + end + + data = { + ensurable: ensurable, exists: exists, changes: changes, + has_name: has_name, success: success + } + + verify_pop data + + @hierarchy.pop + + pop(ensurable, exists, changes, bundle[0]) if bundled + + update_level data, bundled + lines(indent('end', @level * 2)) + end + + # Ensures that all test contexts are defined + def verify + Google::LOGGER.info "Verifying test matrix for #{@object.name} @ #{@file}" + verify_topics + verify_match_expectations + fail_if_not_all_popped unless @hierarchy.empty? + end + + private + + def emit_push(data) + validate_test_context_args(data) + + new_context = data.values.reject { |t| t == :ignore } + raise "Context already exists: #{new_context}" \ + if @actual.include?(new_context) + @actual << new_context + + format_response new_context, data + end + + def present?(needle, haystack) + needle.reduce(false) { |result, n| result || haystack.include?(n) } + end + + def format_response(new_context, data) + format_handlers = { + %i[pass fail] => :format_response_success, + %i[has_name no_name] => :format_response_title, + %i[changes no_change] => :format_response_changes, + %i[exists missing] => :format_response_exists, + %i[present absent] => :format_response_ensurable + } + + format_handlers.each do |values, handler| + return send(handler, data, @level * 2) if present?(values, new_context) + end + + raise "Unknown context: #{new_context}" + end + + def format_response_ensurable(data, spaces) + lines(indent("context 'ensure == #{data[:ensurable].id2name}' do", + spaces)) + end + + def format_response_exists(data, spaces) + lines(indent("context 'resource #{data[:exists].id2name}' do", spaces)) + end + + def format_response_changes(data, spaces) + prefix = data[:changes] == :changes ? '' : 'no ' + lines(indent([ + [ + "# Ensure #{data[:ensurable].id2name}: resource", + [data[:exists].id2name, + data[:changes].id2name].join(', ').tr('_', ' ') + ].join(' '), + "context '#{prefix}changes == #{prefix}action' do" + ], spaces)) + end + + def format_response_title(data, spaces) + msg = (data[:has_name] == :has_name ? 'title != name' : 'title == name') + lines(indent([["# Ensure #{data[:ensurable].id2name}: resource", + [ + data[:exists].id2name, data[:changes].id2name, + data[:has_name].id2name + ].join(', ').tr('_', ' ')].join(' '), + "context '#{msg}' do"], spaces)) + end + + def format_response_success(data, spaces) + msg = (data[:has_name] == :has_name ? 'title != name' : 'title == name') + lines(indent([["# Ensure #{data[:ensurable].id2name}: resource", + [ + data[:exists].id2name, data[:changes].id2name, + data[:has_name].id2name, data[:success].id2name + ].join(', ').tr('_', ' ')].join(' '), + "context '#{msg} (#{data[:success].id2name})' do"], spaces)) + end + + # Converts a tree style hash into a full array: + # A { + # B [ + # C + # D + # ] + # E [ + # F + # ] + # } + # will become: + # [[A, B, C], [A, B, D], [A, E, F] + def hash_explode(hash, tree = [], output = []) + hash.each_with_object(output) do |(k, v), results| + if v.class <= Hash + hash_explode(v, [tree, k], results) + else + v.each { |e| results << [tree, k, e].flatten } + end + end + end + + def lines(content) + @provider.lines(content) + end + + def validate_test_context_args(data) + valid_values = { + ensurable: %i[present absent ignore], + exists: %i[none exists missing], + changes: %i[none changes no_change ignore], + has_name: %i[none has_name no_name ignore], + success: %i[none pass fail] + } + + valid_values.each do |arg, values| + raise "Bad ensure argument #{data[arg]}" \ + unless values.include?(data[arg]) + end + end + + def verify_topics + processed = [%i[none none none none none]] + + @actual.each do |test| + test = test.reject { |t| t == :ignore } + test << :none while test.size < processed[0].size + + topic = topic_for_test(test) + raise "Missing topic: #{topic.reject { |t| t == :none }}" \ + unless processed.include?(topic) + + processed << test + end + end + + def topic_for_test(test) + topic = test.clone + + if test.index(:none).nil? + topic[topic.size - 1] = :none + else + topic[test.index(:none) - 1] = :none + end + + topic + end + + def fail_if_not_all_popped + Google::LOGGER.info 'Missing pop() calls:' + @hierarchy.each { |p| Google::LOGGER.info " - #{p}" } + raise "Missing pop() calls: #{@hierarchy}" + end + + def verify_match_expectations + missing = [] + extra = @actual.reject { |t| t.include?(:none) || t.include?(:ignore) } + hash_explode(@expected).each do |e| + missing << e unless @actual.include?(e) + extra.delete(e) + end + + fail_match_expectations_if_missing(missing) unless missing.empty? + fail_match_expectations_if_extra(extra) unless extra.empty? + + missing.empty? && extra.empty? + end + + def fail_match_expectations_if_missing(missing) + Google::LOGGER.info \ + 'FATAL: The following tests are missing from the matrix:' + missing.each { |t| Google::LOGGER.info " - #{t}" } + raise "The following tests are missing from the matrix: #{missing}" + end + + def fail_match_expectations_if_extra(extra) + Google::LOGGER.info \ + 'FATAL: The following tests are not defined in the matrix:' + extra.each { |t| Google::LOGGER.info " - #{t}" } + raise "The following tests are not defined in the matrix: #{extra}" + end + + def verify_pop(data) + raise "Cannot pop without a push for #{data}" if @hierarchy.empty? + raise "Unexpected pop for #{data}. Expecting pop for #{@hierarchy.last}" \ + unless @hierarchy.last == data + end + + def update_level(data, bundled) + @level = data.values.reject { |t| t == :none || t == :ignore }.size + @level -= 1 if bundled + end + end +end diff --git a/spec/.rubocop.yml b/spec/.rubocop.yml new file mode 100644 index 000000000000..049a7a0d0118 --- /dev/null +++ b/spec/.rubocop.yml @@ -0,0 +1,18 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +inherit_from: ../.rubocop.yml + +# Disable spec length as we bundle all tests in context blocks. +Metrics/BlockLength: + Enabled: false diff --git a/spec/compiler_spec.rb b/spec/compiler_spec.rb new file mode 100644 index 000000000000..59dbd5aa878e --- /dev/null +++ b/spec/compiler_spec.rb @@ -0,0 +1,66 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' +require 'api/compiler' + +describe Api::Compiler do + context 'should fail if file does not exist' do + subject { -> { Api::Compiler.new('spec/data/somedummyfile').run } } + it { is_expected.to raise_error(Errno::ENOENT) } + end + + context 'should use the file provided' do + let(:reader) { mock('reader') } + + subject { -> { Api::Compiler.new('my-file-to-parse.yaml').run } } + + before do + IO.expects(:read).with('my-file-to-parse.yaml') + .returns('--- !ruby/object:Api::Product + name: "foo"') + .twice + end + + it { is_expected.not_to raise_error } + end + + context 'parses file' do + subject { Api::Compiler.new('spec/data/good-file.yaml').run } + + before do + subject.validate + end + + it { is_expected.to be_instance_of Api::Product } + it { is_expected.to have_attributes(name: 'My Product') } + it { is_expected.to have_attribute_of_length(objects: 3) } + end + + context 'should only accept product' do + let(:reader) { mock('reader') } + + subject do + -> { Api::Compiler.new('my-file-to-parse.yaml').run.validate } + end + + before do + IO.expects(:read).with('my-file-to-parse.yaml') + .returns('something: "else"') + end + + it do + is_expected.to raise_error(StandardError, /is .* instead of Api::Product/) + end + end +end diff --git a/spec/copyright.rb b/spec/copyright.rb new file mode 100644 index 000000000000..322adced3f4e --- /dev/null +++ b/spec/copyright.rb @@ -0,0 +1,64 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'find' + +module Google + # Enforces copyright notices on source files. + class CopyrightChecker + def initialize(files) + @input_files = files + @copyrightable_files = { + /.*\.yaml$/ => /^# Copyright 201[78]/, + /.*\.rb$/ => /^# Copyright 201[78]/ + } + end + + def check_missing + checks = [method(:exist?), + method(:file?), + method(:suitable?), + method(:notice_present?)] + files = @input_files + checks.each { |test| files = test.call(files) } + files + end + + private + + def exist?(files) + not_found = files.reject { |f| File.exist?(f) } + raise "Some files were not found: #{not_found}" unless not_found.empty? + files + end + + def file?(files) + not_files = files.reject { |f| File.file?(f) } + raise "Some inputs were not files: #{not_files}" unless not_files.empty? + files + end + + def suitable?(files) + files.reject do |f| + @copyrightable_files.select { |c, _| c =~ f }.empty? + end + end + + def notice_present?(files) + files.select do |f| + mark = @copyrightable_files.reject { |c, _| (c =~ f).nil? } + File.readlines(f).select { |l| mark.values[0] =~ l }.empty? + end + end + end +end diff --git a/spec/copyright_spec.rb b/spec/copyright_spec.rb new file mode 100644 index 000000000000..f86be95adcf1 --- /dev/null +++ b/spec/copyright_spec.rb @@ -0,0 +1,68 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' +require 'find' +require 'copyright' + +describe 'ensure files have copyright notice' do + let(:my_path) { File.expand_path(__dir__) } + let(:my_root) { File.expand_path('..', __dir__) } + it do + files = Find.find(File.expand_path(File.join(my_path, '..'))) + .select { |f| File.file?(f) } + .select do |f| + my_tests = f.start_with?("#{my_path}/data/copyright_") + artifacts = f.start_with?("#{my_root}/build/") + presubmit = f.start_with?("#{my_root}/build/presubmit/") + + !my_tests && !artifacts || presubmit + end + checker = Google::CopyrightChecker.new(files) + missing = checker.check_missing.collect { |f| " - #{f}" } + raise "Files missing (or outdated) copyright:\n#{missing.join("\n")}" \ + unless missing.empty? + end +end + +describe 'check the checker' do + it 'should pass if all files are good' do + expect(Google::CopyrightChecker.new(['spec/data/copyright_good1.rb', + 'spec/data/copyright_good2.rb']) + .check_missing) + .to eq [] + end + + it 'should fail if files do not exist' do + expect do + Google::CopyrightChecker.new(['spec/data/copyright_good1.rb', + 'spec/data/copyright_bad1.rb', + 'spec/data/copyright_missing1.rb']) + .check_missing + end.to raise_error(StandardError, /not found.*missing1.rb/) + end + + it 'should trigger missing copyright' do + expect(Google::CopyrightChecker.new(['spec/data/copyright_good1.rb', + 'spec/data/copyright_bad1.rb', + 'spec/data/copyright_good2.rb']) + .check_missing) + .to contain_exactly 'spec/data/copyright_bad1.rb' + end + + it 'should fail if year is incorrect' do + expect(Google::CopyrightChecker.new(['spec/data/copyright_bad2.rb']) + .check_missing) + .to contain_exactly 'spec/data/copyright_bad2.rb' + end +end diff --git a/spec/data/chef-config.yaml b/spec/data/chef-config.yaml new file mode 100644 index 000000000000..f3c6903c4aeb --- /dev/null +++ b/spec/data/chef-config.yaml @@ -0,0 +1,27 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Chef::Config +examples: !ruby/object:Api::Resource::HashArray::NONE + reason: 'we do not need examples during these tests' +files: !ruby/object:Provider::Config::Files::NONE + reason: 'we do not need files during these tests' +objects: !ruby/object:Api::Resource::HashArray + ManagedZone: + flush: | + raise 'DNS Managed Zone cannot be edited' if @dirty + ResourceRecordSet: + template: + provider: 'dns/templates/resource_record_set.erb' +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'we do not need test data during these tests' diff --git a/spec/data/copyright_bad1.rb b/spec/data/copyright_bad1.rb new file mode 100644 index 000000000000..50ee5d33bee2 --- /dev/null +++ b/spec/data/copyright_bad1.rb @@ -0,0 +1,2 @@ +# Test file for CopyrightChecker with a good copyright notice above. +# This file is missing the copyright notice on purpose to test the checker. diff --git a/spec/data/copyright_bad2.rb b/spec/data/copyright_bad2.rb new file mode 100644 index 000000000000..0d0c07402ecd --- /dev/null +++ b/spec/data/copyright_bad2.rb @@ -0,0 +1,14 @@ +# Copyright 2014 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file contains an outdated copyright notice. It should fail the checker. diff --git a/spec/data/copyright_good1.rb b/spec/data/copyright_good1.rb new file mode 100644 index 000000000000..c250baec91aa --- /dev/null +++ b/spec/data/copyright_good1.rb @@ -0,0 +1,14 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Test file for CopyrightChecker with a good copyright notice above. diff --git a/spec/data/copyright_good2.rb b/spec/data/copyright_good2.rb new file mode 100644 index 000000000000..c250baec91aa --- /dev/null +++ b/spec/data/copyright_good2.rb @@ -0,0 +1,14 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Test file for CopyrightChecker with a good copyright notice above. diff --git a/spec/data/good-export-file.yaml b/spec/data/good-export-file.yaml new file mode 100644 index 000000000000..d337998e90a2 --- /dev/null +++ b/spec/data/good-export-file.yaml @@ -0,0 +1,86 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: My Product +prefix: myproduct +base_url: http://myproduct.google.com/api +scopes: + - http://scope-to-my-api/ +objects: + - !ruby/object:Api::Resource + name: 'ReferencedResource' + kind: 'myproduct#referencedresource' + base_url: 'referencedresource' + exports: + - name + description: 'blah blah' + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: 'an explanation whats this about' + - !ruby/object:Api::Resource + name: 'MyResource' + kind: 'myproduct#myresource' + description: 'blah blah' + exclude: true + - !ruby/object:Api::Resource + name: 'AnotherResource' + kind: 'myproduct#anotherresource' + base_url: 'anotherResource' + description: 'blah blah' + exports: + - !ruby/object:Api::Type::SelfLink + name: 'selfLink' + - property1 + - superLongName + parameters: + - !ruby/object:Api::Type::ResourceRef + resource: 'ReferencedResource' + name: 'property5' + imports: 'name' + description: 'an explanation whats this about' + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. + - !ruby/object:Api::Type::String + name: 'superLongName' + description: 'A single line description' + - !ruby/object:Api::Type::String + name: 'name' + description: 'A name for create_before_data' + - !ruby/object:Api::Type::Array + item_type: Api::Type::String + name: 'property3' + description: 'A single line description' + - !ruby/object:Api::Type::Enum + name: 'property4' + description: | + A long description for a property. Whenever the property description + is too long it may be formatted appropriately by the provider to look + good at the final file. + values: + - :value1 + - 'value2' + - 3 + - !ruby/object:Api::Type::Enum + name: 'property6' + description: | + A long description for a property. Whenever the property description + is too long it may be formatted appropriately by the provider to look + good at the final file. + values: + - 'a long value that is long' diff --git a/spec/data/good-file.yaml b/spec/data/good-file.yaml new file mode 100644 index 000000000000..78ce5294e915 --- /dev/null +++ b/spec/data/good-file.yaml @@ -0,0 +1,70 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: My Product +prefix: myproduct +base_url: http://myproduct.google.com/api +scopes: + - http://scope-to-my-api/ +objects: + - !ruby/object:Api::Resource + name: 'ReferencedResource' + kind: 'myproduct#referencedresource' + base_url: 'referencedresource' + exports: + - name + description: 'blah blah' + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: 'an explanation whats this about' + - !ruby/object:Api::Resource + name: 'MyResource' + kind: 'myproduct#myresource' + description: 'blah blah' + exclude: true + - !ruby/object:Api::Resource + name: 'AnotherResource' + kind: 'myproduct#anotherresource' + base_url: 'anotherResource' + description: 'blah blah' + parameters: + - !ruby/object:Api::Type::ResourceRef + name: 'property5' + resource: 'ReferencedResource' + imports: 'name' + description: 'an explanation whats this about' + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. + - !ruby/object:Api::Type::String + name: 'property2' + description: 'A single line description' + - !ruby/object:Api::Type::Array + item_type: Api::Type::String + name: 'property3' + description: 'A single line description' + - !ruby/object:Api::Type::Enum + name: 'property4' + description: | + A long description for a property. Whenever the property description + is too long it may be formatted appropriately by the provider to look + good at the final file. + values: + - :value1 + - 'value2' + - 3 diff --git a/spec/data/good-longuri.yaml b/spec/data/good-longuri.yaml new file mode 100644 index 000000000000..32c09f52d32f --- /dev/null +++ b/spec/data/good-longuri.yaml @@ -0,0 +1,31 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: My Product +prefix: myproduct +base_url: http://myproduct.google.com/myapi/v123 +scopes: + - http://scope-to-my-api/ +objects: + - !ruby/object:Api::Resource + name: 'MyResource' + kind: 'myproduct#myresource' + base_url: some/long/path/over/80chars + description: 'blah blah' + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. diff --git a/spec/data/good-multi-file.yaml b/spec/data/good-multi-file.yaml new file mode 100644 index 000000000000..767b4c4e5301 --- /dev/null +++ b/spec/data/good-multi-file.yaml @@ -0,0 +1,76 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: My Product +prefix: multiproduct +base_url: http://myproduct.google.com/api +scopes: + - http://scope-to-my-api/ +objects: + - !ruby/object:Api::Resource + name: 'ReferencedResource' + kind: 'myproduct#referencedresource' + base_url: 'referencedresource' + exports: + - name + description: 'blah blah' + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: 'an explanation whats this about' + - !ruby/object:Api::Resource + name: 'MyResource' + kind: 'multiproduct#myresource' + base_url: some/path/to/resources + description: 'blah blah' + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. + - !ruby/object:Api::Resource + name: 'AnotherResource' + kind: 'multiproduct#anotherresource' + base_url: some/path/to/anotherresources + description: 'blah blah' + parameters: + - !ruby/object:Api::Type::ResourceRef + resource: 'ReferencedResource' + name: 'property5' + imports: 'name' + description: 'hey, this is property number 5!' + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. + - !ruby/object:Api::Type::String + name: 'property2' + description: 'A single line description' + - !ruby/object:Api::Type::Array + item_type: Api::Type::String + name: 'property3' + description: 'A single line description' + - !ruby/object:Api::Type::Enum + name: 'property4' + description: | + A long description for a property. Whenever the property description + is too long it may be formatted appropriately by the provider to look + good at the final file. + values: + - :value1 + - 'value2' + - 3 diff --git a/spec/data/good-multi2-file.yaml b/spec/data/good-multi2-file.yaml new file mode 100644 index 000000000000..82f582f45a98 --- /dev/null +++ b/spec/data/good-multi2-file.yaml @@ -0,0 +1,83 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: My Product +prefix: multiproduct +base_url: http://myproduct.google.com/api +scopes: + - http://scope-to-my-api/ +objects: + - !ruby/object:Api::Resource + name: 'ReferencedResource' + kind: 'myproduct#referencedresource' + base_url: 'referencedresource' + description: 'blah blah' + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: 'an explanation whats this about' + - !ruby/object:Api::Resource + name: 'MyResource' + kind: 'multiproduct#resource' + base_url: some/path/to/resources + description: 'blah blah' + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. + - !ruby/object:Api::Resource + name: 'YetAnotherResource' + kind: 'multiproduct#yetanotherresource' + base_url: some/path/to/resources + description: 'blah blah' + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. + - !ruby/object:Api::Resource + name: 'AnotherResource' + kind: 'multiproduct#anotherresource' + base_url: some/path/to/anotherresources + description: 'blah blah' + parameters: + - !ruby/object:Api::Type::String + name: 'property5' + description: 'whats this is about' + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. + - !ruby/object:Api::Type::String + name: 'property2' + description: 'A single line description' + - !ruby/object:Api::Type::Array + item_type: Api::Type::String + name: 'property3' + description: 'A single line description' + - !ruby/object:Api::Type::Enum + name: 'property4' + description: | + A long description for a property. Whenever the property description + is too long it may be formatted appropriately by the provider to look + good at the final file. + values: + - :value1 + - 'value2' + - 3 diff --git a/spec/data/good-single-file.yaml b/spec/data/good-single-file.yaml new file mode 100644 index 000000000000..021c5681522d --- /dev/null +++ b/spec/data/good-single-file.yaml @@ -0,0 +1,31 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: My Product +prefix: myproduct +base_url: http://myproduct.google.com/api +scopes: + - http://scope-to-my-api/ +objects: + - !ruby/object:Api::Resource + name: 'MyResource' + kind: 'myproduct#myresource' + description: 'my resource description' + base_url: myresource + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. diff --git a/spec/data/good-single-readonly-file.yaml b/spec/data/good-single-readonly-file.yaml new file mode 100644 index 000000000000..f25764f29b9c --- /dev/null +++ b/spec/data/good-single-readonly-file.yaml @@ -0,0 +1,32 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: My Product +prefix: myproduct +base_url: http://myproduct.google.com/api +scopes: + - http://scope-to-my-api/ +objects: + - !ruby/object:Api::Resource + name: 'MyResource' + kind: 'myproduct#myresource' + description: 'my resource description' + base_url: myresource + readonly: true + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. diff --git a/spec/data/network_spec_template.erb b/spec/data/network_spec_template.erb new file mode 100644 index 000000000000..dd04eef9b4f3 --- /dev/null +++ b/spec/data/network_spec_template.erb @@ -0,0 +1,9 @@ +type: <%= object.out_name %> +<% unless object.parameters.nil? -%> +<% object.parameters.each do |param| -%> +parameter: <%= param.out_name -%> (<%= param.type -%>) +<% end -%> +<% end -%> +<% object.properties.each do |prop| -%> +property: <%= prop.out_name -%> (<%= prop.type -%>) +<% end -%> diff --git a/spec/data/prov_spec_template.erb b/spec/data/prov_spec_template.erb new file mode 100644 index 000000000000..e6ed43bbfcbd --- /dev/null +++ b/spec/data/prov_spec_template.erb @@ -0,0 +1 @@ +object: <%= object.out_name %> diff --git a/spec/data/prov_template.erb b/spec/data/prov_template.erb new file mode 100644 index 000000000000..cad10c3da220 --- /dev/null +++ b/spec/data/prov_template.erb @@ -0,0 +1 @@ +type: <%= object.out_name %> diff --git a/spec/data/puppet-config.yaml b/spec/data/puppet-config.yaml new file mode 100644 index 000000000000..28da02fef372 --- /dev/null +++ b/spec/data/puppet-config.yaml @@ -0,0 +1,27 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +examples: !ruby/object:Api::Resource::HashArray::NONE + reason: 'we do not need examples during these tests' +files: !ruby/object:Provider::Config::Files::NONE + reason: 'we do not need files during these tests' +objects: !ruby/object:Api::Resource::HashArray + ManagedZone: + flush: | + raise 'DNS Managed Zone cannot be edited' if @dirty + ResourceRecordSet: + template: + provider: 'dns/templates/resource_record_set.erb' +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'we do not need test data during these tests' diff --git a/spec/data/puppet-multicode.yaml b/spec/data/puppet-multicode.yaml new file mode 100644 index 000000000000..a3afc0d0646a --- /dev/null +++ b/spec/data/puppet-multicode.yaml @@ -0,0 +1,27 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Provider::Puppet::Config +examples: !ruby/object:Api::Resource::HashArray::NONE + reason: 'we do not need examples during these tests' +files: !ruby/object:Provider::Config::Files::NONE + reason: 'we do not need files during these tests' +objects: !ruby/object:Api::Resource::HashArray + MyResource: + create: | + longline-create-line1 + longline-create-line2 + longline-create-line3 + longline-create-line4 +test_data: !ruby/object:Provider::Config::TestData::NONE + reason: 'we do not need test data during these tests' diff --git a/spec/data/recipe_template.erb b/spec/data/recipe_template.erb new file mode 100644 index 000000000000..dd04eef9b4f3 --- /dev/null +++ b/spec/data/recipe_template.erb @@ -0,0 +1,9 @@ +type: <%= object.out_name %> +<% unless object.parameters.nil? -%> +<% object.parameters.each do |param| -%> +parameter: <%= param.out_name -%> (<%= param.type -%>) +<% end -%> +<% end -%> +<% object.properties.each do |prop| -%> +property: <%= prop.out_name -%> (<%= prop.type -%>) +<% end -%> diff --git a/spec/data/resourceref-missingimports.yaml b/spec/data/resourceref-missingimports.yaml new file mode 100644 index 000000000000..ada070c79994 --- /dev/null +++ b/spec/data/resourceref-missingimports.yaml @@ -0,0 +1,46 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: My Product +prefix: myproduct +base_url: http://myproduct.google.com/api +scopes: + - http://scope-to-my-api/ +objects: + - !ruby/object:Api::Resource + name: 'ReferencedResource' + kind: 'myproduct#referencedresource' + base_url: 'referencedresource' + description: 'blah blah' + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: 'an explanation whats this about' + - !ruby/object:Api::Resource + name: 'AnotherResource' + kind: 'myproduct#anotherresource' + base_url: 'anotherResource' + description: 'blah blah' + parameters: + - !ruby/object:Api::Type::ResourceRef + resource: 'ReferencedResource' + name: 'property5' + imports: 'missing' + description: 'an explanation whats this about' + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. diff --git a/spec/data/resourceref-missingresource.yaml b/spec/data/resourceref-missingresource.yaml new file mode 100644 index 000000000000..2f9e544648a4 --- /dev/null +++ b/spec/data/resourceref-missingresource.yaml @@ -0,0 +1,37 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- !ruby/object:Api::Product +name: My Product +prefix: myproduct +base_url: http://myproduct.google.com/api +scopes: + - http://scope-to-my-api/ +objects: + - !ruby/object:Api::Resource + name: 'AnotherResource' + kind: 'myproduct#anotherresource' + base_url: 'anotherResource' + description: 'blah blah' + parameters: + - !ruby/object:Api::Type::ResourceRef + resource: 'resource' + name: 'property5' + imports: 'name' + description: 'an explanation whats this about' + properties: + - !ruby/object:Api::Type::String + name: 'property1' + description: | + Some multiline + description for the property. diff --git a/spec/data/type_template.erb b/spec/data/type_template.erb new file mode 100644 index 000000000000..dd04eef9b4f3 --- /dev/null +++ b/spec/data/type_template.erb @@ -0,0 +1,9 @@ +type: <%= object.out_name %> +<% unless object.parameters.nil? -%> +<% object.parameters.each do |param| -%> +parameter: <%= param.out_name -%> (<%= param.type -%>) +<% end -%> +<% end -%> +<% object.properties.each do |prop| -%> +property: <%= prop.out_name -%> (<%= prop.type -%>) +<% end -%> diff --git a/spec/hash_utils_spec.rb b/spec/hash_utils_spec.rb new file mode 100644 index 000000000000..d97c3559f52d --- /dev/null +++ b/spec/hash_utils_spec.rb @@ -0,0 +1,55 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +describe Google::HashUtils do + context '#camelize_keys' do + subject do + described_class.camelize_keys(a_a_a: 'aaa', + bb_bb_bb: 'bbb', + abc_def_ghi: 'abcdefghi') + end + + it do + is_expected.to eq('aAA' => 'aaa', + 'bbBbBb' => 'bbb', + 'abcDefGhi' => 'abcdefghi') + end + end + + context '#navigate' do + let(:source) { { a: { b: { c: %i[d e] } } } } + let(:default) { Object.new } + + context 'find item middle' do + subject { described_class.navigate(source, %i[a b]) } + it { is_expected.to eq(c: %i[d e]) } + end + + context 'find item leaf' do + subject { described_class.navigate(source, %i[a b c]) } + it { is_expected.to eq(%i[d e]) } + end + + context 'item does not exist' do + subject { described_class.navigate(source, %i[d]) } + it { is_expected.to be nil } + end + + context 'returns default' do + subject { described_class.navigate(source, %i[d], default) } + it { is_expected.to eq default } + end + end +end diff --git a/spec/integer_utils_spec.rb b/spec/integer_utils_spec.rb new file mode 100644 index 000000000000..d946de0fedce --- /dev/null +++ b/spec/integer_utils_spec.rb @@ -0,0 +1,38 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +describe Google::IntegerUtils do + context '#underscore' do + context '0' do + subject { described_class.underscore(0) } + it { is_expected.to eq '0' } + end + + context 'full groups' do + subject { described_class.underscore(123_456_789) } + it { is_expected.to eq '123_456_789' } + end + + context 'non-full groups' do + subject { described_class.underscore(1_234_567_890) } + it { is_expected.to eq '1_234_567_890' } + end + + context 'middle zeros' do + subject { described_class.underscore(1_034_007_000) } + it { is_expected.to eq '1_034_007_000' } + end + end +end diff --git a/spec/network_blocker.rb b/spec/network_blocker.rb new file mode 100644 index 000000000000..0628a2366a00 --- /dev/null +++ b/spec/network_blocker.rb @@ -0,0 +1,142 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'net/http' +require 'singleton' + +module Google + module Codegen + # A helper class to block access to the network during tests, while + # providing a whitelist escape for the Google::*::Network::* classes to test + # themselves, also without accessing the network. + class NetworkBlocker + include Singleton + + ALLOWED_TEST_URI = URI.parse('https://www.unreachable-test-host.com/blah') + + attr_reader :allowed_test_hosts + attr_reader :allowed_request + attr_reader :canned_response + + def initialize + @allowed_test_hosts = [ + { host: ALLOWED_TEST_URI.host, port: ALLOWED_TEST_URI.port } + ] + end + + def allow_get(uri, code, type, body) + @allowed_request = { uri: uri } + @canned_response = response(uri, code, type, body) + end + + def allow_delete(uri) + @allowed_request = { uri: uri } + @canned_response = Net::HTTPNoContent.new(1.0, 204, 'No Content') + end + + def allow_post(args) + @allowed_request = { + uri: args[:uri_in], + type: args[:type_in], + body: args[:body_in] + } + @canned_response = response(args[:uri_out], args[:code], + args[:type_out], args[:body_out]) + end + + def allow_put(args) + allow_post(args) # PUT uses same interface as POST + end + + def deny(uri, code = 404) + case code + when 404 + @allowed_request = { uri: uri } + @canned_response = Net::HTTPNotFound.new(1.0, 404, 'Not Found') + else + raise ArgumentError, "Unknown error code #{code}" + end + end + + private + + def response(uri, code, type, body) + response = Net::HTTPOK.new(1.0, code, 'OK') + response.uri = uri + response.content_type = type + response.body = body + response.instance_variable_set(:@read, true) + response + end + end + end +end + +# Monkey patching of core Net::HTTP components to trap and block all network +# access, except to return canned responses when using the magic +# ALLOWED_TEST_URI URL. +module Net + class HTTP + define_method(:initialize) do |*args| + blocker = Google::Codegen::NetworkBlocker.instance + unless blocker.allowed_test_hosts.map { |h| h[:host] }.include?(args[0]) + message = [self, __method__, ':', + 'Network traffic is blocked during tests', ':', + args[0]].join(' ') + if ENV['RSPEC_DEBUG'] + module_dir = File.expand_path('..', File.dirname(__FILE__)) + puts [message, + caller.select { |c| c.include?(module_dir) }.join("\n") + .gsub(/^/, ' ')].join("\n") + end + raise IOError, message + end + end + + instance_methods.each do |m| + unless %i[get copy delete finish get get2 head head2 lock mkcol move + options patch post post2 propfind proppatch put put2 request + request_get request_head request_post request_put send + send_request start trace unlock].include?(m) + next + end + + # rubocop:disable Metrics/MethodLength + define_method(m) do |*args| + request_allowed = true + + blocker = Google::Codegen::NetworkBlocker.instance + if !args.empty? && args[0].is_a?(Net::HTTPGenericRequest) + allow_terms = blocker.allowed_request + allow_terms.each_key do |key| + case key + when :uri + request_allowed &&= args[0].uri == allow_terms[:uri] + when :type + request_allowed &&= args[0].content_type == allow_terms[:type] + when :body + request_allowed &&= args[0].body == allow_terms[:body] + end + end + end + # rubocop:enable Metrics/MethodLength + + return blocker.canned_response if request_allowed + + raise IOError, [self, __method__, ':', + 'Network traffic is blocked during tests', ':', + args[0]].join(' ') + end + end + end +end diff --git a/spec/object_spec.rb b/spec/object_spec.rb new file mode 100644 index 000000000000..023f58111708 --- /dev/null +++ b/spec/object_spec.rb @@ -0,0 +1,37 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +class TestObject < Api::Object::Named + attr_reader :some_property +end + +describe Api::Object do + context 'requires name' do + subject { -> { object('some_property: "bar"').validate } } + it { is_expected.to raise_error(StandardError, /Missing 'name'/) } + end + + context 'out_name underscore style' do + subject { object('name: "MyCamelCaseObjectName"').out_name } + it { is_expected.to eq 'my_camel_case_object_name' } + end + + private + + def object(*data) + Google::YamlValidator.parse(['--- !ruby/object:TestObject'].concat(data) + .join("\n")) + end +end diff --git a/spec/object_store_spec.rb b/spec/object_store_spec.rb new file mode 100644 index 000000000000..525989a52bb3 --- /dev/null +++ b/spec/object_store_spec.rb @@ -0,0 +1,75 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +describe Google::ObjectStore do + context 'Singleton' do + subject { -> { described_class.new } } + it { is_expected.to raise_error NoMethodError } + it { expect(described_class.instance).to be_a Singleton } + end + + context 'Object persisted' do + before(:all) do + described_class.instance.add SomeTypeForKey, SomeTypeForValue.new(1) + described_class.instance.add SomeTypeForKey, SomeTypeForValue.new(2) + end + + subject { described_class.instance[SomeTypeForKey.class] } + + it { is_expected.to all(be_a(SomeTypeForValue)) } + it { described_class.instance[SomeTypeForKey].size == 2 } + end + + context 'Mixed objects persisted' do + before(:all) do + described_class.instance.add SomeTypeForKey, SomeTypeForValue.new(1) + described_class.instance.add SomeOtherTypeForKey, SomeTypeForValue.new(2) + end + + context 'SomeTypeForKey' do + subject { described_class.instance[SomeTypeForKey.class] } + it { is_expected.to all(be_a(SomeTypeForValue)) } + it { !described_class.instance[SomeTypeForKey].empty? } + end + + context 'SomeOtherTypeForKey' do + subject { described_class.instance[SomeOtherTypeForKey.class] } + it { is_expected.to all(be_a(SomeTypeForValue)) } + it { !described_class.instance[SomeOtherTypeForKey].empty? } + end + end + + private + + module Puppet + def self.debug(msg) + puts "Puppet(debug): #{msg}" + end + end + + class SomeTypeForKey + end + + class SomeOtherTypeForKey + end + + class SomeTypeForValue + attr_reader :seed + + def initialize(seed) + @seed = seed + end + end +end diff --git a/spec/perc_bar_spec.rb b/spec/perc_bar_spec.rb new file mode 100644 index 000000000000..6237bba6e546 --- /dev/null +++ b/spec/perc_bar_spec.rb @@ -0,0 +1,76 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +$LOAD_PATH.unshift(File.expand_path('build/presubmit')) + +require 'spec_helper' +require 'perc_bar' + +describe Google::ProgressBar do + context 'same amount' do + subject { described_class.build(10, 10, 20) } + it { is_expected.to eq '[--| ]' } + end + + context 'increased a little' do + subject { described_class.build(10, 10.1, 20) } + it { is_expected.to include '[-|+ ]' } + it { is_expected.to start_with Google::ProgressBar::COLOR_CYAN } + it { is_expected.to end_with Google::ProgressBar::COLOR_NONE } + end + + context 'increased a lot' do + subject { described_class.build(10, 60, 20) } + it { is_expected.to include '[-|++++++++++ ]' } + it { is_expected.to start_with Google::ProgressBar::COLOR_CYAN } + it { is_expected.to end_with Google::ProgressBar::COLOR_NONE } + end + + context 'decreased a little' do + subject { described_class.build(10.1, 10, 20) } + it { is_expected.to include '[-|X ]' } + it { is_expected.to start_with Google::ProgressBar::COLOR_YELLOW } + it { is_expected.to end_with Google::ProgressBar::COLOR_NONE } + end + + context 'decreased a lot' do + subject { described_class.build(60, 10, 20) } + it { is_expected.to include '[-|XXXXXXXXXX ]' } + it { is_expected.to start_with Google::ProgressBar::COLOR_RED } + it { is_expected.to end_with Google::ProgressBar::COLOR_NONE } + end + + context 'small start' do + subject { described_class.build(0, 10, 20) } + it { is_expected.to include '[|++ ]' } + it { is_expected.to start_with Google::ProgressBar::COLOR_CYAN } + it { is_expected.to end_with Google::ProgressBar::COLOR_NONE } + end + + context 'small end' do + subject { described_class.build(10, 0, 20) } + it { is_expected.to include '[|XX ]' } + it { is_expected.to start_with Google::ProgressBar::COLOR_RED } + it { is_expected.to end_with Google::ProgressBar::COLOR_NONE } + end + + context 'good coverage. happy color' do + subject { described_class.build(60, 95, 20) } + it { is_expected.to start_with Google::ProgressBar::COLOR_GREEN } + end + + context 'bad coverage. dropping the ball' do + subject { described_class.build(90, 85, 20) } + it { is_expected.to start_with Google::ProgressBar::COLOR_RED } + end +end diff --git a/spec/product_spec.rb b/spec/product_spec.rb new file mode 100644 index 000000000000..cb0f3240c7d7 --- /dev/null +++ b/spec/product_spec.rb @@ -0,0 +1,97 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +describe Api::Product do + context 'requires name' do + subject { -> { product('prefix: "bar"').validate } } + it { is_expected.to raise_error(StandardError, /Missing 'name'/) } + end + + context 'requires prefix' do + subject do + lambda do + product('name: "foo"', + 'base_url: "http://foo/var/"', + 'objects:', + ' - !ruby/object:Api::Resource', + ' kind: foo#resource', + ' base_url: myres/', + ' description: foo', + ' name: "res1"', + ' properties:', + ' - !ruby/object:Api::Type', + ' description: foo', + ' name: var').validate + end + end + it { is_expected.to raise_error(StandardError, /Missing 'prefix'/) } + end + + context 'requires base_url' do + subject do + lambda do + product('name: "foo"', + 'prefix: "bar"', + 'objects:', + ' - !ruby/object:Api::Resource', + ' kind: foo#resource', + ' base_url: myres/', + ' description: foo', + ' name: "res1"', + ' properties:', + ' - !ruby/object:Api::Type', + ' name: var').validate + end + end + + it { is_expected.to raise_error(StandardError, /Missing 'base_url'/) } + end + + context 'requires objects' do + subject do + lambda do + product('name: "foo"', + 'prefix: "bar"', + 'base_url: "baz"').validate + end + end + it { is_expected.to raise_error(StandardError, /Missing 'objects'/) } + end + + context 'only allows Resources as objects' do + subject do + lambda do + product('name: "foo"', + 'prefix: "bar"', + 'base_url: "baz"', + 'objects:', + ' - bah. bad object!').validate + end + end + + it do + is_expected + .to raise_error(StandardError, + /Property.*objects:item.*instead.*Api::Resource/) + end + end + + private + + def product(*data) + Google::YamlValidator.parse(['--- !ruby/object:Api::Product'].concat(data) + .join("\n")) + end +end diff --git a/spec/provider_chef_spec.rb b/spec/provider_chef_spec.rb new file mode 100644 index 000000000000..2dc44f1cce42 --- /dev/null +++ b/spec/provider_chef_spec.rb @@ -0,0 +1,291 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +class File + class << self + alias real_open open + alias real_read read + end +end + +describe Provider::Chef::Config do + it 'returns Provider::Chef as provider' do + expect(Provider::Chef::Config.new.provider).to be Provider::Chef + end +end + +describe Provider::Chef do + context 'one type generated' do + let(:config) { Provider::Config.parse('spec/data/chef-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-file.yaml').run } + let(:provider) { Provider::Chef.new(config, product) } + + before do + allow_open 'spec/data/good-file.yaml' + allow_open 'spec/data/chef-config.yaml' + allow_open_license + allow_open_properties + allow_open_libraries + allow_open_typed_array + allow_open_spec_templates(true) + product.validate + end + + it do + out = dummy_writer + output_expectations kind: 'myproduct', name: 'another_resource', + provider: { writer: out, tester: out }, + recipe: out, + resourceref: { + name: 'referenced_resource', + imports: 'name', + writer: out + }, + arrays: [:string_array] + + provider.generate 'blah', [] + end + end + + context 'exports generated' do + before do + allow_open 'spec/data/good-export-file.yaml' + allow_open 'spec/data/chef-config.yaml' + allow_open_license + allow_open_properties + allow_open_libraries + allow_open_typed_array + allow_open_real_templates + product.validate + end + + let(:config) { Provider::Config.parse('spec/data/chef-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-export-file.yaml').run } + let(:provider) { Provider::Chef.new(config, product) } + let(:expected) do + ["self_link: __fetched['selfLink']", + 'property1: property1', + 'super_long_name: super_long_name'].to_set + end + + let(:matched) do + matched = Set.new + out = dummy_writer + provider_tester = mock('File') + provider_tester.stubs(:write).with(anything) do |arg| + match = expected.select { |e| arg.include?(e) } + matched << match[0] unless match.empty? + arg + end + output_expectations kind: 'myproduct', name: 'another_resource', + provider: { writer: provider_tester, tester: out }, + recipe: out, + resourceref: { + name: 'referenced_resource', + imports: 'name', + writer: out + }, + arrays: [:string_array] + + provider.generate 'blah', [] + matched + end + + subject { matched } + it { is_expected.to eq expected.to_set } + end + + private + + def allow_open(file_name) + IO.expects(:read).with(file_name).returns(File.real_read(file_name)) + .at_least(0) + end + + def allow_read(file_name) + File.expects(:read).at_least(0).with(file_name) + .returns(File.real_read(file_name)) + end + + def file_read_map(target, source) + File.expects(:read).with(target).returns(File.real_read(source)) + end + + # rubocop:disable Metrics/AbcSize + def output_expectations(data) + output_file "blah/resources/#{data[:name]}.rb", + data[:provider][:writer] + output_file "blah/spec/#{data[:name]}_spec.rb", data[:provider][:tester] + property_expectations(data) + output_expectations_libraries(data) + output_expectations_string_array data + + return unless data[:resourceref] + + # ResourceRef + output_file "blah/resources/#{data[:resourceref][:name]}.rb", + data[:resourceref][:writer] + output_file "blah/spec/#{data[:resourceref][:name]}_spec.rb", + data[:resourceref][:writer] + output_real_network_data("#{data[:kind]}_#{data[:resourceref][:name]}") + allow_open_real_network_data(data) + end + # rubocop:enable Metrics/AbcSize + + def output_expectations_string_array(data) + return if data[:arrays].nil? || !data[:arrays].include?(:string_array) + output_file \ + "blah/libraries/google/#{data[:kind][1..-1]}/property/string_array.rb", + dummy_writer + output_file \ + "blah/libraries/google/#{data[:kind][1..-1]}/property/array.rb", + dummy_writer + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def output_expectations_libraries(data) + dw = dummy_writer + output_file \ + "blah/libraries/google/#{data[:kind][1..-1]}/network/base.rb", dw + output_file \ + "blah/libraries/google/#{data[:kind][1..-1]}/network/delete.rb", dw + output_file \ + "blah/libraries/google/#{data[:kind][1..-1]}/network/get.rb", dw + output_file \ + "blah/libraries/google/#{data[:kind][1..-1]}/network/post.rb", dw + output_file \ + "blah/libraries/google/#{data[:kind][1..-1]}/network/put.rb", dw + output_file 'blah/spec/network_delete_spec.rb', dw + output_file 'blah/spec/network_get_spec.rb', dw + output_file 'blah/spec/network_post_spec.rb', dw + output_file 'blah/spec/network_put_spec.rb', dw + output_file 'blah/spec/network_blocker.rb', dw + output_file 'blah/spec/network_blocker_spec.rb', dw + end + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize + + def property_expectations(data) + dw = dummy_writer + output_file \ + "blah/libraries/google/#{data[:kind][1..-1]}/property/enum.rb", dw + output_file \ + "blah/libraries/google/#{data[:kind][1..-1]}/property/string.rb", dw + return unless data[:resourceref] + + resourceref_name = [data[:resourceref][:name].delete('_'), + data[:resourceref][:imports]].join('_') + output_file \ + ["blah/libraries/google/#{data[:kind][1..-1]}/property", + "#{resourceref_name}.rb"].join('/'), dw + end + + def allow_open_spec_templates(resourceref = false) + file_read_map 'templates/chef/resource.erb', 'spec/data/prov_template.erb' + file_read_map 'templates/chef/resource_spec.erb', + 'spec/data/prov_spec_template.erb' + 6.times.each do + file_read_map 'templates/network_spec.yaml.erb', + 'spec/data/network_spec_template.erb' + end + + allow_open_spec_templates(false) if resourceref + end + + def allow_open_real_templates + allow_read 'templates/chef/resource_spec.erb' + allow_read 'templates/chef/resource.erb' + + allow_open 'templates/autogen_notice.erb' + allow_open 'templates/expand_variables.erb' + allow_open 'templates/network_mocks.erb' + allow_open 'templates/network_spec.yaml.erb' + allow_open 'templates/provider_helpers.erb' + allow_open 'templates/chef/resourceref_expandvars.erb' + allow_open 'templates/resourceref_mocks.erb' + allow_open 'templates/return_if_object.erb' + allow_open 'templates/transport.erb' + + allow_open_real_tests + + allow_open_license + end + + def allow_open_real_tests + allow_open 'templates/chef/tests/present~create.erb' + allow_open 'templates/chef/tests/absent~no_action.erb' + allow_open 'templates/chef/tests/absent~delete.erb' + allow_open 'templates/chef/tests/present~no_changes.erb' + end + + def allow_open_libraries + allow_read 'templates/network/base.rb.erb' + allow_read 'templates/network/delete.rb.erb' + allow_read 'templates/network/delete_spec.rb.erb' + allow_read 'templates/network/get.rb.erb' + allow_read 'templates/network/get_spec.rb.erb' + allow_read 'templates/network/post.rb.erb' + allow_read 'templates/network/post_spec.rb.erb' + allow_read 'templates/network/put.rb.erb' + allow_read 'templates/network/put_spec.rb.erb' + allow_read 'templates/network/network_blocker.rb.erb' + allow_read 'templates/network/network_blocker_spec.rb.erb' + end + + def allow_open_typed_array + allow_open 'templates/chef/property/array_typed.rb.erb' + allow_open 'templates/chef/property/array.rb.erb' + end + + def allow_open_properties + allow_read 'templates/chef/property/array.rb.erb' + allow_read 'templates/chef/property/enum.rb.erb' + allow_read 'templates/chef/property/string.rb.erb' + allow_read 'templates/chef/property/resourceref.rb.erb' + end + + def allow_open_real_network_data(data) + name = "#{data[:kind]}_#{data[:name]}" + output_real_network_data(name) + end + + def output_real_network_data(name) + dw = dummy_writer + 3.times.each do |id| + %w[name title].each do |title| + output_file ['blah/spec/data/network', + "#{name}/success#{id + 1}~#{title}.yaml"].join('/'), + dw + end + end + end + + def allow_open_license + allow_open 'templates/license.erb' + allow_open 'templates/autogen_notice.erb' + end + + def output_file(name, output) + File.expects(:open).with(name, 'w').yields(output) + .at_least(0) + end + + def dummy_writer + out = mock('File') + out.expects(:write).at_least(0) + out + end +end diff --git a/spec/provider_config_spec.rb b/spec/provider_config_spec.rb new file mode 100644 index 000000000000..5be732ddf752 --- /dev/null +++ b/spec/provider_config_spec.rb @@ -0,0 +1,59 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +module Module1 + module Module2 + class MyConfig < Provider::Config + attr_reader :p1 + attr_reader :p2 + + def validate + # Not validating or invoking full validation for these tests + end + end + end +end + +describe Provider::Config do + it 'requires override provider' do + expect { Provider::Config.new.provider } + .to raise_error(StandardError, /provider not implemented/) + end + + context 'parsing validation' do + it 'allows any Provider::Config class' do + IO.expects(:read).with('foo/bar').twice.returns([ + '--- !ruby/object:Module1::Module2::MyConfig', + 'p1: A', + 'p2: B', + 'objects:', + ' - o1' + ].join("\n")) + + Provider::Config.parse('foo/bar') + end + + it 'fails if not a Provider::Config class' do + IO.expects(:read).with('foo/bar').returns([ + 'a: A', + 'b: B' + ].join("\n")) + + expect do + Provider::Config.parse('foo/bar') + end.to raise_error(StandardError, /is not a Provider::Config/) + end + end +end diff --git a/spec/provider_core_spec.rb b/spec/provider_core_spec.rb new file mode 100644 index 000000000000..2838ea6f4256 --- /dev/null +++ b/spec/provider_core_spec.rb @@ -0,0 +1,96 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +describe Provider::Core do + context '#format' do + subject { described_class.new(mock('config'), mock('api')) } + + it 'fails if cannot fit' do + expect do + subject.format [['x' * 21]], 0, 0, 20 + end.to raise_error ArgumentError, /No code fits/ + end + + it 'fails if cannot fit any' do + expect do + subject.format [['x' * 21], ['y' * 21], ['z' * 30]], 0, 0, 20 + end.to raise_error ArgumentError, /No code fits/ + end + + it 'fits 80 chars' do + subject.format [['x' * 80]] + end + + context 'fits 80 chars' do + subject do + described_class.new(nil, nil).format([ + ['x' * 80], + ['y' * 80], + ['z' * 80] + ]) + end + + it { is_expected.to include 'x' } + end + + context '#format(ident)' do + it 'fits' do + subject.format [['x' * 74]], 6 + end + + it 'does not fit' do + expect do + subject.format [['x' * 75]], 6 + end.to raise_error(ArgumentError, /No code fits/) + end + end + + context '#format(start)' do + it 'fits' do + subject.format [['x' * 74]], 0, 6 + end + + it 'does not fit' do + expect do + subject.format [['x' * 75]], 0, 6 + end.to raise_error(ArgumentError, /No code fits/) + end + end + + context '#format(start, indent)' do + it 'fits' do + subject.format [['x' * 66]], 8, 6 + end + + it 'does not fit' do + expect do + subject.format [['x' * 67]], 8, 6 + end.to raise_error(ArgumentError, /No code fits/) + end + end + + context 'selects second option' do + subject do + described_class.new(nil, nil).format([ + ['x' * 81], + ['y' * 80], + ['z' * 80] + ]) + end + + it { is_expected.to include 'y' } + end + end +end diff --git a/spec/provider_puppet_spec.rb b/spec/provider_puppet_spec.rb new file mode 100644 index 000000000000..4c7d6dc25016 --- /dev/null +++ b/spec/provider_puppet_spec.rb @@ -0,0 +1,688 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'set' +require 'spec_helper' + +class File + class << self + alias real_open open + alias real_read read + end +end + +describe Provider::Puppet::Config do + it 'returns Provider::Puppet as provider' do + expect(Provider::Puppet::Config.new.provider).to be Provider::Puppet + end +end + +describe Provider::Puppet do + context 'one type with resourceref generated' do + let(:config) { Provider::Config.parse('spec/data/puppet-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-file.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + + before do + allow_open 'spec/data/good-file.yaml' + allow_open 'spec/data/puppet-config.yaml' + allow_open_license + allow_open_typed_array + allow_open_properties + allow_open_libraries + allow_open_spec_templates(true) + product.validate + end + + it do + out = dummy_writer + output_expectations kind: 'myproduct', name: 'another_resource', + type: { writer: out, tester: out }, + provider: { writer: out, tester: out }, + resourceref: { + name: 'referenced_resource', + imports: 'name', + writer: out + }, + arrays: [:string_array] + provider.generate 'blah', [] + end + end + + context 'multiple types generated' do + let(:config) { Provider::Config.parse('spec/data/puppet-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-multi-file.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + + before do + allow_open 'spec/data/good-multi-file.yaml' + allow_open 'spec/data/puppet-config.yaml' + allow_open_typed_array + allow_open_properties + allow_open_libraries + allow_open_real_templates + product.validate + end + + it do + out1 = dummy_writer + out2 = dummy_writer + output_expectations kind: 'multiproduct', name: 'my_resource', + type: { writer: out1, tester: out1 }, + provider: { writer: out1, tester: out1 }, + resourceref: { + name: 'referenced_resource', + imports: 'name', + writer: out1 + } + output_expectations kind: 'multiproduct', name: 'another_resource', + type: { writer: out2, tester: out2 }, + provider: { writer: out2, tester: out2 }, + arrays: [:string_array] + provider.generate 'blah', [] + end + end + + context 'test template' do + let(:config) { Provider::Config.parse('spec/data/puppet-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-file.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + + before do + allow_open 'spec/data/good-file.yaml' + allow_open 'spec/data/puppet-config.yaml' + allow_open_license + allow_open_typed_array + allow_open_properties + allow_open_libraries + allow_open_spec_templates(true) + product.validate + end + + it do + out = dummy_writer + type_writer = mock('File') + type_writer.expects(:write).with("type: myproduct_another_resource\n") + type_writer.expects(:write).with("property: property1 (String)\n") + type_writer.expects(:write).with("property: property2 (String)\n") + type_writer.expects(:write).with("property: property3 (Array)\n") + type_writer.expects(:write).with("property: property4 (Enum)\n") + type_writer.expects(:write).with("parameter: property5 (ResourceRef)\n") + provider_writer = mock('File') + provider_writer.expects(:write).with("type: myproduct_another_resource\n") + output_expectations kind: 'myproduct', name: 'another_resource', + type: { writer: type_writer, tester: out }, + provider: { writer: provider_writer, tester: out }, + resourceref: { + name: 'referenced_resource', + imports: 'name', + writer: out + }, + arrays: [:string_array] + provider.generate 'blah', [] + end + end + + context 'long collection uri' do + let(:config) { Provider::Config.parse('spec/data/puppet-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-longuri.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + let(:expected) do + ['URI.join(', "'http://myproduct.google.com/myapi/v123',", + 'expand_variables(', "'some/long/path/over/80chars',", 'data', ')', + ')'] + end + + before do + allow_open 'spec/data/good-longuri.yaml' + allow_open 'spec/data/puppet-config.yaml' + allow_open_real_templates + allow_open_properties + allow_open_libraries + product.validate + end + + it do + collected = [] + out = dummy_writer + provider_writer = mock('File') + provider_writer.stubs(:write).with(anything) do |arg| + collected << arg.strip + end + output_expectations kind: 'myproduct', name: 'my_resource', + type: { writer: out, tester: out }, + provider: { writer: provider_writer, tester: out } + provider.generate 'blah', [] + expect(collected).to contain_array(expected) + end + end + + context 'multiline code format' do + let(:config) { Provider::Config.parse('spec/data/puppet-multicode.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-single-file.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + let(:expected) do + [" longline-create-line1\n", + " longline-create-line2\n", + " longline-create-line3\n", + " longline-create-line4\n"] + end + + before do + allow_open 'spec/data/good-single-file.yaml' + allow_open 'spec/data/puppet-multicode.yaml' + allow_open_real_templates + allow_open_properties + allow_open_libraries + product.validate + end + + it do + matched = [] + out = dummy_writer + provider_writer = mock('File') + provider_writer.stubs(:write).with(anything) do |arg| + match = expected.select { |e| arg.include?(e) } + matched << match[0] unless match.empty? + arg + end + output_expectations kind: 'myproduct', name: 'my_resource', + type: { writer: out, tester: out }, + provider: { writer: provider_writer, tester: out } + provider.generate 'blah', [] + expect(matched).to eq expected + end + end + + context 'readonly false sets all require requests' do + let(:config) { Provider::Config.parse('spec/data/puppet-multicode.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-single-file.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + let(:expected) do + ["require 'google/yproduct/network/delete'\n", + "require 'google/yproduct/network/post'\n", + "require 'google/yproduct/network/put'\n"] + end + + before do + allow_open 'spec/data/puppet-multicode.yaml' + allow_open 'spec/data/good-single-file.yaml' + allow_open_real_templates + allow_open_properties + allow_open_libraries + product.validate + end + + it 'blank or false readonly should have all requests required' do + matched = [] + out = dummy_writer + provider_writer = mock('File') + provider_writer.stubs(:write).with(anything) do |arg| + match = expected.select { |e| arg.include?(e) } + matched << match[0] unless match.empty? + arg + end + output_expectations kind: 'myproduct', name: 'my_resource', + type: { writer: out, tester: out }, + provider: { writer: provider_writer, tester: out } + provider.generate 'blah', [] + expect(matched).to eq expected + end + end + + context 'readonly true sets only get require requests' do + let(:config) { Provider::Config.parse('spec/data/puppet-multicode.yaml') } + let(:product) do + Api::Compiler.new('spec/data/good-single-readonly-file.yaml').run + end + let(:provider) { Provider::Puppet.new(config, product) } + let(:not_expected) do + ["require 'google/request/post'\n", + "require 'google/request/delete'\n"] + end + + before do + allow_open 'spec/data/puppet-multicode.yaml' + allow_open 'spec/data/good-single-readonly-file.yaml' + allow_open_real_templates + allow_open_properties + allow_open_libraries + product.validate + end + + it 'blank or false readonly should have all requests required' do + matched = [] + out = dummy_writer + provider_writer = mock('File') + provider_writer.stubs(:write).with(anything) do |arg| + match = not_expected.select { |e| arg.include?(e) } + matched << match[0] unless match.empty? + arg + end + output_expectations kind: 'myproduct', name: 'my_resource', + type: { writer: out, tester: out }, + provider: { writer: provider_writer, tester: out } + provider.generate 'blah', [] + expect(matched).to eq [] + end + end + + context 'filters creation based on command line list' do + let(:config) { Provider::Config.parse('spec/data/puppet-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-multi2-file.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + let(:type) { 'YetAnotherResource' } + + before do + allow_open 'spec/data/good-multi2-file.yaml' + allow_open 'spec/data/puppet-config.yaml' + allow_open_spec_templates + allow_open_properties + allow_open_libraries + allow_open_license + product.validate + end + + it do + out = dummy_writer + output_expectations kind: 'multiproduct', name: 'yet_another_resource', + type: { writer: out, tester: out }, + provider: { writer: out, tester: out } + + provider.generate 'blah', [type] + end + end + + context 'exports proper provider info when specified' do + before do + allow_open 'spec/data/good-export-file.yaml' + allow_open 'spec/data/puppet-config.yaml' + allow_open_typed_array + allow_open_real_templates + allow_open_properties + allow_open_libraries + product.validate + end + + let(:config) { Provider::Config.parse('spec/data/puppet-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-export-file.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + let(:expected) do + ['property1: resource[:property1]', + 'self_link: @fetched[\'selfLink\']', + 'super_long_name: resource[:super_long_name]'].to_set + end + + let(:matched) do + matched = [] + out = dummy_writer + provider_tester = mock('File') + provider_tester.stubs(:write).with(anything) do |arg| + match = expected.select { |e| arg.include?(e) } + matched << match[0] unless match.empty? + arg + end + output_expectations kind: 'myproduct', name: 'another_resource', + type: { writer: out, tester: out }, + provider: { writer: provider_tester, tester: out }, + resourceref: { + name: 'referenced_resource', + imports: 'name', + writer: out + }, + arrays: [:string_array] + provider.generate 'blah', [] + matched + end + + subject { matched.to_set } + + it { is_expected.to eq expected } + end + + context 'exports proper type info when specified' do + before do + allow_open 'spec/data/good-export-file.yaml' + allow_open 'spec/data/puppet-config.yaml' + allow_open_typed_array + allow_open_real_templates + allow_open_properties + allow_open_libraries + product.validate + end + + let(:config) { Provider::Config.parse('spec/data/puppet-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-export-file.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + let(:expected) do + ['def exports', + 'provider.exports'] + end + + let(:matched) do + matched = [] + out = dummy_writer + type_tester = mock('File') + type_tester.stubs(:write).with(anything) do |arg| + match = expected.select { |e| arg.include?(e) } + matched << match[0] unless match.empty? + arg + end + output_expectations kind: 'myproduct', name: 'another_resource', + type: { writer: type_tester, tester: out }, + provider: { writer: out, tester: out }, + resourceref: { + name: 'referenced_resource', + imports: 'name', + writer: out + }, + arrays: [:string_array] + provider.generate 'blah', [] + matched + end + + subject { matched } + + it { is_expected.to eq expected } + end + + context 'does not exports proper provider info when not specified' do + before do + allow_open 'spec/data/good-file.yaml' + allow_open 'spec/data/puppet-config.yaml' + allow_open_typed_array + allow_open_real_templates + allow_open_properties + allow_open_libraries + product.validate + end + + let(:config) { Provider::Config.parse('spec/data/puppet-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-file.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + let(:expected) do + ['self_link: @fetched[\'selfLink\']', + 'property1: @fetched[\'property1\']', + 'super_long_name: @fetched[\'superLongName\']'] + end + + let(:matched) do + matched = [] + out = dummy_writer + provider_tester = mock('File') + provider_tester.stubs(:write).with(anything) do |arg| + match = expected.select { |e| arg.include?(e) } + matched << match[0] unless match.empty? + arg + end + output_expectations kind: 'myproduct', name: 'another_resource', + type: { writer: out, tester: out }, + provider: { writer: provider_tester, tester: out }, + resourceref: { + name: 'referenced_resource', + imports: 'name', + writer: out + }, + arrays: [:string_array] + + provider.generate 'blah', [] + matched + end + + subject { matched } + + it { is_expected.to eq [] } + end + + context 'does not exports proper type info when not specified' do + before do + allow_open 'spec/data/good-file.yaml' + allow_open 'spec/data/puppet-config.yaml' + allow_open_typed_array + allow_open_real_templates + allow_open_properties + allow_open_libraries + product.validate + end + + let(:config) { Provider::Config.parse('spec/data/puppet-config.yaml') } + let(:product) { Api::Compiler.new('spec/data/good-file.yaml').run } + let(:provider) { Provider::Puppet.new(config, product) } + let(:expected) do + ['def exports', + 'provider.exports'] + end + + let(:matched) do + matched = [] + out = dummy_writer + type_tester = mock('File') + type_tester.stubs(:write).with(anything) do |arg| + match = expected.select { |e| arg.include?(e) } + matched << match[0] unless match.empty? + arg + end + output_expectations kind: 'myproduct', name: 'another_resource', + type: { writer: type_tester, tester: out }, + provider: { writer: out, tester: out }, + resourceref: { + name: 'referenced_resource', + imports: 'name', + writer: out + }, + arrays: [:string_array] + provider.generate 'blah', [] + matched + end + + subject { matched } + + it { is_expected.to eq [] } + end + + private + + def allow_open(file_name) + IO.expects(:read).with(file_name).returns(File.real_read(file_name)) + .at_least(0) + end + + def allow_read(file_name) + File.expects(:read).at_least(0).with(file_name) + .returns(File.real_read(file_name)) + end + + def file_read_map(target, source) + File.expects(:read).with(target).returns(File.real_read(source)) + end + + def output_expectations(data) + name = "#{data[:kind]}_#{data[:name]}" + output_file "blah/lib/puppet/type/#{name}.rb", data[:type][:writer] + output_file "blah/lib/puppet/provider/#{name}/google.rb", + data[:provider][:writer] + output_file "blah/spec/#{name}_provider_spec.rb", data[:provider][:tester] + + output_expectations_properties data + output_expectations_libraries data + output_expectations_string_array data + output_expectations_resource_ref data unless data[:resourceref].nil? + output_expectations_network_data data + end + + def output_expectations_network_data(data) + dw = dummy_writer + name = "#{data[:kind]}_#{data[:name]}" + 3.times.each do |id| + %w[name title].each do |title| + output_file ['blah/spec/data/network', + "#{name}/success#{id + 1}~#{title}.yaml"].join('/'), + dw + end + end + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def output_expectations_resource_ref(data) + rref_name = "#{data[:kind]}_#{data[:resourceref][:name]}" + rref_file = [data[:resourceref][:name].tr('_', ''), + data[:resourceref][:imports]].join('_') + + output_file "blah/lib/puppet/type/#{rref_name}.rb", + data[:resourceref][:writer] + output_file "blah/lib/puppet/provider/#{rref_name}/google.rb", + data[:resourceref][:writer] + output_file "blah/spec/#{rref_name}_provider_spec.rb", + data[:resourceref][:writer] + + output_file ["blah/lib/google/#{data[:kind][1..-1]}/property/", + "#{rref_file}.rb"].join, + data[:resourceref][:writer] + + 3.times.each do |id| + %w[name title].each do |title| + output_file ['blah/spec/data/network', + "#{rref_name}/success#{id + 1}~#{title}.yaml"].join('/'), + data[:resourceref][:writer] + end + end + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + # rubocop:disable Metrics/AbcSize + def output_expectations_libraries(data) + dw = dummy_writer + output_file "blah/lib/google/#{data[:kind][1..-1]}/network/base.rb", dw + output_file "blah/lib/google/#{data[:kind][1..-1]}/network/delete.rb", dw + output_file "blah/lib/google/#{data[:kind][1..-1]}/network/get.rb", dw + output_file "blah/lib/google/#{data[:kind][1..-1]}/network/post.rb", dw + output_file "blah/lib/google/#{data[:kind][1..-1]}/network/put.rb", dw + output_file 'blah/spec/network_delete_spec.rb', dw + output_file 'blah/spec/network_get_spec.rb', dw + output_file 'blah/spec/network_post_spec.rb', dw + output_file 'blah/spec/network_put_spec.rb', dw + output_file 'blah/spec/network_blocker.rb', dw + output_file 'blah/spec/network_blocker_spec.rb', dw + end + # rubocop:enable Metrics/AbcSize + + def output_expectations_properties(data) + dw = dummy_writer + output_file "blah/lib/google/#{data[:kind][1..-1]}/property/base.rb", dw + output_file "blah/lib/google/#{data[:kind][1..-1]}/property/enum.rb", dw + output_file "blah/lib/google/#{data[:kind][1..-1]}/property/resourceref.rb", + dw + output_file "blah/lib/google/#{data[:kind][1..-1]}/property/string.rb", dw + end + + def output_expectations_string_array(data) + return if data[:arrays].nil? || !data[:arrays].include?(:string_array) + output_file \ + "blah/lib/google/#{data[:kind][1..-1]}/property/string_array.rb", + dummy_writer + output_file \ + "blah/lib/google/#{data[:kind][1..-1]}/property/array.rb", + dummy_writer + output_file \ + "blah/lib/google/#{data[:kind][1..-1]}/property/base.rb", + dummy_writer + end + + def allow_open_libraries + allow_read 'templates/network/base.rb.erb' + allow_read 'templates/network/delete.rb.erb' + allow_read 'templates/network/delete_spec.rb.erb' + allow_read 'templates/network/get.rb.erb' + allow_read 'templates/network/get_spec.rb.erb' + allow_read 'templates/network/post.rb.erb' + allow_read 'templates/network/post_spec.rb.erb' + allow_read 'templates/network/put.rb.erb' + allow_read 'templates/network/put_spec.rb.erb' + allow_read 'templates/network/network_blocker.rb.erb' + allow_read 'templates/network/network_blocker_spec.rb.erb' + end + + def allow_open_properties + allow_read 'templates/puppet/property/base.rb.erb' + allow_read 'templates/puppet/property/enum.rb.erb' + allow_read 'templates/puppet/property/resourceref.rb.erb' + allow_read 'templates/puppet/property/string.rb.erb' + end + + def allow_open_spec_templates(rref = false) + file_read_map 'templates/puppet/type.erb', 'spec/data/type_template.erb' + file_read_map 'templates/puppet/provider.erb', 'spec/data/prov_template.erb' + file_read_map 'templates/puppet/provider_spec.erb', + 'spec/data/prov_spec_template.erb' + # 6 combinations of success1-3 and name/title + 6.times.each do + file_read_map 'templates/network_spec.yaml.erb', + 'spec/data/network_spec_template.erb' + end + + # Templates will be opened a second time to create the resourceref + allow_open_spec_templates if rref + end + + def allow_open_real_templates + allow_read 'templates/puppet/type.erb' + allow_read 'templates/puppet/provider.erb' + allow_read 'templates/puppet/provider_spec.erb' + + allow_open 'templates/expand_variables.erb' + allow_open 'templates/provider_helpers.erb' + allow_open 'templates/network_mocks.erb' + allow_open 'templates/network_spec.yaml.erb' + allow_open 'templates/return_if_object.erb' + allow_open 'templates/transport.erb' + + allow_open_real_tests + allow_open_real_resourceref + + allow_open_license + end + + def allow_open_real_resourceref + allow_open 'templates/resourceref_mocks.erb' + allow_open 'templates/puppet/resourceref_expandvars.erb' + end + + def allow_open_real_tests + allow_open 'templates/puppet/test~absent~delete.erb' + allow_open 'templates/puppet/test~absent~no_action.erb' + allow_open 'templates/puppet/test~present~create.erb' + allow_open 'templates/puppet/test~present~no_changes.erb' + end + + def allow_open_license + allow_open 'templates/autogen_notice.erb' + allow_open 'templates/license.erb' + end + + def allow_open_typed_array + allow_open 'templates/puppet/property/array_typed.rb.erb' + allow_read 'templates/puppet/property/array.rb.erb' + end + + def output_file(name, output) + File.expects(:open).with(name, 'w').yields(output).at_least(0) + end + + def dummy_writer + out = mock('File') + out.expects(:write).at_least(0) + out + end +end diff --git a/spec/resource_spec.rb b/spec/resource_spec.rb new file mode 100644 index 000000000000..5b3bbb7985d6 --- /dev/null +++ b/spec/resource_spec.rb @@ -0,0 +1,26 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' +require 'api/object' + +RSpec.describe Api::Object do + it 'uses product base_url if missing' do + end + + it 'ignores product base_url if absolute' do + end + + it 'combines base_url with product if relative' do + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 000000000000..30e8b601cc38 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,65 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'simplecov' +SimpleCov.start + +RSpec.configure do |config| + config.mock_with :mocha +end + +$LOAD_PATH.unshift(File.expand_path('.')) + +# Prevents any network access during tests +require 'network_blocker' + +ENV['GOOGLE_LOGGER'] = '0' unless ENV['RSPEC_DEBUG'] + +%w[api google provider].each do |subsystem| + Dir[File.join(subsystem, '**', '*.rb')].reject { |p| File.directory? p } + .each do |f| + puts "Auto requiring #{f}" \ + if ENV['RSPEC_DEBUG'] + require f + end +end + +RSpec.configure do |config| + config.mock_with :mocha +end + +RSpec::Matchers.define :have_attribute_of_length do |expected| + match do |actual| + expected.each do |name, length| + my_len = actual.send(name).length + raise "#{name} should have #{length} elements, but found only #{my_len}" \ + unless my_len == length + end + end +end + +RSpec::Matchers.define :contain_array do |expected| + match do |actual| + while actual.include?(expected[0]) + start = actual.index(expected[0]) + actual = actual.drop(start) unless start.nil? + return true if actual[0, expected.size] == expected + actual = actual.drop(1) + end + false + end +end + +require 'pp' + +Google::LOGGER.info 'Runing tests' diff --git a/spec/string_utils_spec.rb b/spec/string_utils_spec.rb new file mode 100644 index 000000000000..110b1eedad93 --- /dev/null +++ b/spec/string_utils_spec.rb @@ -0,0 +1,26 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +describe Google::StringUtils do + context '#camelize' do + subject { described_class.camelize('some_string_with_underscores') } + it { is_expected.to eq 'someStringWithUnderscores' } + end + + describe '#underscore' do + subject { described_class.underscore('aStringInCamelCase') } + it { is_expected.to eq 'a_string_in_camel_case' } + end +end diff --git a/spec/test_matrix_spec.rb b/spec/test_matrix_spec.rb new file mode 100644 index 000000000000..561145ad04d7 --- /dev/null +++ b/spec/test_matrix_spec.rb @@ -0,0 +1,162 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +describe Provider::TestMatrix do + let(:provider) { mock('provider') } + let(:object) { mock('object') } + let(:expected) do + { + exists: { + changes: %i[pass fail], + no_change: %i[fail] + } + } + end + + before(:each) do + provider.stubs(:lines) + object.stubs(:name).returns('test') + end + + subject { described_class.new __FILE__, object, provider, expected } + + context 'good matrix' do + before(:each) do + subject.push(:ignore, :exists) + subject.push(:ignore, :exists, :changes) + subject.push(:ignore, :exists, :changes, :ignore, :pass) + subject.pop(:ignore, :exists, :changes, :ignore, :pass) + subject.push(:ignore, :exists, :changes, :ignore, :fail) + subject.pop(:ignore, :exists, :changes, :ignore, :fail) + subject.pop(:ignore, :exists, :changes) + subject.push(:ignore, :exists, :no_change) + subject.push(:ignore, :exists, :no_change, :ignore, :fail) + subject.pop(:ignore, :exists, :no_change, :ignore, :fail) + subject.pop(:ignore, :exists, :no_change) + subject.pop(:ignore, :exists) + end + + it { expect { subject.verify }.not_to raise_error } + end + + context 'cannot have duplicates' do + context 'sequential' do + before(:each) do + subject.push(:ignore, :exists) + end + + it do + expect { subject.push(:ignore, :exists) }.to \ + raise_error(RuntimeError, /already exists/) + end + end + + context 'out of order' do + before(:each) do + subject.push(:ignore, :exists) + subject.push(:ignore, :exists, :changes) + end + + it do + expect { subject.push(:ignore, :exists) }.to \ + raise_error(RuntimeError, /already exists/) + end + end + end + + context 'cannot #pop out of order' do + before(:each) do + subject.push(:ignore, :exists) + subject.push(:ignore, :exists, :changes) + end + + it do + expect { subject.pop(:ignore, :exists) }.to \ + raise_error(RuntimeError, + /Unexpected pop.*:ignore.*:exists.*:none.*:none.*:none/) + end + end + + context 'cannot miss a test' do + before(:each) do + subject.push(:ignore, :exists) + subject.push(:ignore, :exists, :changes) + subject.push(:ignore, :exists, :changes, :ignore, :pass) + subject.pop(:ignore, :exists, :changes, :ignore, :pass) + # we will miss push+pop for :ignore, :exists, :changes, :ignore, :fail + subject.pop(:ignore, :exists, :changes) + subject.push(:ignore, :exists, :no_change) + subject.push(:ignore, :exists, :no_change, :ignore, :fail) + subject.pop(:ignore, :exists, :no_change, :ignore, :fail) + subject.pop(:ignore, :exists, :no_change) + subject.pop(:ignore, :exists) + end + + it do + expect { subject.verify }.to \ + raise_error(RuntimeError, /missing.*\[:exists, :changes, :fail\]/) + end + end + + context 'cannot miss a pop()' do + before(:each) do + subject.push(:ignore, :exists) + subject.push(:ignore, :exists, :changes) + subject.push(:ignore, :exists, :changes, :ignore, :pass) + subject.pop(:ignore, :exists, :changes, :ignore, :pass) + subject.push(:ignore, :exists, :changes, :ignore, :fail) + subject.pop(:ignore, :exists, :changes, :ignore, :fail) + # we will miss pop for :ignore, :exists, :changes + subject.push(:ignore, :exists, :no_change) + subject.push(:ignore, :exists, :no_change, :ignore, :fail) + subject.pop(:ignore, :exists, :no_change, :ignore, :fail) + subject.pop(:ignore, :exists, :no_change) + end + + it do + expect(-> { subject.pop(:ignore, :exists) }).to \ + raise_error(RuntimeError, + /Expect.*pop for.*:ignore.*:exists.*:changes.*:none.*:none/) + end + end + + context 'matrixes are added to the collection automatically' do + let(:registry) { Provider::TestMatrix::Registry.instance } + + before(:each) do + registry.stubs(:add).once # ensures add() is called + end + + after(:each) { registry.unstub(:add) } + + it { is_expected.not_to be_nil } + end +end + +describe Provider::TestMatrix::Collector do + context 'matrixes are verified' do + let(:matrixes) { [mock('matrix-1'), mock('matrix-2')] } + + before(:each) do + matrixes.each do |m| + m.stubs(:verify).once # ensures verify() is called + m.stubs(:name).returns(m) + subject.add(m, __FILE__, m) + end + end + + it { expect { subject.verify_all }.not_to raise_error } + end +end diff --git a/spec/type_spec.rb b/spec/type_spec.rb new file mode 100644 index 000000000000..834b6d8d3f59 --- /dev/null +++ b/spec/type_spec.rb @@ -0,0 +1,265 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +module Level1 + module Level2 + class MyType < Api::Type + attr_reader :myproperty + end + end +end + +describe Api::Type do + context 'requires name' do + subject { -> { Api::Type.new.validate } } + it { is_expected.to raise_error(StandardError, /Missing 'name'/) } + end + + context 'requires description' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Api::Type + name: "test"').validate + end + end + it { is_expected.to raise_error(StandardError, /Missing 'description'/) } + end + + context 'type name' do + subject do + Google::YamlValidator.parse('--- !ruby/object:Level1::Level2::MyType + name: "test" + description: "mydescription" + myproperty: 10') + end + it { is_expected.to have_attributes(type: 'MyType') } + end + + context 'allows specific properties on children' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Level1::Level2::MyType + name: "test" + description: "mydescription" + myproperty: 10').validate + end + end + it { is_expected.not_to raise_error } + end + + context 'prevents extraneous values' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Level1::Level2::MyType + name: "test" + description: "mydescription" + myproperty: 10 + a_bad_property: 20').validate + end + end + + it do + is_expected.to raise_error(StandardError, /Extraneous .*a_bad_property/) + end + end + + context 'retrieves simple type name' do + subject do + Google::YamlValidator.parse('--- !ruby/object:Level1::Level2::MyType + name: "MyTypeName" + description: "mydescription" + myproperty: 10') + end + + it { is_expected.to be_instance_of Level1::Level2::MyType } + end +end + +describe Api::Type::Array do + context 'requires underlying type' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Api::Type::Array + description: some description + name: "test"').validate + end + end + + it { is_expected.to raise_error(StandardError, /Missing 'item_type'/) } + end + + context 'requires underlying type to exist' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Api::Type::Array + name: myname + description: some description + item_type: Level1::Level2::MyType').validate + end + end + + it { is_expected.not_to raise_error } + end + + context 'requires underlying type to exist' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Api::Type::Array + name: myname + description: some description + item_type: ATypeThatDoesNotExist').validate + end + end + it do + is_expected.to \ + raise_error(StandardError, + /uninitialized constant .*ATypeThatDoesNotExist/) + end + end + + context 'requires name' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Api::Type::Array + item_type: Api::String').validate + end + end + it { is_expected.to raise_error(StandardError, /Missing 'name'/) } + end + + context 'requires description' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Api::Type::Array + item_type: Api::String + name: "test"').validate + end + end + it { is_expected.to raise_error(StandardError, /Missing 'description'/) } + end +end + +describe Api::Type::Enum do + context 'requires values' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Api::Type::Enum + description: some description + name: "test"').validate + end + end + it { is_expected.to raise_error(StandardError, /Missing 'values'/) } + end + + context 'values is an array' do + subject do + Google::YamlValidator.parse('--- !ruby/object:Api::Type::Enum + name: "name" + description: "description" + values: + - :A + - "b" + - 3') + end + it { is_expected.to have_attributes(values: [:A, 'b', 3]) } + end + + context 'requires name' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Api::Type::Enum + values: + - :A').validate + end + end + + it { is_expected.to raise_error(StandardError, /Missing 'name'/) } + end + + context 'requires description' do + subject do + lambda do + Google::YamlValidator.parse('--- !ruby/object:Api::Type::Enum + name: "test" + values: + - :A').validate + end + end + + it { is_expected.to raise_error(StandardError, /Missing 'description'/) } + end +end + +describe Api::Type::ResourceRef do + context 'requires valid resource' do + let(:spec_location) do + File.join(File.dirname(__FILE__), 'data', + 'resourceref-missingresource.yaml') + end + let(:spec) do + File.open(spec_location, 'r') + end + + after(:each) { spec.close } + subject do + lambda do + Google::YamlValidator.parse(spec.read).validate + end + end + + it { is_expected.to raise_error(StandardError, /Missing 'resource'/) } + end + + context 'requires valid imports' do + let(:spec_location) do + File.join(File.dirname(__FILE__), 'data', + 'resourceref-missingimports.yaml') + end + let(:spec) do + File.open(spec_location, 'r') + end + let(:error) do + /'missing' does not exist on 'ReferencedResource'/ + end + + after(:each) { spec.close } + subject do + lambda do + Google::YamlValidator.parse(spec.read).validate + end + end + + it { is_expected.to raise_error(StandardError, error) } + end + + context 'returns referenced type' do + let(:product) { Api::Compiler.new('spec/data/good-file.yaml').run } + + before { product.validate } + + subject do + product.objects.collect(&:parameters) + .flatten + .select { |p| p.class <= Api::Type::ResourceRef } + end + + it { is_expected.to have_attributes(length: 1) } + it 'matches reference type' do + is_expected.to satisfy do |value| + value[0].out_type == 'myproduct_referenced_resource' + end + end + end +end diff --git a/spec/yaml_validator_spec.rb b/spec/yaml_validator_spec.rb new file mode 100644 index 000000000000..3548127c9b7c --- /dev/null +++ b/spec/yaml_validator_spec.rb @@ -0,0 +1,82 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'spec_helper' + +class MyTestObject < Google::YamlValidator + attr_reader :some_property +end + +describe Google::YamlValidator do + context 'prevents extraneous variables' do + subject do + lambda do + object('some_property: "good"', + 'other_variable: 12345').validate + end + end + + it do + is_expected.to raise_error(StandardError, + /Extraneous variable 'other_variable'/) + end + end + + context 'sets variable properties' do + subject { object('name: "bar"', 'description: "good"') } + + it 'pre-flight test' do + is_expected.not_to respond_to(:@__custom_property) + end + + context 'set variable' do + let(:custom_obj) { mock('custom') } + + before do + subject.set_variable(custom_obj, :__custom_property) + end + + it do + expect(subject.instance_variable_get(:@__custom_property)) + .to be custom_obj + end + end + end + + context 'protects against property conflicts' do + subject do + -> { object('name: "bar"', 'description: "baz"').set_variable({}, :name) } + end + + it do + is_expected.to raise_error(StandardError, /Conflict of property 'name'/) + end + end + + context 'do not allow unapproved classes deserialized' do + subject do + -> { described_class.parse("--- !ruby/object:Digest::SHA256\na: b") } + end + + it do + is_expected.to raise_error(Psych::DisallowedClass, /Digest::SHA256/) + end + end + + private + + def object(*data) + described_class.parse(['--- !ruby/object:MyTestObject'].concat(data) + .join("\n")) + end +end diff --git a/templates/CHANGELOG.md.erb b/templates/CHANGELOG.md.erb new file mode 100644 index 000000000000..3e7673699c8e --- /dev/null +++ b/templates/CHANGELOG.md.erb @@ -0,0 +1,18 @@ +# Changelog +<% changes.sort_by(&:date).reverse.each do |change| -%> + +## <%= change.version -%> (<%= change.date.strftime('%Y-%m-%d') -%>) +<%= lines_before(lines(change.general.strip)) unless change.general.nil? -%> +<% unless change.features.nil? -%> + +### New features + +<%= lines(change.features.map { |f| "- #{wrap_field(f, 2).strip}" }) -%> +<% end # change.features.nil? -%> +<% unless change.fixes.nil? -%> + +### Fixes + +<%= lines(change.fixes.map { |f| "- #{wrap_field(f, 2).strip}" }) -%> +<% end # change.fixes.nil? -%> +<% end # changes....each -%> diff --git a/templates/CONTRIBUTING.md.erb b/templates/CONTRIBUTING.md.erb new file mode 100644 index 000000000000..6413f25e08c0 --- /dev/null +++ b/templates/CONTRIBUTING.md.erb @@ -0,0 +1,111 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +# How to become a contributor and submit your own code + +## Contributor License Agreements + +We'd love to accept your sample apps and patches! Before we can take them, we +have to jump a couple of legal hurdles. + +Please fill out either the individual or corporate Contributor License +Agreement (CLA). + + * If you are an individual writing original source code and you're sure you + own the intellectual property, then you'll need to sign an [individual CLA] + (https://developers.google.com/open-source/cla/individual). + * If you work for a company that wants to allow you to contribute your work, + then you'll need to sign a [corporate CLA] + (https://developers.google.com/open-source/cla/corporate). + +Follow either of the two links above to access the appropriate CLA and +instructions for how to sign and return it. Once we receive it, we'll +be able to accept your pull requests. + +## Contributing A Patch + +1. Submit an issue describing your proposed change to the repo in question. +1. The repo owner will respond to your issue promptly. +1. If your proposed change is accepted, and you haven't already done so, sign a + Contributor License Agreement (see details above). +1. Fork the desired repo, develop and test your code changes. +1. Ensure that your code adheres to the existing style in the sample to which + you are contributing. +1. Ensure that your code has an appropriate set of unit tests which all pass. +1. Submit a pull request. + +## Style + +We adhere as much as possible to the [ruby-style-guide][] and make the code +[rubocop][] compliant. Tests are setup to fail if there are style guide +violations. + +## Testing + +Please make sure all tests pass before sending a patch. This will help us to +approve your change faster. + +As a matter of policy the master branch is always passing all tests, and changes +that break tests cannot be accepted. If that's your case reach out and we can +help you get it fixed. + +### Running Tests + +<%= compile 'templates/puppet/run-tests-steps.erb' -%> + +## Auto generated files + +Various of the files in this repository are automatically generated by +<%= compiler -%>. Such files contain a prominent comment warning for its +auto generated origins. However some types, such as JSON or MD, do not allow +embedding comments without breaking the file or causing side effects. + +### Changing auto generated files + +Of course these files are not perfect there will inevitably be issues with them. +If you find an issue with them there are 2 options: + +1. Send a patch to the affected files to us and we'll update the source used to + generate the file, thus addressing the issue. Note that in this option your + patch will not be accepted but will be used as a guide to fix the original + file. + +2. Change the file directly in the sources used by <%= compiler -%>. By taking + this approach your change will be attributed to you, as you'd be the author + of the change. If you'd like to take credit for the change this is the + recommended approach. This approach has the nice side effect to fix all other + projects that have the same issue at once. + +### File list +<% unless generated_files.empty? -%> + +The list below contains all the files that were automatically generated by +<%= compiler -%>: + +<% generated_files.clone.uniq.map(&:to_s).sort_by(&:downcase).each do |file| -%> + * <%= file %> +<% end # generated_files....map -%> +<% end # generated_files.empty? -%> +<% unless sourced_files.empty? -%> + +The list below contains all the files that were automatically sourced from a +central location: + +<% sourced_files.clone.uniq.map(&:to_s).sort_by(&:downcase).each do |file| -%> + * <%= file %> +<% end # sourced_files....map -%> +<% end # sourced_files.empty? -%> + +[ruby-style-guide]: https://github.com/bbatsov/ruby-style-guide +[rubocop]: https://rubocop.readthedocs.io/en/latest/ diff --git a/templates/LICENSE b/templates/LICENSE new file mode 100644 index 000000000000..ef51da2b0e8d --- /dev/null +++ b/templates/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/templates/ansible/resource.erb b/templates/ansible/resource.erb new file mode 100644 index 000000000000..2127f480307b --- /dev/null +++ b/templates/ansible/resource.erb @@ -0,0 +1,140 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2017 Google +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +<%= compile 'templates/autogen_notice.erb' -%> + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +################################################################################ +# Documentation +################################################################################ + +<% + metadata_version = quote_string(@config.manifest.metadata_version) + supported_by = quote_string(@config.manifest.metadata_version) +-%> +ANSIBLE_METADATA = {'metadata_version': <%= metadata_version -%>, + 'status': <%= @config.manifest.status -%>, + 'supported_by': <%= supported_by -%>} + +DOCUMENTATION=''' +--- +module: <%= object.out_name %> +description: | + <%= lines(object.description) -%> +version_added: <%= lines(@config.manifest.version_added) -%> +author: <%= lines(@config.manifest.author) -%> +requirements: +<% @config.manifest.requirements.each do |line| -%> +<%= lines(indent(bullet_lines(line, 4), 4)) -%> +<% end -%> +options: + state: + description: + - Whether the given object should exist in GCP + required: false + choices: ['present', 'absent'] + default: 'present' +<% object.all_user_properties.reject(&:output).each do |prop| -%> + <%= Google::StringUtils.underscore(prop.name) -%>: + description: +<%= lines(indent(bullet_lines(prop.description, 12), 12)) -%> + required: <%= prop.required ? 'true' : 'false' %> +<% end -%> +''' + +RETURNS=''' +<% object.all_user_properties.select(&:output).each do |prop| -%> + <%= Google::StringUtils.underscore(prop.name) -%>: + description: +<%= lines(indent(bullet_lines(prop.description, 12), 12)) -%> + returned: success + type: <%= lines(prop.type.downcase) -%> +<% end -%> +''' + +################################################################################ +## Imports +################################################################################ + +from ansible.module_utils.gcp_utils import navigate_hash, GcpAuthentication +from ansible.module_utils.basic import AnsibleModule +import json + +################################################################################ +# Main +################################################################################ + + +def main(): + """Main function""" + + module = AnsibleModule( + argument_spec=dict( + state=dict(default='present', choices=['present', 'absent'], type='str'), +<% object.all_user_properties.reject(&:output).each do |prop| -%> +<% + line = "#{Google::StringUtils.underscore(prop.out_name)}=dict(" + options = [ + ("required=True" if prop.required), + "type=#{quote_string(python_type(prop))}" + ].compact +-%> +<%= lines(indent("#{line}#{options.join(', ')})", 12)) -%> +<% end -%> + project=dict(required=True, type='str'), + auth_kind=dict( + required=True, + choices=['machineaccount', 'serviceaccount', 'application'], + type='str'), + service_account_email=dict(type='str'), + service_account_file=dict(type='path'), + scopes=dict(required=True, type='list') + ), + mutually_exclusive=[['service_account_email', 'service_account_file']], + ) + + state = module.params['state'] + kind = <%= lines(quote_string(object.kind)) -%> + + fetch = fetch_resource(module, self_link(module), kind) + changed = False + + if fetch: + if state == 'present': + if is_different(module, fetch): + fetch = update(module, self_link(module), kind) + else: + delete(module, self_link(module), kind) + fetch = {} + changed = True + else: + if state == 'present': + fetch = create(module, collection(module), kind) + changed = True + + if fetch: + fetch.update({'changed': changed}) + else: + fetch = {'changed': changed} + + module.exit_json(**fetch) + + +def create(module, link, kind): + auth = GcpAuthentication(module) + return return_if_object(module, auth.session().post(link, json=resource_to_request(module)), kind) + + +def update(module, link, kind): + module.fail_json(msg="<%= object.name -%> cannot be edited") + + +def delete(module, link, kind): + auth = GcpAuthentication(module) + return return_if_object(module, auth.session().delete(link), kind) + + diff --git a/templates/async.erb b/templates/async.erb new file mode 100644 index 000000000000..25fbf1307cbb --- /dev/null +++ b/templates/async.erb @@ -0,0 +1,119 @@ +def expand_variables(template, var_data, extra_data = {}) + self.class.expand_variables(template, var_data, extra_data) +end + +<% if object.kind? -%> +def fetch_resource(resource, self_link, kind) + self.class.fetch_resource(resource, self_link, kind) +end +<% else # object.kind? -%> +def fetch_resource(resource, self_link) + self.class.fetch_resource(resource, self_link) +end +<% end # object.kind? -%> + +<%= lines(emit_link('async_op_url', async_operation_url(object), false, + true)) -%> + +def wait_for_operation(response, resource) +<% if object.kind? -%> +<% op_kind = object.async.operation.kind -%> + op_result = return_if_object(response, '<%= op_kind -%>') +<% else # object.kind? -%> + op_result = return_if_object(response) +<% end # object.kind? -%> + return if op_result.nil? +<% stat_path = Google::HashUtils.path2navigate(object.async.status.path) -%> + status = ::Google::HashUtils.navigate(op_result, <%= stat_path -%>) +<% res_path = Google::HashUtils.path2navigate(object.async.result.path) -%> +<% if object.self_link_query.nil? -%> +<%= + lines(format( + [ + [ + 'fetch_resource(', + indent([ + 'resource,', + 'URI.parse(::Google::HashUtils.navigate(wait_for_completion(status,', + indent(['op_result,', + 'resource),'], 59), # 59 = align with last ( previous line + # 39 = align with ( previous line + indent("#{res_path}))#{',' if object.kind?}", 39), + ("'#{object.kind}'" if object.kind?) + ].compact, 2), + ')' + ], + [ + 'wait_done = wait_for_completion(status, op_result, resource)', + 'fetch_resource(', + indent([ + 'resource,', + 'URI.parse(::Google::HashUtils.navigate(wait_done,', + # 39 = align with ( previous line + indent("#{res_path}))#{',' if object.kind?}", 39), + ("'#{object.kind}'" if object.kind?) + ].compact, 2), + ')' + ] + ], 2, inside_indent + )) +-%> +<% else # object.self_link_query.nil? -%> + wait_for_completion status, op_result, resource +<% obj_kind = object.kind? ? "'#{object.kind}'," : '' -%> +<%= + lines(format( + [ + [ + "fetch_wrapped_resource(resource, #{obj_kind}", + ("'#{object.self_link_query.kind}'," if object.self_link_query.kind?), + "'#{object.self_link_query.items}')" + ].join(' '), + [ + [ + "fetch_wrapped_resource(resource, #{obj_kind}", + ("'#{object.self_link_query.kind}'," if object.self_link_query.kind?) + ].join(' '), + indent([ + "'#{object.self_link_query.items}')" + ], 23) # 23 = align with ( previous line + ], + [ + "fetch_wrapped_resource(resource, #{obj_kind}", + indent([ + "'#{object.self_link_query.kind}',", + "'#{object.self_link_query.items}')" + ], 23) # 31 = align with ( previous line + ] + ], 2, inside_indent + )) +-%> +<% end # object.self_link_query.nil? -%> +end + +def wait_for_completion(status, op_result, resource) +<% op_path = Google::HashUtils.path2navigate(object.async.operation.path) -%> + op_id = ::Google::HashUtils.navigate(op_result, <%= op_path -%>) + op_uri = async_op_url(resource, op_id: op_id) + while status != '<%= object.async.status.complete -%>' + debug("Waiting for completion of operation #{op_id}") +<% err_path = Google::HashUtils.path2navigate(object.async.error.path) -%> +<% err_msg = object.async.error.message -%> + raise_if_errors op_result, <%= err_path -%>, '<%= err_msg -%>' + sleep <%= sprintf('%.1f', object.async.operation.wait_ms / 1000.0) %> +<% allowed_states = object.async.status.allowed -%> + raise "Invalid result '#{status}' on <%= object.out_name -%>." \ + unless %w[<%= allowed_states.join(' ') -%>].include?(status) +<% if object.kind? -%> + op_result = fetch_resource(resource, op_uri, '<%= op_kind -%>') +<% else # object.kind? -%> + op_result = fetch_resource(resource, op_uri) +<% end # object.kind? -%> + status = ::Google::HashUtils.navigate(op_result, <%= stat_path -%>) + end + op_result +end + +def raise_if_errors(response, err_path, msg_field) + self.class.raise_if_errors(response, err_path, msg_field) +end diff --git a/templates/async.yaml.erb b/templates/async.yaml.erb new file mode 100644 index 000000000000..7e6971400ec3 --- /dev/null +++ b/templates/async.yaml.erb @@ -0,0 +1,18 @@ +async: !ruby/object:Api::Async + operation: !ruby/object:Api::Async::Operation + kind: 'sql#operation' + path: 'name' + base_url: 'projects/{{project}}/operations/{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::Async::Result + path: 'targetLink' + status: !ruby/object:Api::Async::Status + path: 'status' + complete: 'DONE' + allowed: + - 'PENDING' + - 'RUNNING' + - 'DONE' + error: !ruby/object:Api::Async::Error + path: 'error/errors' + message: 'message' diff --git a/templates/autogen_notice.erb b/templates/autogen_notice.erb new file mode 100644 index 000000000000..2604cfbef394 --- /dev/null +++ b/templates/autogen_notice.erb @@ -0,0 +1,13 @@ +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by <%= compiler -%> and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in README.md and +# CONTRIBUTING.md located at the root of this package. +# +# ---------------------------------------------------------------------------- diff --git a/templates/bundle.rb.erb b/templates/bundle.rb.erb new file mode 100644 index 000000000000..9f9f7cbdc86a --- /dev/null +++ b/templates/bundle.rb.erb @@ -0,0 +1,33 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'open3' + +class Bundle + def self.run(args) + Open3.popen2e(*%w[bundle exec].concat(args)) do |_, stdout_err, wait_thr| + return -2 if wait_thr.nil? + + exit_code = wait_thr.value.to_i + puts stdout_err.readlines unless exit_code.zero? + + return exit_code + end + -1 + end +end diff --git a/templates/chef/Berksfile.erb b/templates/chef/Berksfile.erb new file mode 100644 index 000000000000..0d200db22463 --- /dev/null +++ b/templates/chef/Berksfile.erb @@ -0,0 +1,21 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +source 'https://supermarket.chef.io' + +metadata diff --git a/templates/chef/Gemfile.erb b/templates/chef/Gemfile.erb new file mode 100644 index 000000000000..a4321dd21f96 --- /dev/null +++ b/templates/chef/Gemfile.erb @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +source 'https://rubygems.org' + +group :test do +<% + # TODO(alexstephen + Chef): Investigate how to remove the two google gems + # from all cookbooks except google-gauth +-%> + gem 'chef' + gem 'chefspec', '~> 6.2.0' + gem 'foodcritic' + gem 'google-api-client', '= 0.10.1' + gem 'googleauth' + gem 'rake', '~> 10.0' + gem 'rspec' + gem 'rspec-mocks' + # TODO(alexstephen): Monitor rubocop upsteam changes + # https://github.com/bbatsov/rubocop/pull/4329 + # Change will allow rubocop to use --ignore-parent-exclusion flag + # Current rubocop upstream will not check Chef files because of + # AllCops/Exclude + gem 'rubocop', git: 'https://github.com/nelsonjr/rubocop.git', + branch: 'feature/ignore-parent-exclude' + gem 'simplecov' +end diff --git a/templates/chef/Gemfile.lock b/templates/chef/Gemfile.lock new file mode 100644 index 000000000000..a6e0679d3e6a --- /dev/null +++ b/templates/chef/Gemfile.lock @@ -0,0 +1,249 @@ +GIT + remote: https://github.com/nelsonjr/rubocop.git + revision: 178b3150b7610195c327783575177520316e1985 + branch: feature/ignore-parent-exclude + specs: + rubocop (0.50.0) + parallel (~> 1.10) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 2.2.2, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + +GEM + remote: https://rubygems.org/ + specs: + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) + ast (2.3.0) + backports (3.8.0) + builder (3.2.3) + chef (13.0.118) + addressable + bundler (>= 1.10) + chef-config (= 13.0.118) + chef-zero (>= 13.0) + diff-lcs (~> 1.2, >= 1.2.4) + erubis (~> 2.7) + ffi-yajl (~> 2.2) + highline (~> 1.6, >= 1.6.9) + iniparse (~> 1.4) + iso8601 (~> 0.9.1) + mixlib-archive (~> 0.4) + mixlib-authentication (~> 1.4) + mixlib-cli (~> 1.7) + mixlib-log (~> 1.3) + mixlib-shellout (~> 2.0) + net-sftp (~> 2.1, >= 2.1.2) + net-ssh (>= 2.9, < 5.0) + net-ssh-multi (~> 1.2, >= 1.2.1) + ohai (~> 13.0) + plist (~> 3.2) + proxifier (~> 1.0) + rspec-core (~> 3.5) + rspec-expectations (~> 3.5) + rspec-mocks (~> 3.5) + rspec_junit_formatter (~> 0.2.0) + serverspec (~> 2.7) + specinfra (~> 2.10) + syslog-logger (~> 1.6) + uuidtools (~> 2.1.5) + chef-config (13.0.118) + addressable + fuzzyurl + mixlib-config (~> 2.0) + mixlib-shellout (~> 2.0) + chef-zero (13.0.0) + ffi-yajl (~> 2.2) + hashie (>= 2.0, < 4.0) + mixlib-log (~> 1.3) + rack (~> 2.0) + uuidtools (~> 2.1) + chefspec (6.2.0) + chef (>= 12.0) + fauxhai (>= 3.6, < 5) + rspec (~> 3.0) + cucumber-core (2.0.0) + backports (~> 3.6) + gherkin (~> 4.0) + declarative (0.0.10) + declarative-option (0.1.0) + diff-lcs (1.3) + docile (1.1.5) + erubis (2.7.0) + faraday (0.13.1) + multipart-post (>= 1.2, < 3) + fauxhai (4.1.0) + net-ssh + ffi (1.9.18) + ffi-yajl (2.3.0) + libyajl2 (~> 1.2) + foodcritic (11.1.0) + cucumber-core (>= 1.3) + erubis + nokogiri (>= 1.8.1, < 2.0) + rake + rufus-lru (~> 1.0) + treetop (~> 1.4) + yajl-ruby (~> 1.3.1) + fuzzyurl (0.9.0) + gherkin (4.1.3) + google-api-client (0.10.1) + addressable (~> 2.3) + googleauth (~> 0.5) + httpclient (~> 2.7) + hurley (~> 0.1) + memoist (~> 0.11) + mime-types (>= 1.6) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + googleauth (0.5.3) + faraday (~> 0.12) + jwt (~> 1.4) + logging (~> 2.0) + memoist (~> 0.12) + multi_json (~> 1.11) + os (~> 0.9) + signet (~> 0.7) + hashie (3.5.5) + highline (1.7.8) + httpclient (2.8.3) + hurley (0.2) + iniparse (1.4.2) + ipaddress (0.8.3) + iso8601 (0.9.1) + json (2.1.0) + jwt (1.5.6) + libyajl2 (1.2.0) + little-plugger (1.1.4) + logging (2.2.2) + little-plugger (~> 1.1) + multi_json (~> 1.10) + memoist (0.16.0) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mini_portile2 (2.1.0) + mixlib-archive (0.4.1) + mixlib-log + mixlib-authentication (1.4.1) + mixlib-log + mixlib-cli (1.7.0) + mixlib-config (2.2.4) + mixlib-log (1.7.1) + mixlib-shellout (2.2.7) + multi_json (1.12.1) + multipart-post (2.0.0) + net-scp (1.2.1) + net-ssh (>= 2.6.5) + net-sftp (2.1.2) + net-ssh (>= 2.6.5) + net-ssh (4.1.0) + net-ssh-gateway (2.0.0) + net-ssh (>= 4.0.0) + net-ssh-multi (1.2.1) + net-ssh (>= 2.6.5) + net-ssh-gateway (>= 1.2.0) + net-telnet (0.1.1) + nokogiri (1.8.1) + mini_portile2 (~> 2.1.0) + ohai (13.0.1) + chef-config (>= 12.5.0.alpha.1, < 14) + ffi (~> 1.9) + ffi-yajl (~> 2.2) + ipaddress + mixlib-cli + mixlib-config (~> 2.0) + mixlib-log (>= 1.7.1, < 2.0) + mixlib-shellout (~> 2.0) + plist (~> 3.1) + systemu (~> 2.6.4) + wmi-lite (~> 1.0) + os (0.9.6) + parallel (1.12.0) + parser (2.4.0.0) + ast (~> 2.2) + plist (3.3.0) + polyglot (0.3.5) + powerpack (0.1.1) + proxifier (1.0.3) + public_suffix (2.0.5) + rack (2.0.1) + rainbow (2.2.2) + rake + rake (10.5.0) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.1) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-its (1.2.0) + rspec-core (>= 3.0.0) + rspec-expectations (>= 3.0.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + rspec_junit_formatter (0.2.3) + builder (< 4) + rspec-core (>= 2, < 4, != 2.12.0) + ruby-progressbar (1.9.0) + rufus-lru (1.1.0) + serverspec (2.38.0) + multi_json + rspec (~> 3.0) + rspec-its + specinfra (~> 2.53) + sfl (2.3) + signet (0.7.3) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (~> 1.5) + multi_json (~> 1.10) + simplecov (0.14.1) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + specinfra (2.67.9) + net-scp + net-ssh (>= 2.7, < 5.0) + net-telnet + sfl + syslog-logger (1.6.8) + systemu (2.6.5) + treetop (1.6.8) + polyglot (~> 0.3) + uber (0.1.0) + unicode-display_width (1.3.0) + uuidtools (2.1.5) + wmi-lite (1.0.0) + yajl-ruby (1.3.1) + +PLATFORMS + ruby + +DEPENDENCIES + chef + chefspec (~> 6.2.0) + foodcritic + google-api-client (= 0.10.1) + googleauth + rake (~> 10.0) + rspec + rspec-mocks + rubocop! + simplecov + +BUNDLED WITH + 1.15.3 diff --git a/templates/chef/README.md.erb b/templates/chef/README.md.erb new file mode 100644 index 000000000000..f6457424410b --- /dev/null +++ b/templates/chef/README.md.erb @@ -0,0 +1,153 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% require 'provider/chef/outline' -%> +# <%= product.name %> Chef Cookbook + +<%= lines(manifest.description) -%> + +## Requirements + +### Platforms + +#### Supported Operating Systems + +This cookbook was tested on the following operating systems: + +<%= lines(manifest.operating_systems.map do |os| + ['* ', os.name, ' ', os.versions.join(', ')].join + end.join("\n")) -%> + +<%# TODO(alexstephen): Remove this example. -%> +## Example + +```ruby +<%= compile ["products/#{@api.prefix[1..-1]}/files/examples", 'cookbook', + 'readme.rb'].join('~') -%> +``` + +## Credentials + +All Google Cloud Platform cookbooks use an unified authentication mechanism, +provided by the `google-gauth` cookbook. Don't worry, it is automatically +installed when you install this module. + +### Example + +```ruby +<%= compile 'templates/chef/example~auth.rb.erb' %> +``` + +For complete details of the authentication cookbook, visit the +[google-gauth][] cookbook documentation. + +## Resources + +<% objects = product.objects.select { |o| !o.exclude } -%> +<% objects.each do |object| -%> +* [`<%= object.out_name -%>`](#<%= object.out_name -%>) - +<%= lines(indent(wrap_field(object.description, 6), 2)) -%> +<% end -%> + + +<% objects.each do |object| -%> +### <%= object.out_name -%> + +<%= object.description %> + +#### Example + +```ruby +<%= compile ["products/#{@api.prefix[1..-1]}/files/examples", 'cookbook', + "#{Google::StringUtils.underscore(object.name)}.rb"].join('~') %> +``` + +#### Reference + +```ruby +<% outline = Provider::ChefOutline.new(self) -%> +<%= lines(outline.generate(object)) -%> +``` + +#### Actions + +* `create` - +<%= lines(wrap_field("Converges the `#{object.out_name}` resource into the final +state described within the block. If the resource does not exist, Chef will +attempt to create it.", 0)) -%> +* `delete` - +<%= lines(wrap_field("Ensures the `#{object.out_name}` resource is not present. +If the resource already exists Chef will attempt to delete it.", 0)) -%> + +#### Properties + +<% # TODO(nelsonjr): Add suffixes "(read only)" "(output only") etc + # on all providers -%> +<% object.all_user_properties.each do |property| -%> +* `<%= property.out_name -%>` - +<% + description = [] + description << 'Required.' if property.required + description << 'Output only.' if property.output + description << property.description +-%> +<%= lines(wrap_field(description.join(' '), 2), 1) -%> +<% if property.is_a? Api::Type::NestedObject -%> +<%= build_nested_object(property, property.out_name).join -%> +<% elsif property.is_a? Api::Type::Array and \ + property.item_type.is_a? Api::Type::NestedObject -%> +<%= build_nested_object(property.item_type, "#{property.out_name}[]").join -%> +<% elsif property.is_a? Api::Type::Array and \ + property.item_type.is_a? Api::Type::Array + raise "Array of arrays. Not supported." +-%> +<% end # property.is_a? Api::Type::NestedObject -%> +<% end # object.all_user_properties.-%> +#### Label +Set the `<%= label_name(object) -%>` property when attempting to set primary key +of this object. The primary key will always be referred to by the initials of +the resource followed by "_label" + +<% end -%> +<% unless @config.functions.nil? -%> +## Functions + +### About Functions +In order to use these functions inside of a Chef recipe, you'll need to import +the function first. Before calling a function, add the following line: + +```ruby +::Chef::Resource.send(:include, Google::Functions) +``` + +<% @config.functions.each do |fn| -%> +### `<%= fn.name -%>` + +<%= lines(wrap_field(fn.description, 0)) -%> + +#### Arguments + +<% fn.arguments.each do |arg| -%> + - `<%= arg.name -%>`: +<%= lines(indent(wrap_field(arg.description, 6), 2), 1) -%> +<% end # fn.arguments.each -%> +<% unless fn.examples.nil? || fn.examples.empty? -%> +#### Examples + +<%= lines(fn.examples.map { |eg| lines(['```ruby', eg, '```']) }, 1) -%> +<% end # fn.examples.nil? || fn.examples.empty? -%> +<% end # @config.functions.each -%> + +<% end # config.functions.nil? -%> +[google-gauth]: https://supermarket.chef.io/cookbooks/google-gauth diff --git a/templates/chef/chefignore.erb b/templates/chef/chefignore.erb new file mode 100644 index 000000000000..537f2f4a8cd9 --- /dev/null +++ b/templates/chef/chefignore.erb @@ -0,0 +1,125 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +# Put files/directories that should be ignored in this file when uploading +# to a chef-server or supermarket. +# Lines that start with '# ' are comments. + +# OS generated files # +###################### +.DS_Store +Icon? +nohup.out +ehthumbs.db +Thumbs.db + +# SASS # +######## +.sass-cache + +# EDITORS # +########### +\#* +.#* +*~ +*.sw[a-z] +*.bak +REVISION +TAGS* +tmtags +*_flymake.* +*_flymake +*.tmproj +.project +.settings +mkmf.log + +## COMPILED ## +############## +a.out +*.o +*.pyc +*.so +*.com +*.class +*.dll +*.exe +*/rdoc/ + +# Testing # +########### +.watchr +.rspec +spec/* +spec/fixtures/* +test/* +features/* +examples/* +Guardfile +Procfile +.kitchen* +.rubocop.yml +spec/* +Rakefile +.travis.yml +.foodcritic +.codeclimate.yml + +# SCM # +####### +.git +*/.git +.gitignore +.gitmodules +.gitconfig +.gitattributes +.svn +*/.bzr/* +*/.hg/* +*/.svn/* + +# Berkshelf # +############# +Berksfile +Berksfile.lock +cookbooks/* +tmp + +# Policyfile # +############## +Policyfile.rb +Policyfile.lock.json + +# Cookbooks # +############# +CONTRIBUTING* +CHANGELOG* +TESTING* +MAINTAINERS.toml + +# Strainer # +############ +Colanderfile +Strainerfile +.colander +.strainer + +# Vagrant # +########### +.vagrant +Vagrantfile diff --git a/templates/chef/credential.erb b/templates/chef/credential.erb new file mode 100644 index 000000000000..9fd098de7a7c --- /dev/null +++ b/templates/chef/credential.erb @@ -0,0 +1,41 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'chef/resource' + +module Google + class CredentialResourceMock < Chef::Resource + resource_name :gauth_credential + + property :name, String, identity: true, desired_state: false + property :path, String, desired_state: false + property :scopes, Array, desired_state: false + + default_action :nothing + + action :nothing do + end + + action :serviceaccount do + end + + def authorization + Google::FakeAuthorization.new + end + end +end diff --git a/templates/chef/dot~rubocop~root.yml b/templates/chef/dot~rubocop~root.yml new file mode 100644 index 000000000000..035c3067b4b8 --- /dev/null +++ b/templates/chef/dot~rubocop~root.yml @@ -0,0 +1,39 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +# TODO(alexstephen): Monitor rubocop upsteam changes +# https://github.com/bbatsov/rubocop/pull/4329 +# Change will allow rubocop to use --ignore-parent-exclusion flag +# Current rubocop upstream will not check Chef files because of AllCops/Exclude +AllCops: + Exclude: + # Foodcritic and Rubocop give conflicting information. + - 'recipes/*' + +Metrics/AbcSize: + Max: 25 + +Metrics/BlockLength: + Enabled: false + +Metrics/MethodLength: + Max: 15 + +Naming/FileName: + Exclude: + - 'recipes/*.rb' diff --git a/templates/chef/example~auth.rb.erb b/templates/chef/example~auth.rb.erb new file mode 100644 index 000000000000..6f2b2a61ee89 --- /dev/null +++ b/templates/chef/example~auth.rb.erb @@ -0,0 +1,56 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +# An example Chef recipe that creates a Google Cloud Computing DNS Managed Zone +# in a project. + +# Defines a credential to be used when communicating with Google Cloud +# Platform. The title of this credential is then used as the 'credential' +# parameter in the gdns_project type. +# +# For more information on the gauth_credential parameters and providers please +# refer to its detailed documentation at: +# +# For the sake of this example we set the parameter 'path' to point to the file +# that contains your credential in JSON format. And for convenience this example +# allows a variable named $cred_path to be provided to it. If running from the +# command line you can pass it via the command line: +# +<% +filename = File.basename(out_file, File.extname(out_file)) +-%> +# CRED_PATH=/path/to/my/cred.json \ +# chef-client -z --runlist \ +# "recipe[<%= @api.prefix -%>::<%= filename -%>]" +# +# For convenience you optionally can add it to your ~/.bash_profile (or the +# respective .profile settings) environment: +# +# export CRED_PATH=/path/to/my/cred.json +# +# TODO(nelsonjr): Add link to documentation on Supermarket / Github +# ________________________ + +raise "Missing parameter 'CRED_PATH'. Please read docs at #{__FILE__}" \ + unless ENV.key?('CRED_PATH') + +<% end # name != README.md -%> +gauth_credential 'mycred' do + action :serviceaccount + path ENV['CRED_PATH'] # e.g. '/path/to/my_account.json' + scopes [ +<%= lines(indent_list(data[:scopes].map { |s| quote_string(s) }, 4)) -%> + ] +end diff --git a/templates/chef/foodcritic_spec.rb.erb b/templates/chef/foodcritic_spec.rb.erb new file mode 100644 index 000000000000..44169d61467d --- /dev/null +++ b/templates/chef/foodcritic_spec.rb.erb @@ -0,0 +1,43 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'spec_helper' + +context 'check examples with foodcritic' do + let(:recipes) do + Dir[File.join(File.dirname(__FILE__), '../', 'recipes', '*.rb')] + end + + let(:command) { ['foodcritic'].concat(recipes) } + + subject { Bundle.run(command) } + + it { is_expected.to be_zero } +end + +context 'ensure foodcritic recognizes bad recipes' do + let(:poor_recipe) do + File.join(File.dirname(__FILE__), 'data', 'poor_recipe.rb') + end + + let(:command) { ['foodcritic', poor_recipe] } + + subject { Bundle.run(command) } + + it { is_expected.not_to be_zero } +end diff --git a/templates/chef/function.erb b/templates/chef/function.erb new file mode 100644 index 000000000000..be10cd17e048 --- /dev/null +++ b/templates/chef/function.erb @@ -0,0 +1,51 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% unless fn.search_paths.nil? -%> +<%= + lines( + fn.search_paths.sort.map do |search| + name = "search_#{search.name}" + [ + ("\# #{search.comment}" unless search.comment.nil?), + "#{name} = File.expand_path('#{search.path}', __dir__)", + "$LOAD_PATH.unshift #{name} unless $LOAD_PATH.include?(#{name})" + ].compact + end, 1 + ) +-%> +<% end # unless fn.search_paths.nil? -%> +<%= + lines(fn.requires.sort.map { |r| "require '#{r}'" }, 1) \ + unless fn.requires.nil? +-%> +<%= lines(emit_function_doc(fn)) -%> +module Google + # Module that holds all client-side functions + module Functions +<% args = fn.arguments.map { |a| a.name }.join(', ') -%> + def self.<%= fn.name -%>(<%= args -%>) +<%= lines(indent(fn.code, 6)) -%> + end + + def <%= fn.name -%>(<%= args -%>) + ::Google::Functions.<%= fn.name -%>(<%= args -%>) + end +<%= lines(lines_before(indent(fn.helpers,4))) unless fn.helpers.nil? -%> + end +end diff --git a/templates/chef/google-gauth~metadata.rb.erb b/templates/chef/google-gauth~metadata.rb.erb new file mode 100644 index 000000000000..7f9ec34c8d0b --- /dev/null +++ b/templates/chef/google-gauth~metadata.rb.erb @@ -0,0 +1,25 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +name 'google-gauth' +maintainer 'The Authors' +maintainer_email 'you@example.com' +license 'all_rights' +description 'Installs/Configures gauth' +long_description 'Installs/Configures gauth' +version '0.1.0' diff --git a/templates/chef/init_library_path.rb.erb b/templates/chef/init_library_path.rb.erb new file mode 100644 index 000000000000..6da8107a10ef --- /dev/null +++ b/templates/chef/init_library_path.rb.erb @@ -0,0 +1,20 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +# Add libraries/ to library search path +$LOAD_PATH.unshift ::File.expand_path(::File.dirname(__FILE__)) diff --git a/templates/chef/metadata.rb.erb b/templates/chef/metadata.rb.erb new file mode 100644 index 000000000000..0741d6a69b43 --- /dev/null +++ b/templates/chef/metadata.rb.erb @@ -0,0 +1,45 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +name 'google-<%= product.prefix -%>' +maintainer 'Google' +maintainer_email 'nelsona@google.com' +license 'apachev2' +description '<%= manifest.summary -%>' +long_description ' +<%= indent(wrap_field(manifest.description, 4), 2) -%>' +version '<%= manifest.version -%>' +<% manifest.depends.each do |dep| -%> +depends '<%= dep.name -%>', '<%= dep.versions -%>' +<% end -%> +<%= lines(emit_rubocop(binding, :attribute, 'issues_url', :disabled)) -%> +issues_url '<%= manifest.issues -%>' \ + if respond_to?(:issues_url) +<%= lines(emit_rubocop(binding, :attribute, 'issues_url', :enabled)) -%> +source_url '<%= manifest.source -%>' \ + if respond_to?(:source_url) + +supports 'centos' +supports 'debian' +supports 'fedora' +supports 'freebsd' +supports 'opensuse' +supports 'redhat' +supports 'suse' +supports 'ubuntu' +supports 'windows' diff --git a/templates/chef/poor_recipe.rb b/templates/chef/poor_recipe.rb new file mode 100644 index 000000000000..920d2c388d22 --- /dev/null +++ b/templates/chef/poor_recipe.rb @@ -0,0 +1,17 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Will not run because of missing README. +test do + bad_thing 'BAD' +end diff --git a/templates/chef/property/array.rb.erb b/templates/chef/property/array.rb.erb new file mode 100644 index 000000000000..23e79d84b916 --- /dev/null +++ b/templates/chef/property/array.rb.erb @@ -0,0 +1,35 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +module Google + module <%= product_ns %> + module Property + # A class to handle serialization of Array items. + class Array +<%= emit_coerce(product_ns, 'Array', 8) -%> + def self.api_parse(value) + value + end + + def self.catalog_parse(value) + value + end + end + end + end +end diff --git a/templates/chef/property/array_typed.rb.erb b/templates/chef/property/array_typed.rb.erb new file mode 100644 index 000000000000..add874042133 --- /dev/null +++ b/templates/chef/property/array_typed.rb.erb @@ -0,0 +1,49 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= prop_ns_dir -%>/property/array' + +module Google + module <%= product_ns %> + module Property + # A class to handle serialization of Array items. + class <%= type -%>Array < Google::<%= product_ns -%>::Property::Array +<%= emit_coerce(product_ns, "#{type}Array", 8) -%> + def self.api_parse(value) + value + end + + def self.catalog_parse(value) + value + end + + def self.validate(value) + return if value.nil? || value.is_a?(::<%= type -%>) + unless value.is_a? ::Array +<% + message = + "Expected #{type.downcase} but found \#{value.class} instead: \#{value}" +-%> + raise "<%= message -%>" + end + value.each { |v| validate v } + end + end + end + end +end diff --git a/templates/chef/property/boolean.rb.erb b/templates/chef/property/boolean.rb.erb new file mode 100644 index 000000000000..5f754ee34167 --- /dev/null +++ b/templates/chef/property/boolean.rb.erb @@ -0,0 +1,35 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +module Google + module <%= product_ns %> + module Property + # A class to handle serialization of Boolean items. + class Boolean +<%= emit_coerce(product_ns, 'Boolean', 8) -%> + def self.api_parse(value) + value + end + + def self.catalog_parse(value) + value + end + end + end + end +end diff --git a/templates/chef/property/double.rb.erb b/templates/chef/property/double.rb.erb new file mode 100644 index 000000000000..47325e05ee08 --- /dev/null +++ b/templates/chef/property/double.rb.erb @@ -0,0 +1,35 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +module Google + module <%= product_ns %> + module Property + # A class to handle serialization of Integer items. + class Double +<%= emit_coerce(product_ns, 'Double', 8) -%> + def self.catalog_parse(value) + value.to_f + end + + def self.api_parse(value) + value.to_f + end + end + end + end +end diff --git a/templates/chef/property/enum.rb.erb b/templates/chef/property/enum.rb.erb new file mode 100644 index 000000000000..6cedc57dee2c --- /dev/null +++ b/templates/chef/property/enum.rb.erb @@ -0,0 +1,35 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +module Google + module <%= product_ns %> + module Property + # A class to handle serialization of Enumerated items. + class Enum +<%= emit_coerce(product_ns, 'Enum', 8) -%> + def self.api_parse(value) + value + end + + def self.catalog_parse(value) + value + end + end + end + end +end diff --git a/templates/chef/property/integer.rb.erb b/templates/chef/property/integer.rb.erb new file mode 100644 index 000000000000..03cd7f9963e3 --- /dev/null +++ b/templates/chef/property/integer.rb.erb @@ -0,0 +1,35 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +module Google + module <%= product_ns %> + module Property + # A class to handle serialization of Integer items. + class Integer +<%= emit_coerce(product_ns, 'Integer', 8) -%> + def self.api_parse(value) + value.to_i + end + + def self.catalog_parse(value) + value.to_i + end + end + end + end +end diff --git a/templates/chef/property/namevalues.rb.erb b/templates/chef/property/namevalues.rb.erb new file mode 100644 index 000000000000..3b5327efda86 --- /dev/null +++ b/templates/chef/property/namevalues.rb.erb @@ -0,0 +1,35 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +module Google + module <%= product_ns %> + module Property + # A class to handle serialization of NameValues items. + class NameValues +<%= emit_coerce(product_ns, 'NameValues', 8) -%> + def self.api_parse(value) + value + end + + def self.catalog_parse(value) + value + end + end + end + end +end diff --git a/templates/chef/property/nested_object.rb.erb b/templates/chef/property/nested_object.rb.erb new file mode 100644 index 000000000000..e6d71c866cc6 --- /dev/null +++ b/templates/chef/property/nested_object.rb.erb @@ -0,0 +1,269 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% + def emit_parse_assignment(prop, method, source, supports_symbols = false) + parser = [prop.property_type, method].join('.') + if supports_symbols + format( + [ + ["@#{prop.out_name} = #{parser}(args[:#{source}])"], + [ + "@#{prop.out_name} =", + indent("#{parser}(args[:#{source}])", 2) + ], + [ + "@#{prop.out_name} = #{parser}(", + indent("args[:#{source}]", 2), + ')' + ], + [ + "@#{prop.out_name} =", + indent([ + "#{parser}(", + indent("args[:#{source}]", 2), + ')' + ], 2) + ] + ], 0, 10 + ) + else + format( + [ + ["@#{prop.out_name} = #{parser}(args['#{source}'])"], + [ + "@#{prop.out_name} =", + indent("#{parser}(args['#{source}'])", 2) + ], + [ + "@#{prop.out_name} = #{parser}(", + indent("args['#{source}']", 2), + ')' + ], + [ + "@#{prop.out_name} =", + indent([ + "#{parser}(", + indent("args['#{source}']", 2), + ')' + ], 2) + ] + ], 0, 10 + ) + end + end +-%> +<%= compile('templates/license.erb') -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% if emit_array -%> +require 'google/<%= prop_ns_dir -%>/property/array' +<% end -%> +module Google + module <%= product_ns %> + module Data +<%= lines(indent( + emit_rubocop(binding, :class, + ['Google', product_ns, 'Data', class_name].join('::'), + :disabled), + 6)) -%> +<%= + lines(format([ + ["# A class to manage data for #{field} for #{obj_name}."], + [ + "# A class to manage data for #{field} for", + "# #{obj_name}." + ] + ], 6)) +-%> + class <%= class_name %> + include Comparable + +<% if !nested_properties.empty? -%> +<% nested_properties.each do |prop| -%> + attr_reader :<%= prop.out_name %> +<% end # nested_properties.each -%> + +<% end # if !nested_properties.empty? -%> +<% + prop_to_json = [] + prop_to_s = [] + nested_properties.each do |prop| + prop_to_json << "'#{prop.field_name}' => #{prop.out_name}" + prop_to_s << + if prop.is_a?(Api::Type::Array) + if prop.item_type.is_a?(Api::Type::NestedObject) + [ + "#{prop.out_name}: ['[',", + indent([ + "#{prop.out_name}.map(&:to_json).join(', '),", + "']'].join(' ')" + ], prop.out_name.length + 3), # 3 = ": [" + ] + elsif prop.item_type.is_a?(::String) + "#{prop.out_name}: #{prop.out_name}.to_s" + else + raise "Unknown array type" + end + else + "#{prop.out_name}: #{prop.out_name}.to_s" + end + end + + json_code = [] + json_code << '{' + json_code << indent_list(prop_to_json, 2) + json_code << '}.reject { |_k, v| v.nil? }.to_json' + + to_s_code = [] + to_s_code << '{' + to_s_code << indent_list(prop_to_s, 2) + to_s_code << '}.map { |k, v| "#{k}: #{v}" }.join(\', \')' +-%> +<%= lines(indent(emit_method('to_json', ['_arg = nil'], json_code, + file_relative), 8), + 1) -%> +<%= lines(indent(emit_method('to_s', %w[], to_s_code, file_relative), 8), 1) -%> + def ==(other) + return false unless other.is_a? <%= class_name %> + compare_fields(other).each do |compare| + next if compare[:self].nil? || compare[:other].nil? + return false if compare[:self] != compare[:other] + end + true + end + + def <=>(other) + return false unless other.is_a? <%= class_name %> + compare_fields(other).each do |compare| + next if compare[:self].nil? || compare[:other].nil? + result = compare[:self] <=> compare[:other] + return result unless result.zero? + end + 0 + end + + def inspect + to_json + end + + private + +<% + compare_code = [] + compare_code << '[' + compare_code << indent_list(nested_properties.map do |prop| + format( + [ + ["{ self: #{prop.out_name}", + "other: other.#{prop.out_name} }"] + .join(', '), + ['{', + indent_list([ + "self: #{prop.out_name}", + "other: other.#{prop.out_name}" + ], 2), + '}' + ] + ], 0, 12 + ) + end, 2) + compare_code << ']' +-%> +<%= lines(indent(emit_method('compare_fields', %w[other], compare_code, + file_relative), 8)) -%> + end + + # Manages a <%= class_name -%> nested object + # Data is coming from the GCP API + class <%= class_name -%>Api < <%= class_name %> +<% + init_code = [] + init_code.concat(nested_properties.map do |prop| + emit_parse_assignment(prop, 'api_parse', prop.field_name) + end) +-%> +<%= lines(indent(emit_method('initialize', %w[args], init_code, file_relative, + class_name: "#{class_name}Api"), + 8)) -%> + end + + # Manages a <%= class_name -%> nested object + # Data is coming from the Chef catalog + class <%= class_name -%>Catalog < <%= class_name %> +<% + init_code = [] + init_code.concat(nested_properties.map do |prop| + emit_parse_assignment(prop, 'catalog_parse', prop.out_name, + true) + end) +-%> +<%= lines(indent(emit_method('initialize', %w[args], init_code, file_relative, + class_name: "#{class_name}Catalog"), + 8)) -%> + end + end + + module Property +<%= + lines(format([ + ["# A class to manage input to #{field} for #{obj_name}."], + [ + "# A class to manage input to #{field} for", + "# #{obj_name}." + ] + ], 6)) +-%> + class <%= class_name %> +<%= emit_coerce(product_ns, "#{class_name}", 8) -%> + # Used for parsing Chef catalog + def self.catalog_parse(value) + return if value.nil? + return value if value.is_a? Data::<%= class_name %> + Data::<%= class_name %>Catalog.new(value) + end + + # Used for parsing GCP API responses + def self.api_parse(value) + return if value.nil? + return value if value.is_a? Data::<%= class_name %> + Data::<%= class_name %>Api.new(value) + end + end +<% if emit_array -%> + + # A Chef property that holds an integer + class <%= class_name %>Array < Google::<%= product_ns -%>::Property::Array +<%= emit_coerce(product_ns, "#{class_name}Array", 8) -%> + # Used for parsing Chef catalog + def self.catalog_parse(value) + return if value.nil? + return <%= class_name %>.catalog_parse(value) \ + unless value.is_a?(::Array) + value.map { |v| <%= class_name %>.catalog_parse(v) } + end + + # Used for parsing GCP API responses + def self.api_parse(value) + return if value.nil? + return <%= class_name %>.api_parse(value) \ + unless value.is_a?(::Array) + value.map { |v| <%= class_name %>.api_parse(v) } + end + end +<% end # if emit_array -%> + end + end +end diff --git a/templates/chef/property/resourceref.rb.erb b/templates/chef/property/resourceref.rb.erb new file mode 100644 index 000000000000..979919c61295 --- /dev/null +++ b/templates/chef/property/resourceref.rb.erb @@ -0,0 +1,162 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if emit_array -%> +require 'google/<%= prop_ns_dir -%>/property/array' +<% end -%> +<%# Requires: resource - name of resource being fetched -%> +<%# imports - name of property being fetched -%> +<%= compile('templates/license.erb') -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +module Google + module <%= product_ns %> + module Data + # Base class for ResourceRefs + # Imports <%= imports -%> from <%= resource %> +<%= + lines(indent(emit_rubocop(binding, :class, + ['Google', product_ns, 'Data', + class_name].join('::'), + :disabled), 6)) +-%> + class <%= class_name %> + include Comparable + + def ==(other) + return false unless other.is_a? <%= class_name %> + return false if resource != other.resource + true + end + + def <=>(other) + resource <=> other.resource + end + + # Overriding inspect method ensures that Chef logs only the + # fetched value to the console + def inspect + "'#{resource}'" + end + end + + # A class to fetch the resource value from a referenced block + # Will return the value exported from a different Chef resource +<%= + lines(indent(emit_rubocop(binding, :class, + ['Google', product_ns, 'Data', + "#{class_name}Title"].join('::'), + :disabled), 6)) +-%> + class <%= class_name %>Catalog < <%= class_name %> + def initialize(title) + @title = title + end + + # Chef requires the title for autorequiring + def autorequires + [@title] + end + + def to_s + resource.to_s + end + + def to_json(_arg = nil) + return if resource.nil? + resource.to_json + end + + def resource +<% out_name = property.resource_ref.out_name -%> + Chef.run_context.resource_collection.each do |entry| + return entry.exports[:<%= imports -%>] if entry.name == @title + end + raise ArgumentError, "<%= out_name -%>[#{@title}] required" + end + end + + # A class to manage a JSON blob from GCP API + # Will immediately return value from JSON blob without changes +<%= + lines(indent(emit_rubocop(binding, :class, + ['Google', product_ns, 'Data', + "#{class_name}Resource"].join('::'), + :disabled), 6)) +-%> + class <%= class_name %>Api < <%= class_name %> + attr_reader :resource + + def initialize(resource) + @resource = resource + end + + def to_s + @resource.to_s + end + + def to_json(_arg = nil) + @resource.to_json + end + end + end + + module Property + # A class to manage fetching <%= imports -%> from a <%= resource %> + class <%= class_name %> +<%= emit_coerce(product_ns, class_name, 8) -%> + def catalog_parse(value) + return if value.nil? + self.class.catalog_parse(value) + end + + def self.catalog_parse(value) + return if value.nil? + return value if value.is_a? Data::<%= class_name %> + Data::<%= class_name %>Catalog.new(value) + end + + # Used for fetched JSON values + def self.api_parse(value) + return if value.nil? + return value if value.is_a? Data::<%= class_name %> + Data::<%= class_name %>Api.new(value) + end + end +<% if emit_array -%> + + # A Chef property that holds an Array of <%= class_name %> + class <%= class_name %>Array < Google::<%= product_ns -%>::Property::Array +<%= emit_coerce(product_ns, "#{class_name}Array", 8) -%> + # Used for parsing Chef catalog + def self.catalog_parse(value) + return if value.nil? + return <%= class_name %>.catalog_parse(value) \ + unless value.is_a?(::Array) + value.map { |v| <%= class_name %>.catalog_parse(v) } + end + + # Used for parsing GCP API responses + def self.api_parse(value) + return if value.nil? + return <%= class_name %>.api_parse(value) \ + unless value.is_a?(::Array) + value.map { |v| <%= class_name %>.api_parse(v) } + end + end +<% end # if emit_array -%> + end + end +end diff --git a/templates/chef/property/string.rb.erb b/templates/chef/property/string.rb.erb new file mode 100644 index 000000000000..bfac950ccf08 --- /dev/null +++ b/templates/chef/property/string.rb.erb @@ -0,0 +1,35 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +module Google + module <%= product_ns %> + module Property + # A class to handle serialization of String items. + class String +<%= emit_coerce(product_ns, 'String', 8) -%> + def self.api_parse(value) + value + end + + def self.catalog_parse(value) + value + end + end + end + end +end diff --git a/templates/chef/property/time.rb.erb b/templates/chef/property/time.rb.erb new file mode 100644 index 000000000000..e6108549da6d --- /dev/null +++ b/templates/chef/property/time.rb.erb @@ -0,0 +1,60 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'time' + +module Google + module <%= product_ns %> + module Data + # A Time that always returns a ISO-8601 string + class Time < ::Time +<%# TODO(alexstephen): Add a .to_resource method to replace .to_s -%> +<% + # Overriden .to_s ensures that value coercison does not need to be placed + # within the providers. Providers do not have to worry about the time formats +-%> + def to_s + # All GCP APIs expect timestamps in the ISO-8601 / RFC3339 format + + # Overriding the .to_s method ensures that Ruby will get a + # ISO-8601 timestamp at the last moment and ensures the timestamp + # format is abstracted away. + iso8601 + end + end + end + + module Property + # A class to handle serialization of Time items. + class Time +<%= emit_coerce(product_ns, 'Time', 8) -%> + def self.api_parse(value) + return if value.nil? + return value if value.is_a? ::Time + Data::Time.parse(value) + end + + def self.catalog_parse(value) + return if value.nil? + return value if value.is_a? ::Time + Data::Time.parse(value) + end + end + end + end +end diff --git a/templates/chef/recipe_README.md.erb b/templates/chef/recipe_README.md.erb new file mode 100644 index 000000000000..d23100fd7060 --- /dev/null +++ b/templates/chef/recipe_README.md.erb @@ -0,0 +1,21 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +# Recipes + +The examples beginning with 'examples~' are ready to be run for creating GCP +resources. + +The examples beginning with 'tests~' are not meant to be run without the +end-to-end test runner included with the Magic Module code generator. diff --git a/templates/chef/resource.erb b/templates/chef/resource.erb new file mode 100644 index 000000000000..a5ddda97defa --- /dev/null +++ b/templates/chef/resource.erb @@ -0,0 +1,605 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +# Add our google/ lib +$LOAD_PATH.unshift ::File.expand_path('../libraries', ::File.dirname(__FILE__)) + +<% + require 'google/string_utils' + + inside_indent = 8 + + requires = generate_requires(object.all_user_properties) + requires << 'chef/resource' + requires << 'google/hash_utils' + requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'get') + unless object.readonly + requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'delete') + requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'post') + requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'put') + end +-%> +<%= lines(emit_requires(requires)) -%> + +module Google + module <%= @api.prefix.upcase %> + # A provider to manage <%= @api.name -%> resources. +<%= lines(indent( + emit_rubocop(binding, :class, + ['Google', @api.prefix.upcase, object.name].join('::'), + :disabled), + 4)) -%> + class <%= object.name -%> < Chef::Resource + resource_name :<%= object.out_name %> + +<% object.all_user_properties.each do |prop| -%> +<% + prop_name = "property :#{property_out_name(prop)}" + prop_name = "property :#{label_name(object)}" if prop.out_name == 'name' + prop_attrs = [ + ('name_property: true' if prop.out_name == 'name'), + 'desired_state: true' + ].compact +-%> +<% if prop_decl(prop) == "Array" -%> +<%= + lines(format([ + ["# #{property_out_name(prop)} is Array of #{prop.property_type}"], + [ + "# #{property_out_name(prop)} is Array of", + "# #{prop.property_type}" + ] + ], 6)) +-%> +<% end -%> +<%= + lines(format([ + # All on one line + [ + ["#{prop_name}, #{prop_decl(prop)}", + "coerce: ::#{prop.property_type}.coerce", + prop_attrs].flatten.join(', ') + ], + # Prop name on first line, all others on second line + [ + "#{prop_name}", + ["#{indent(prop_decl(prop), 9)}", + "coerce: ::#{prop.property_type}.coerce", + prop_attrs].flatten.join(', ') + ], + # Prop name on first line, prop types on second line + # All others on third line + [ + "#{prop_name},", + "#{indent(prop_decl(prop), 9)},", + [indent("coerce: ::#{prop.property_type}.coerce", 9), + prop_attrs].flatten.join(', ') + ], + # Prop name on first line, prop types on second line + # Coercion one-liner on third line + # Rest of attributes on fourth line + [ + "#{prop_name},", + "#{indent(prop_decl(prop), 9)},", + indent("coerce: ::#{prop.property_type}.coerce,", 9), + "#{indent(prop_attrs.join(', '), 9)}" + ], + # Prop name on first line, prop types on second line + # Coercion two-liner on third + fourth lines + # Rest of attributes on fifth line + [ + "#{prop_name},", + "#{indent(prop_decl(prop), 9)},", + indent("coerce: \\", 9), + indent("::#{prop.property_type}.coerce,", 11), + "#{indent(prop_attrs.join(', '), 9)}" + ], + ], 6)) +-%> +<% end -%> + + property :credential, String, desired_state: false, required: true + property :project, String, desired_state: false, required: true + +<% if save_api_results?(config, object) -%> + # TODO(alexstephen): Check w/ Chef how to not expose this property yet + # allow the resource to store the @fetched API results for exports usage. + property :__fetched, Hash, desired_state: false, required: false + +<% end # save_api_results?(config, object) -%> + action :create do +<% if object.self_link_query.nil? -%> +<% if object.kind? -%> + fetch = fetch_resource(@new_resource, self_link(@new_resource), + '<%= object.kind -%>') +<% else # object.kind? -%> + fetch = fetch_resource(@new_resource, self_link(@new_resource)) +<% end # object.kind? -%> +<% else # object.self_link_query.nil? -%> + fetch = fetch_wrapped_resource(@new_resource, '<%= object.kind -%>', + '<%= object.self_link_query.kind -%>', + '<%= object.self_link_query.items -%>') +<% end # object.self_link_query.nil? -%> + if fetch.nil? +<%= + lines(format([ + ["converge_by \"Creating #{object.out_name}[\#{new_resource.name}]\" do"], + [ + "converge_by ['Creating #{object.out_name}',", + indent('"[#{new_resource.name}]"].join do', 13) + ] + ], 10, 0)) +-%> + # TODO(nelsonjr): Show a list of variables to create + # TODO(nelsonjr): Determine how to print green like update converge + puts # making a newline until we find a better way TODO: find! + compute_changes.each { |log| puts " - #{log.strip}\n" } +<% custom_create = get_code_multiline config, 'create' -%> +<% if custom_create.nil? -%> +<% + if object.create_verb.nil? || object.create_verb == :POST + body_new = 'collection(@new_resource)' + request_new = "::Google::#{product_ns}::Network::Post" + elsif object.create_verb == :PUT + body_new = 'self_link(@new_resource)' + request_new = "::Google::#{product_ns}::Network::Put" + end +-%> + create_req = <%= request_new -%>.new( + <%= body_new -%>, fetch_auth(@new_resource), + 'application/json', resource_to_request + ) +<% unless object.async -%> +<% + kind_param = object.kind? ? ", '#{object.kind}'" : '' +-%> +<% if save_api_results?(config, object) -%> + @new_resource.__fetched = + return_if_object create_req.send<%= kind_param %> +<% else # save_api_results?(config, object) -%> + return_if_object create_req.send<%= kind_param %> +<% end # save_api_results?(config, object) -%> +<% else # object.async -%> +<% if save_api_results?(config, object) -%> + @new_resource.__fetched = + wait_for_operation create_req.send, @new_resource +<% else # save_api_results?(config, object) -%> + wait_for_operation create_req.send, @new_resource +<% end # save_api_results?(config, object) -%> +<% end # object.async -%> +<% else -%> +<%= lines(indent(custom_create, 12)) -%> +<% end -%> + end + else + @current_resource = @new_resource.clone +<% + fetch_code = object.properties.reject(&:input).map do |prop| + name = property_out_name(prop) + name = label_name(object) if prop.out_name == 'name' + api_name = prop.name + type = "::#{prop.property_type}" + field_name = prop.field || prop.name + if api_name.include?('.') + fetch_tree = api_name.split('.').join(' ') + assignment = format( + [ + [ + "@current_resource.#{name} = #{type}.api_parse(", + indent("::Google::HashUtils.navigate(fetch, %w[#{fetch_tree}])", 2), + ')' + ], + [ + "@current_resource.#{name} =", + indent([ + "#{type}.api_parse(", + indent("::Google::HashUtils.navigate(fetch, %w[#{fetch_tree}])", + 2), + ')' + ], 2) + ], + [ + "@current_resource.#{name} =", + indent([ + "#{type}.api_parse(", + indent([ + '::Google::HashUtils.navigate(', + indent("fetch, %w[#{fetch_tree}]", 2), + ')' + ], 2), + ')' + ], 2) + ] + ], 0, 12 + ) + else + assignment = format( + [ + [ + "@current_resource.#{name} =", + "#{type}.api_parse(fetch['#{field_name}'])" + ].join(' '), + [ + "@current_resource.#{name} =", + indent("#{type}.api_parse(fetch['#{field_name}'])", 2) + ], + [ + "@current_resource.#{name} =", + indent([ + "#{type}.api_parse(", + indent("fetch['#{field_name}']", 2), + ')' + ], 2) + ] + ], 0, 12 + ) + end + end +-%> +<%= lines(indent(fetch_code, 10)) -%> +<% if save_api_results?(config, object) -%> + @new_resource.__fetched = fetch +<% end # save_api_results?(config, object) -%> + + update + end + end + + action :delete do +<% if object.self_link_query.nil? -%> +<% if object.kind? -%> + fetch = fetch_resource(@new_resource, self_link(@new_resource), + '<%= object.kind -%>') +<% else # object.kind? -%> + fetch = fetch_resource(@new_resource, self_link(@new_resource)) +<% end # object.kind? -%> +<% else # object.self_link_query.nil? -%> +<% obj_kind = object.kind? ? "'#{object.kind}'," : '' -%> +<% if object.self_link_query.kind.nil? -%> + fetch = fetch_wrapped_resource(@new_resource, <%= obj_kind %> + '<%= object.self_link_query.items -%>') +<% else #object.self_link_query.kind.nil? -%> + fetch = fetch_wrapped_resource(@new_resource, <%= obj_kind %> + '<%= object.self_link_query.kind -%>', + '<%= object.self_link_query.items -%>') +<% end #object.self_link_query.kind.nil? -%> +<% end # object.self_link_query.nil? -%> + unless fetch.nil? +<%= + lines(format([ + ["converge_by \"Deleting #{object.out_name}[\#{new_resource.name}]\" do"], + [ + "converge_by ['Deleting #{object.out_name}',", + indent('"[#{new_resource.name}]"].join do', 13) + ] + ], 10, 0)) +-%> +<% custom_delete = get_code_multiline config, 'delete' -%> +<% if custom_delete.nil? -%> + delete_req = ::Google::<%= product_ns -%>::Network::Delete.new( + self_link(@new_resource), fetch_auth(@new_resource) + ) +<% unless object.async -%> +<% obj_kind = object.kind? ? ", '#{object.kind}'" : '' -%> + return_if_object delete_req.send<%= obj_kind %> +<% else -%> + wait_for_operation delete_req.send, @new_resource +<% end -%> +<% else -%> +<%= lines(indent(custom_delete, 12)) -%> +<% end -%> + end + end + end + + # TODO(nelsonjr): Add actions :manage and :modify + +<% unless object.exports.nil? -%> +<% + exp_list = [ + '{', + indent_list(object.exported_properties.map do |p| + exp_name = p.out_name + exp_name = label_name(object) if p.name == 'name' + if p.is_a?(Api::Type::FetchedExternal) + "#{p.out_name}: __fetched['#{p.field_name}']" + else + "#{p.out_name}: #{exp_name}" + end + end, 2), + '}' + ] +-%> +<%= lines(indent(emit_method('exports', [], exp_list, file_relative), 6), 1) -%> +<% end -%> + private + + action_class do +<% + prop_code = [] + prop_code << "kind: '#{object.kind}'" if object.kind? + prop_code.concat(object.properties.reject { |p| p.output } + .map do |prop| + override_name = property_out_name(prop) + override_name = label_name(object) if override_name == 'name' + format([ + ["#{prop.field_name}: new_resource.#{override_name}"], + ["#{prop.field_name}:", + indent("new_resource.#{override_name}", 2)], + ], 0, 12) + end) + + prop_code.concat((object.parameters || []) + .select { |p| p.input } + .map do |prop| + override_name = property_out_name(prop) + override_name = label_name(object) if override_name == 'name' + format([ + ["#{prop.field_name}: new_resource.#{override_name}"], + ["#{prop.field_name}:", + indent("new_resource.#{override_name}", 2)], + ], 0, 12) + end + ) + + r2r_code = [] + r2r_code << 'request = {' + r2r_code << indent_list(prop_code, 2) + r2r_code << '}.reject { |_, v| v.nil? }' + + resource_to_request_patch = get_code_multiline config, + 'resource_to_request_patch' + unless resource_to_request_patch.nil? + r2r_code << resource_to_request_patch + r2r_code << '' + end # resource_to_request_patch.nil? + + if object.encoder? + r2r_code << '# Format request to conform with API endpoint' + r2r_code << "request = #{object.transport.encoder}(request)" + end + + r2r_code << 'request.to_json' + -%> +<%= + lines(indent(emit_method('resource_to_request', [], r2r_code, file_relative), + 8), 1) +-%> +<% + unless false?(Google::HashUtils.navigate(config, + %w[provider_helpers visible + unwrap_resource])) + unless object.self_link_query.nil? +-%> +<% + urf_code = [ + '{', + indent_list( + Hash[object.identity.map { |i| [i, "resource.#{property_out_name(i)}"] }] + .map { |k, v| "#{k.out_name}: #{v}" }, 2 + ), + '}' + ] +-%> + def unwrap_resource_filter(resource) + self.class.unwrap_resource_filter(resource) + end + +<%= lines(indent(emit_method('self.unwrap_resource_filter', %w[resource], + urf_code, file_relative), 8), 1) -%> +<% end # unless object.self_link_query.nil? -%> +<% end # visible:unwrap_resource -%> + def update + converge_if_changed do |_vars| + # TODO(nelsonjr): Determine how to print indented like upd converge + # TODO(nelsonjr): Check w/ Chef... can we print this in red? + puts # making a newline until we find a better way TODO: find! + compute_changes.each { |log| puts " - #{log.strip}\n" } +<% custom_update = get_code_multiline config, 'update' -%> +<% if custom_update.nil? -%> +<% put_new = "::Google::#{product_ns}::Network::Put.new" -%> +<%= lines(indent("update_req =", 12)) -%> +<%= + lines(indent_list([ + "#{put_new}(self_link(@new_resource)"].concat( + indent([ + 'fetch_auth(@new_resource)', + "'application/json'", + 'resource_to_request)' + ], put_new.length + 1).split("\n") + ), 14)) +-%> +<% if object.async -%> + wait_for_operation update_req.send, @new_resource +<% else # object.async -%> + return_if_object update_req.send, '<%= object.kind -%>' +<% end # object.async -%> +<% else # custom_update.nil? -%> +<%= lines(indent(custom_update, 12)) -%> +<% end # custom_update.nil? -%> + end + end +<% unless object.all_resourcerefs.empty? -%> + + def self.fetch_export(resource, type, id, property) + return if id.nil? + resource.resources("#{type}[#{id}]").exports[property] + end +<% end # object.exports.nil? -%> + +<% + all_properties = object.all_user_properties + has_project_property = \ + !object.all_user_properties.select { |o| o.name == 'project' }.empty? + project_arg = has_project_property ? [] : ['project: resource.project'] + has_name = !object.all_user_properties.select { |o| o.name == 'name' }.empty? + name_prop = "name: resource.name" + name_prop = "name: resource.#{label_name(object)}" if has_name + r2h_code = [ + '{', + indent_list(project_arg.concat( + [ + name_prop, + ("kind: '#{object.kind}'" if object.kind?) + ].compact + ).concat(all_properties.reject { |p| p.name == 'name' }.map do |prop| + format([ + ["#{prop.out_name}: resource.#{property_out_name(prop)}"], + [ + "#{prop.out_name}:", + indent("resource.#{property_out_name(prop)}", 2) + ] + ], 0, 12) + end), 2), + '}.reject { |_, v| v.nil? }' + ] +-%> +<%= lines(indent(emit_method('self.resource_to_hash', %w[resource], r2h_code, + file_relative), 8)) -%> + + # Copied from Chef > Provider > #converge_if_changed + def compute_changes + properties = @new_resource.class.state_properties.map(&:name) + properties = properties.map(&:to_sym) + if current_resource + compute_changes_for_existing_resource properties + else + compute_changes_for_new_resource properties + end + end + + # Collect the list of modified properties + def compute_changes_for_existing_resource(properties) + specified_properties = properties.select do |property| + @new_resource.property_is_set?(property) + end + modified = specified_properties.reject do |p| + @new_resource.send(p) == current_resource.send(p) + end + + generate_pretty_green_text(modified) + end + + def generate_pretty_green_text(modified) + property_size = modified.map(&:size).max + modified.map! do |p| + properties_str = if @new_resource.sensitive + '(suppressed sensitive property)' + else + [ + @new_resource.send(p).inspect, + "(was #{current_resource.send(p).inspect})" + ].join(' ') + end + " set #{p.to_s.ljust(property_size)} to #{properties_str}" + end + end + + # Write down any properties we are setting. + def compute_changes_for_new_resource(properties) + property_size = properties.map(&:size).max + properties.map do |property| + default = ' (default value)' \ + unless @new_resource.property_is_set?(property) + next if @new_resource.send(property).nil? + properties_str = if @new_resource.sensitive + '(suppressed sensitive property)' + else + @new_resource.send(property).inspect + end + [" set #{property.to_s.ljust(property_size)}", + "to #{properties_str}#{default}"].join(' ') + end.compact + end + +<% unless object.self_link_query.nil? -%> +<% + r2q_code = [ + '{', + indent_list( + Hash[object.identity.map { |i| [i, "resource.#{i.out_name}"] }] + .map { |k, v| "#{k.out_name}: #{v}" }, 2 + ), + '}' + ] +-%> + def resource_to_query_predicate(resource) + self.class.resource_to_query_predicate(resource) + end + +<%= lines(indent(emit_method('self.resource_to_query_predicate', %w[resource], + r2q_code, file_relative), inside_indent), 1) -%> +<% end # unless object.self_link_query.nil? -%> + def fetch_auth(resource) + self.class.fetch_auth(resource) + end + + def self.fetch_auth(resource) + resource.resources("gauth_credential[#{resource.credential}]") + .authorization + end + +<% if object.kind? -%> + def fetch_resource(resource, self_link, kind) + self.class.fetch_resource(resource, self_link, kind) + end +<% else -%> + def fetch_resource(resource, self_link) + self.class.fetch_resource(resource, self_link) + end +<% end -%> + + def debug(message) + Chef::Log.debug(message) + end + +<% custom_collection = get_code_multiline config, 'collection' -%> +<% if custom_collection.nil? -%> +<%= lines(indent(emit_link('collection', collection_url(object), true), 8)) %> +<% else # custom_collection.nil? -%> +<%= lines(indent(emit_link('collection', custom_collection, true), 8)) %> +<% end # custom_collection.nil? -%> +<% custom_self_link = get_code_multiline config, 'self_link' -%> +<% if custom_self_link.nil? -%> +<%= lines(indent(emit_link('self_link', self_link_url(object), true), 8), 1) -%> +<% else # custom_self_link.nil? -%> +<%= lines(indent(emit_link('self_link', custom_self_link, true), 8), 1) -%> +<% end # custom_self_link.nil? -%> +<% custom_return_if_object = get_code_multiline config, 'return_if_object' -%> +<% if custom_return_if_object.nil? -%> +<%= lines(indent(compile('templates/return_if_object.erb'), 8)) %> +<% else # custom_return_if_object.nil? -%> +<%= lines(indent(custom_return_if_object, 8), 1) -%> +<% end # custom_return_if_object.nil? -%> +<%= lines(indent(compile('templates/expand_variables.erb'), 8)) %> +<%= + if object.async + lines(indent(compile('templates/async.erb'), inside_indent), 1) + end +-%> +<%= lines(indent(compile('templates/provider_helpers.erb'), 8), 1) -%> +<%= lines(indent(compile('templates/transport.erb'), 8)) -%> + end + end +<%= lines(indent( + emit_rubocop(binding, :class, + ['Google', @api.prefix.upcase, object.name].join('::'), + :enabled), + 4)) -%> + end +end diff --git a/templates/chef/resource_spec.erb b/templates/chef/resource_spec.erb new file mode 100644 index 000000000000..349ba9b9d86a --- /dev/null +++ b/templates/chef/resource_spec.erb @@ -0,0 +1,383 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'spec_helper' + +# TODO(alexstephen): Reformat tests to use nested describe blocks +# TODO(alexstephen): Add title == name tests +# Test Matrix: +# +# +--------------------------------------------------------+ +# | Action | Exists | Changes | Success | Result | +# +--------------------------------------------------------+ +# | create | Y | Y | Y | Edit | +# | create | Y | Y | N | Fail | +# | create | Y | N | Y | Fetch (no-op) | +# | create | Y | N | N | Fail | +# | create | N | Y | Y | Create | +# | create | N | Y | N | Fail | +# +--------------------------------------------------------+ +# | delete | Y | Y | Y | Delete | +# | delete | Y | Y | N | Fail | +# | delete | N | Y | Y | Fail (no delete)| +# | delete | N | Y | N | Fail | +# +--------------------------------------------------------+ +# TODO(alexstephen): Add tests for manage +# TODO(alexstephen): Add tests for modify +<% +if object.virtual + test_matrix = Provider::TestMatrix.new(template, object, self, + exists: { + changes: [ # read-only object mistmatch + [:no_name, :fail], + [:has_name, :fail] + ], + no_change: [ + [:no_name, :pass], + [:has_name, :pass] + ], + }, + missing: [ + [:no_name, :fail], + [:has_name, :fail] + ] + ) +else + test_matrix = Provider::TestMatrix.new(template, object, self, + present: { + exists: { + changes: { # converge + no_name: [:pass, :fail], + has_name: [:pass, :fail] + }, + no_change: { # no action + no_name: [:pass, :fail], + has_name: [:pass, :fail] + } + }, + missing: { # create + # changes == ignore + no_name: [:pass, :fail], + has_name: [:pass, :fail] + } + }, + absent: { + exists: { # delete + # changes == ignore + no_name: [:pass, :fail], + has_name: [:pass, :fail] + }, + missing: { # no action + # changes == ignore + no_name: [:pass, :fail], + has_name: [:pass, :fail] + } + } + ) +end + +prop_data = Provider::TestData::Expectations.new(self, @data_gen) +catalogger = Provider::ChefTestCatalogFormatter.new self +-%> +context '<%= object.out_name -%>' do +<% if object.virtual -%> +<% # Object does NOT provides 'ensure' parameter -%> +<%= test_matrix.push(:ignore, :exists) -%> +<%= test_matrix.push(:ignore, :exists, :no_change) -%> +<%= test_matrix.push(:ignore, :exists, :no_change, [:no_name, :pass]) -%> + # TODO(alexstephen): Implement new test format. +<%= test_matrix.pop(:ignore, :exists, :no_change, [:no_name, :pass]) -%> + +<%= test_matrix.push(:ignore, :exists, :no_change, [:has_name, :pass]) -%> + # TODO(alexstephen): Implement new test format. +<%= test_matrix.pop(:ignore, :exists, :no_change, [:has_name, :pass]) -%> +<%= test_matrix.pop(:ignore, :exists, :no_change) -%> + +<%= test_matrix.push(:ignore, :exists, :changes) -%> +<%= test_matrix.push(:ignore, :exists, :changes, [:no_name, :fail]) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:ignore, :exists, :changes, [:no_name, :fail]) -%> + +<%= test_matrix.push(:ignore, :exists, :changes, [:has_name, :fail]) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:ignore, :exists, :changes, [:has_name, :fail]) -%> +<%= test_matrix.pop(:ignore, :exists, :changes) -%> +<%= test_matrix.pop(:ignore, :exists) -%> + +<%= test_matrix.push(:ignore, :missing) -%> +<%= test_matrix.push(:ignore, :missing, :ignore, [:no_name, :fail]) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:ignore, :missing, :ignore, [:no_name, :fail]) -%> + +<%= test_matrix.push(:ignore, :missing, :ignore, [:has_name, :fail]) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:ignore, :missing, :ignore, [:has_name, :fail]) -%> +<%= test_matrix.pop(:ignore, :missing) -%> +<% else -%> +<% # Object that provides 'ensure' parameter -%> +<%= test_matrix.push(:present) -%> +<%= test_matrix.push(:present, :exists) -%> +<%= test_matrix.push(:present, :exists, :no_change) -%> +<%= test_matrix.push(:present, :exists, :no_change, :no_name) -%> +<%= test_matrix.push(:present, :exists, :no_change, :no_name, :pass) -%> +<%= + has_name = false + indent(compile('templates/chef/tests/present~no_changes.erb'), + (test_matrix.level + 1) * 2) +%> +<%= test_matrix.pop(:present, :exists, :no_change, :no_name, :pass) -%> + +<%= test_matrix.push(:present, :exists, :no_change, :no_name, :fail) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :exists, :no_change, :no_name, :fail) -%> +<%= test_matrix.pop(:present, :exists, :no_change, :no_name) -%> + +<%= test_matrix.push(:present, :exists, :no_change, :has_name) -%> +<%= test_matrix.push(:present, :exists, :no_change, :has_name, :pass) -%> +<%= + has_name = true + indent(compile('templates/chef/tests/present~no_changes.erb'), + (test_matrix.level + 1) * 2) +%> +<%= test_matrix.pop(:present, :exists, :no_change, :has_name, :pass) -%> + +<%= test_matrix.push(:present, :exists, :no_change, :has_name, :fail) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :exists, :no_change, :has_name, :fail) -%> +<%= test_matrix.pop(:present, :exists, :no_change, :has_name) -%> +<%= test_matrix.pop(:present, :exists, :no_change) -%> + +<%= test_matrix.push(:present, :exists, :changes) -%> +<%= test_matrix.push(:present, :exists, :changes, :no_name) -%> +<%= test_matrix.push(:present, :exists, :changes, :no_name, :pass) -%> + # TODO(alexstephen): Implement new test format. +<%= test_matrix.pop(:present, :exists, :changes, :no_name, :pass) -%> + +<%= test_matrix.push(:present, :exists, :changes, :no_name, :fail) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :exists, :changes, :no_name, :fail) -%> +<%= test_matrix.pop(:present, :exists, :changes, :no_name) -%> + +<%= test_matrix.push(:present, :exists, :changes, :has_name) -%> +<%= test_matrix.push(:present, :exists, :changes, :has_name, :pass) -%> + # TODO(alexstephen): Implement new test format +<%= test_matrix.pop(:present, :exists, :changes, :has_name, :pass) -%> + +<%= test_matrix.push(:present, :exists, :changes, :has_name, :fail) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :exists, :changes, :has_name, :fail) -%> +<%= test_matrix.pop(:present, :exists, :changes, :has_name) -%> +<%= test_matrix.pop(:present, :exists, :changes) -%> +<%= test_matrix.pop(:present, :exists) -%> + +<%= test_matrix.push(:present, :missing) -%> +<%= test_matrix.push(:present, :missing, :ignore, :no_name) -%> +<%= test_matrix.push(:present, :missing, :ignore, :no_name, :pass) -%> +<%= + # Creates the resource + has_name = false + indent(compile('templates/chef/tests/present~create.erb'), + (test_matrix.level + 1) * 2) +%> +<%= test_matrix.pop(:present, :missing, :ignore, :no_name, :pass) -%> + +<%= test_matrix.push(:present, :missing, :ignore, :no_name, :fail) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :missing, :ignore, :no_name, :fail) -%> +<%= test_matrix.pop(:present, :missing, :ignore, :no_name) -%> + +<%= test_matrix.push(:present, :missing, :ignore, :has_name) -%> +<%= test_matrix.push(:present, :missing, :ignore, :has_name, :pass) -%> +<%= + # Creates the resource + has_name = true + indent(compile('templates/chef/tests/present~create.erb'), + (test_matrix.level + 1) * 2) +%> +<%= test_matrix.pop(:present, :missing, :ignore, :has_name, :pass) -%> + +<%= test_matrix.push(:present, :missing, :ignore, :has_name, :fail) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :missing, :ignore, :has_name, :fail) -%> +<%= test_matrix.pop(:present, :missing, :ignore, :has_name) -%> +<%= test_matrix.pop(:present, :missing) -%> +<%= test_matrix.pop(:present) -%> + +<%= test_matrix.push(:absent) -%> +<%= test_matrix.push(:absent, :missing) -%> +<%= test_matrix.push(:absent, :missing, :ignore, :no_name) -%> +<%= test_matrix.push(:absent, :missing, :ignore, :no_name, :pass) -%> +<%= + has_name = false + indent(compile('templates/chef/tests/absent~no_action.erb'), + (test_matrix.level + 1) * 2) +%> +<%= test_matrix.pop(:absent, :missing, :ignore, :no_name, :pass) -%> + +<%= test_matrix.push(:absent, :missing, :ignore, :no_name, :fail) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:absent, :missing, :ignore, :no_name, :fail) -%> +<%= test_matrix.pop(:absent, :missing, :ignore, :no_name) -%> + +<%= test_matrix.push(:absent, :missing, :ignore, :has_name) -%> +<%= test_matrix.push(:absent, :missing, :ignore, :has_name, :pass) -%> +<%= + has_name = true + indent(compile('templates/chef/tests/absent~no_action.erb'), + (test_matrix.level + 1) * 2) +%> +<%= test_matrix.pop(:absent, :missing, :ignore, :has_name, :pass) -%> + +<%= test_matrix.push(:absent, :missing, :ignore, :has_name, :fail) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:absent, :missing, :ignore, :has_name, :fail) -%> +<%= test_matrix.pop(:absent, :missing, :ignore, :has_name) -%> +<%= test_matrix.pop(:absent, :missing) -%> + +<%= test_matrix.push(:absent, :exists) -%> +<%= test_matrix.push(:absent, :exists, :ignore, :no_name) -%> +<%= test_matrix.push(:absent, :exists, :ignore, :no_name, :pass) -%> +<%= + # Delete the resource + has_name = false + indent(compile('templates/chef/tests/absent~delete.erb'), + (test_matrix.level + 1) * 2) +%> +<%= test_matrix.pop(:absent, :exists, :ignore, :no_name, :pass) -%> + +<%= test_matrix.push(:absent, :exists, :ignore, :no_name, :fail) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:absent, :exists, :ignore, :no_name, :fail) -%> +<%= test_matrix.pop(:absent, :exists, :ignore, :no_name) -%> +<%= test_matrix.push(:absent, :exists, :ignore, :has_name) -%> +<%= test_matrix.push(:absent, :exists, :ignore, :has_name, :pass) -%> +<%= + # Delete the resource + has_name = true + indent(compile('templates/chef/tests/absent~delete.erb'), + (test_matrix.level + 1) * 2) +%> +<%= test_matrix.pop(:absent, :exists, :ignore, :has_name, :pass) -%> + +<%= test_matrix.push(:absent, :exists, :ignore, :has_name, :fail) -%> + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:absent, :exists, :ignore, :has_name, :fail) -%> +<%= test_matrix.pop(:absent, :exists, :ignore, :has_name) -%> +<%= test_matrix.pop(:absent, :exists) -%> +<%= test_matrix.pop(:absent) -%> +<% end -%> + + def expand_variables(template, data, extra_data = {}) +<% prefix = object.__product.prefix.upcase -%> + Google::<%= prefix -%>::<%= object.name %> + .action_class.expand_variables(template, data, extra_data) + end +<% if object.encoder? -%> + + def <%= object.transport.encoder -%>(resource) + Google::<%= prefix -%>::<%= object.name %> + .action_class.<%= object.transport.encoder -%>(resource) + end +<% end # object.encoder? -%> + +<%= lines(indent(compile('templates/network_mocks.erb'), 2), 1) -%> +<%= + lines(indent(compile('templates/chef/resourceref_expandvars.erb'), 2), 1) +-%> +<%= lines(indent(compile_if(tests, %w[expectation_helpers]), 2), 1) -%> +<%= lines(indent(emit_link('collection', collection_url(object), false), 2)) %> +<%= lines(indent(emit_link('self_link', self_link_url(object), false), 2)) -%> + +<% uri_data = @constants.value_assign(object) -%> +<% if uri_data.empty? -%> + # Creates variable test data to comply with self_link URI parameters + def uri_data(_id) + {} + end +<% else # value.keys.empty? -%> + # Creates variable test data to comply with self_link URI parameters + def uri_data(id) + { +<%= lines(indent_list(uri_data, 6)) -%> + } + end +<% end # value.keys.empty? -%> + + def build_cred + <<-CRED + gauth_credential 'mycred' do + action :serviceaccount + path '/home' + scopes [ + 'test_path' + ] + end + CRED + end + + # Creates a test recipe file and runs a block before destroying the file + def apply_recipe(recipe) + # Creates a random string name + recipe_name = "recipe~test~#{(0...8).map { (65 + rand(26)).chr }.join}" + recipe_loc = File.join(File.dirname(__FILE__), '..', 'recipes', + "#{recipe_name}.rb") + + File.open(recipe_loc, 'w') do |file| + file.write([build_cred, recipe].join("\n")) + end +<%= + lines(indent(format([ + ["recipe_path = \"google-#{object.__product.prefix.downcase}", + "::\#{recipe_name}\""].join + ]), 4)) +-%> + begin + yield recipe_path + ensure + File.delete(recipe_loc) + end + end +end diff --git a/templates/chef/resourceref_expandvars.erb b/templates/chef/resourceref_expandvars.erb new file mode 100644 index 000000000000..e9fa5e8754d7 --- /dev/null +++ b/templates/chef/resourceref_expandvars.erb @@ -0,0 +1,27 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%# Requires: none -%> +<% object.all_resourcerefs.each do |ref| -%> +<% + prop_ref = ref.resource_ref + ref_underscore_name = Google::StringUtils.underscore(ref.resource) +-%> +def expand_variables_<%= ref_underscore_name -%>(template, data, ext_dat = {}) +<% prefix = object.__product.prefix.upcase -%> + Google::<%= prefix -%>::<%= prop_ref.name %> + .action_class.expand_variables(template, data, ext_dat) +end + +<% end -%> diff --git a/templates/chef/spec_helper.rb.erb b/templates/chef/spec_helper.rb.erb new file mode 100644 index 000000000000..831c81489c50 --- /dev/null +++ b/templates/chef/spec_helper.rb.erb @@ -0,0 +1,79 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +#---------------------------------------------------------- +# Setup timezone. +# +# Our default timezone is UTC, to avoid local time compromise +# test code seed generation. + +ENV['TZ'] = 'UTC' + +#---------------------------------------------------------- +require 'simplecov' +SimpleCov.start + +$LOAD_PATH.unshift(File.expand_path('.')) +$LOAD_PATH.unshift(File.expand_path('./libraries')) +$LOAD_PATH.unshift(File.expand_path('./resources')) +$LOAD_PATH.unshift(File.expand_path('../chef-google-auth/lib')) + +require 'network_blocker' + +# Enable access to localhost as Chef creates a fake HTTP to fetch the proxy +# info. Refer offending code and explanation at: +# chef-12.19.36/lib/chef/monkey_patches/net_http.rb:49 +Google::<%= product_ns -%>::NetworkBlocker.instance.allowed_test_hosts \ + << { host: '::1', port: 80 } + +files = [] +files << 'spec/bundle.rb' +files << 'spec/copyright.rb' +files << 'spec/fake_auth.rb' +files << 'spec/fake_cred.rb' +files << 'spec/test_constants.rb' +files << File.join('libraries', '**', '*.rb') +files << File.join('resources', '**', '*.rb') + +# Require chef first as spec/credential.rb is dependant +require 'chef' + +# Require all files so we can track them via code coverage +Dir[*files].reject { |p| File.directory? p } + .each do |f| + puts "Auto requiring #{f}" \ + if ENV['RSPEC_DEBUG'] + require f + end + +require 'pp' +require 'yaml' +require 'chefspec' + +# Matchers required for ChefSpec Resource tests +def create(res_type, res_name) + ChefSpec::Matchers::ResourceMatcher.new(res_type, :create, res_name) +end + +def delete(res_type, res_name) + ChefSpec::Matchers::ResourceMatcher.new(res_type, :delete, res_name) +end + +RSpec.configure do |c| + c.filter_run_excluding broken: true +end diff --git a/templates/chef/tests/absent~delete.erb b/templates/chef/tests/absent~delete.erb new file mode 100644 index 000000000000..375362342b17 --- /dev/null +++ b/templates/chef/tests/absent~delete.erb @@ -0,0 +1,92 @@ +<% + title_name = 'title_eq_name' + title_name = 'title_and_name' if has_name + config_path = %w[absent exists] + ['success', title_name] + + # Build out a graph of objects. + # This graph, including the current object, will be stored in a + # TestObjects instance. + collector = Dependencies::DependencyGraph.new(@data_gen) + collector.add(object, 0, (name ? :name : :title), action: ':delete') + + # Generate the Chef catalog using the graph of objects above. + # This manifest will be ordered by dependencies. + resources = catalogger.generate_all_objects(collector, object.name, + has_name ? :name : :title, + action: ':delete') + + # Creates the expect_network_* statements required for this test. + # This includes the expect statements for all referenced resources. + expectations = prop_data.delete_before_data(tests, object, + { + path: config_path + %w[before], + exists: true, + has_name: has_name + }, collector) + + resource_block = resources.flatten(1).uniq.map { |r| lines(r) }.join("\n") +-%> +before do +<%= + lines(indent(expectations, 2)) +-%> +end + +let(:runner) do + # This path ensures that the current cookbook is + # loaded for testing. + # This path will load all other cookbooks in the + # parent directory. + # Under most circumstances, this will include the + # real google-gauth cookbook. + parent_dir = File.join(File.dirname(__FILE__), '..', '..') + cookbook_paths = [parent_dir] + + # If there's no real version of the google-gauth , + # add in the mocked version so that the tests do not fail. + # Since cookbooks can have any name, we assume that + # any directory with the word auth is the google-gauth cookbook. + if Dir.entries(parent_dir).select { |p| p.include? 'auth' }.empty? + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + end + ChefSpec::SoloRunner.new( +<%= lines(step_into_list(object, 4, 10)) -%> + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) +end + +let(:chef_run) do + apply_recipe( + <<-MANIFEST +<%= lines(indent(resource_block, 6)) -%> + MANIFEST + ) do |recipe_name| + runner.converge(recipe_name) do + cred = Google::CredentialResourceMock.new('mycred', + runner.run_context) + runner.resource_collection.insert(cred) + end + end +end + +subject do +<% + find_lines = [ + "chef_run.find_resource(:#{object.out_name},", + "'title0')" + ] +-%> +<%= + format([ + [find_lines[0] + " #{find_lines[1]}"], + [find_lines[0], indent(find_lines[1], 24)] + ], 2, (test_matrix.level + 1) * 2) +%> +end + +it 'should run test correctly' do + expect(chef_run).to delete(:<%= object.out_name -%>, + 'title0') +end diff --git a/templates/chef/tests/absent~no_action.erb b/templates/chef/tests/absent~no_action.erb new file mode 100644 index 000000000000..0c50cb2cb6eb --- /dev/null +++ b/templates/chef/tests/absent~no_action.erb @@ -0,0 +1,85 @@ +<% + title_name = 'title_eq_name' + title_name = 'title_and_name' if has_name + config_path = %w[absent not_exist] + ['success', title_name] + + # Build out a graph of objects. + # This graph, including the current object, will be stored in a + # TestObjects instance. + collector = Dependencies::DependencyGraph.new(@data_gen) + collector.add(object, 0, (name ? :name : :title), action: ':delete') + + # Generate the Chef catalog using the graph of objects above. + # This manifest will be ordered by dependencies. + resources = catalogger.generate_all_objects(collector, object.name, + has_name ? :name : :title, + action: ':delete') + + # Creates the expect_network_* statements required for this test. + # This includes the expect statements for all referenced resources. + expectations = prop_data.delete_before_data(tests, object, + { + path: config_path + %w[before], + exists: false, + has_name: has_name + }, collector) + + resource_block = resources.flatten(1).uniq.map { |r| lines(r) }.join("\n") +-%> +before do +<%= lines(indent(expectations, 2)) -%> +end + +let(:runner) do + # This path ensures that the current cookbook is + # loaded for testing. + # This path will load all other cookbooks in the + # parent directory. + # Under most circumstances, this will include the + # real google-gauth cookbook. + parent_dir = File.join(File.dirname(__FILE__), '..', '..') + cookbook_paths = [parent_dir] + + # If there's no real version of the google-gauth , + # add in the mocked version so that the tests do not fail. + # Since cookbooks can have any name, we assume that + # any directory with the word auth is the google-gauth cookbook. + if Dir.entries(parent_dir).select { |p| p.include? 'auth' }.empty? + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + end + ChefSpec::SoloRunner.new( +<%= lines(step_into_list(object, 4, 10)) -%> + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) +end + +let(:chef_run) do + apply_recipe( + <<-MANIFEST +<%= lines(indent(resource_block, 6)) -%> + MANIFEST + ) do |recipe_name| + runner.converge(recipe_name) do + cred = Google::CredentialResourceMock.new('mycred', + runner.run_context) + runner.resource_collection.insert(cred) + end + end +end + +subject do +<% + find_lines = [ + "chef_run.find_resource(:#{object.out_name},", + "'title0')" + ] +-%> +<%= + format([ + [find_lines[0] + " #{find_lines[1]}"], + [find_lines[0], indent(find_lines[1], 24)] + ], 2, (test_matrix.level + 1) * 2) +%> +end diff --git a/templates/chef/tests/present~create.erb b/templates/chef/tests/present~create.erb new file mode 100644 index 000000000000..0c68d74d844c --- /dev/null +++ b/templates/chef/tests/present~create.erb @@ -0,0 +1,131 @@ +<% + title_name = 'title_eq_name' + title_name = 'title_and_name' if has_name + config_path = %w[present not_exist] + ['success', title_name] + + # Build out a graph of objects. + # This graph, including the current object, will be stored in a + # TestObjects instance. + collector = Dependencies::DependencyGraph.new(@data_gen) + collector.add(object, 0, (name ? :name : :title), action: ':create') + + # Generate the Chef catalog using the graph of objects above. + # This manifest will be ordered by dependencies. + resources = catalogger.generate_all_objects(collector, object.name, + has_name ? :name : :title, + action: ':create') + + # Creates the expect_network_* statements required for this test. + # This includes the expect statements for all referenced resources. + expect_data = @create_data.create_expect_data(config_path + %w[result], + has_name, tests, object) + + expectations = prop_data.create_before_data(tests, object, + { + path: config_path + %w[before], + has_name: has_name, + expected_data: expect_data + }, collector) + + # Create network expectations using collected resourcerefs + + # TODO(nelsonjr): Fix test when object references itself as a ResourceRef, + # e.g. a bucket ACL points back to a bucket as a resource ref. When generating + # the references the code it oblivious to that fact and attempt to have + # various objects created to satisfy the dependency. That leads to collision + # of object seed==0, as well as not having objects being tested created to + # satisty dependency. + + references_self_type = false + collector.each do |obj| + references_self_type = true if obj.parent && \ + obj.parent.__resource == obj.object + end + resource_block = resources.flatten(1).uniq.map { |r| lines(r) }.join("\n") +-%> +before do +<%= lines(indent(expectations, 2)) -%> +end + +let(:runner) do + # This path ensures that the current cookbook is + # loaded for testing. + # This path will load all other cookbooks in the + # parent directory. + # Under most circumstances, this will include the + # real google-gauth cookbook. + parent_dir = File.join(File.dirname(__FILE__), '..', '..') + cookbook_paths = [parent_dir] + + # If there's no real version of the google-gauth , + # add in the mocked version so that the tests do not fail. + # Since cookbooks can have any name, we assume that + # any directory with the word auth is the google-gauth cookbook. + if Dir.entries(parent_dir).select { |p| p.include? 'auth' }.empty? + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + end + ChefSpec::SoloRunner.new( +<%= lines(step_into_list(object, 4, 10)) -%> + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) +end + +let(:chef_run) do + apply_recipe( + <<-MANIFEST +<%= lines(indent(resource_block, 6)) -%> + MANIFEST + ) do |recipe_name| + runner.converge(recipe_name) do + cred = Google::CredentialResourceMock.new('mycred', + runner.run_context) + runner.resource_collection.insert(cred) + end + end +end + +subject do +<% + find_lines = [ + "chef_run.find_resource(:#{object.out_name},", + "'title0')" + ] +-%> +<%= + format([ + [find_lines[0] + " #{find_lines[1]}"], + [find_lines[0], indent(find_lines[1], 24)] + ], 2, (test_matrix.level + 1) * 2) +%> +end + +<% # TODO(alexstephen): Temporarily disblae tests where object has reference + # to its own type -%> +<% if references_self_type -%> +it 'should run test correctly', broken: true do + pending('Implement tests where object references its own type') +end +<% else -%> +it 'should run test correctly' do + expect(chef_run).to create(:<%= object.out_name -%>, + 'title0') +end +<% +object.all_user_properties.select { |p| !p.output } + .each do |p| +-%> +<% + value = @data_gen.value(p.class, p, 0) + value = '\'title0\'' if p.name == 'name' && !has_name + name_override = label_name(object) if p.name == 'name' +-%> +<%= + lines(indent(@property.property(p, 0, @data_gen.comparator(p), + value, + (test_matrix.level + 1) * 2, + name_override), 0)) +%> +<% end # all_user_props.each -%> +<% end # references_self_type -%> diff --git a/templates/chef/tests/present~no_changes.erb b/templates/chef/tests/present~no_changes.erb new file mode 100644 index 000000000000..412db9d933f8 --- /dev/null +++ b/templates/chef/tests/present~no_changes.erb @@ -0,0 +1,152 @@ +<% + title_name = 'title_eq_name' + title_name = 'title_and_name' if has_name + config_path = if has_name + %w[present exist success title_and_name before] + else + %w[present exist success title_eq_name before] + end + cust_before = get_code_multiline(tests, config_path) + + def expt(id, has_name, object, space_used, prop_data) + line = "expect_network_get_success" + prop_data.create_expectation(line, has_name, object, space_used, [], id) + end + + # Build out a graph of objects. + # This graph, including the current object, will be stored in a + # TestObjects instance. + # Build out a graph of objects. + collector = Dependencies::DependencyGraph.new(@data_gen) + (0..2).each do |index| + collector.add(object, index, (name ? :name : :title), action: ':create') + end + + # Generate the Chef catalog using the graph of objects above. + # This manifest will be ordered by dependencies. + resources = catalogger.generate_all_objects(collector, object.name, + has_name ? :name : :title, + action: ':create') + + # Creates the expect_network_* statements required for this test. + # This includes the expect statements for all referenced resources. + expectations = [ + # Generate network expectations for 3 objects + (1..3).map do |idx| + prop_data.create_expectation('expect_network_get_success', has_name, + object, (test_matrix.level + 2) * 2, + [], idx) + end, + prop_data.create_resource_ref_get_success(object, collector, + (test_matrix.level + 2) * 2) + ].flatten.compact.uniq + + # TODO(nelsonjr): Fix test when object references itself as a ResourceRef, + # e.g. a bucket ACL points back to a bucket as a resource ref. When generating + # the references the code it oblivious to that fact and attempt to have + # various objects created to satisfy the dependency. That leads to collision + # of object seed==0, as well as not having objects being tested created to + # satisty dependency. + references_self_type = false + collector.each do |obj| + references_self_type = true if obj.parent && \ + obj.parent.__resource == obj.object + end + + resource_block = resources.flatten(1).uniq.map { |r| lines(r) }.join("\n") +-%> +<% if !cust_before.nil? -%> +before do +<%= lines(indent(cust_before, 2)) -%> +end +<% else -%> +before do + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) +<%= lines(indent(expectations, 2)) -%> +end +<% end # if !cust_before.nil? -%> + +let(:runner) do + # This path ensures that the current cookbook is + # loaded for testing. + # This path will load all other cookbooks in the + # parent directory. + # Under most circumstances, this will include the + # real google-gauth cookbook. + parent_dir = File.join(File.dirname(__FILE__), '..', '..') + cookbook_paths = [parent_dir] + + # If there's no real version of the google-gauth , + # add in the mocked version so that the tests do not fail. + # Since cookbooks can have any name, we assume that + # any directory with the word auth is the google-gauth cookbook. + if Dir.entries(parent_dir).select { |p| p.include? 'auth' }.empty? + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + end + ChefSpec::SoloRunner.new( +<%= lines(step_into_list(object, 4, 10)) -%> + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) +end + +let(:chef_run) do + apply_recipe( + <<-MANIFEST +<%= lines(indent(resource_block, 6)) -%> + MANIFEST + ) do |recipe_name| + runner.converge(recipe_name) do + cred = Google::CredentialResourceMock.new('mycred', + runner.run_context) + runner.resource_collection.insert(cred) + end + end +end +<% 3.times.each do |index| -%> + +<% if references_self_type -%> +context '<%= object.out_name -%>[title<%= index -%>]', broken: true do + pending('Implement tests where object references its own type') +<% else -%> +context '<%= object.out_name -%>[title<%= index -%>]' do + subject do +<% + find_lines = [ + "chef_run.find_resource(:#{object.out_name},", + "'title#{index}')" + ] +-%> +<%= + lines(format([ + [find_lines.join(' ')], + [ + find_lines[0], + indent(find_lines[1], 23) + ] + ], 4, (test_matrix.level + 2) * 2)) +-%> + end +<% +object.all_user_properties.select { |p| !p.output } + .each do |p| +-%> +<% + value = @data_gen.value(p.class, p, index) + value = "\'title#{index}\'" if p.name == 'name' && !has_name + name_override = label_name(object) if p.name == 'name' +-%> + +<%= + lines(indent(@property.property(p, index, @data_gen.comparator(p), + value, + (test_matrix.level + 2) * 2, + name_override), 2)) +-%> +<% end # all_user_props.each -%> +<% end # references_self_type -%> +end +<% end # 3.times -%> diff --git a/templates/dot~gitignore b/templates/dot~gitignore new file mode 100644 index 000000000000..60cfd52b9bf3 --- /dev/null +++ b/templates/dot~gitignore @@ -0,0 +1,23 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile('templates/license.erb') -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +*.swp +coverage/* + +# Keep project coverage statistics under source control +!coverage/.last_run.json diff --git a/templates/dot~rubocop~root.yml b/templates/dot~rubocop~root.yml new file mode 100644 index 000000000000..0f0681b4e78b --- /dev/null +++ b/templates/dot~rubocop~root.yml @@ -0,0 +1,31 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +Metrics/AbcSize: + Max: 30 + +Metrics/BlockLength: + Enabled: false + +Metrics/MethodLength: + Max: 15 + +# Puppet likes using :true and :false inside of type files +# Rubocop wants these to be true and false instead. +Lint/BooleanSymbol: + Enabled: false diff --git a/templates/dot~rubocop~spec.yml b/templates/dot~rubocop~spec.yml new file mode 100644 index 000000000000..dbae2df547e5 --- /dev/null +++ b/templates/dot~rubocop~spec.yml @@ -0,0 +1,25 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +inherit_from: ../.rubocop.yml + +Metrics/AbcSize: + Max: 40 + +Metrics/MethodLength: + Max: 50 diff --git a/templates/end2end_README.md b/templates/end2end_README.md new file mode 100644 index 000000000000..02a1d22d5f2a --- /dev/null +++ b/templates/end2end_README.md @@ -0,0 +1,8 @@ +# End-to-End Test Data + +This folder contains the data files that are run for full end-to-end tests. +These tests are meant to be run by the end-to-end test runner that are included +with the MagicModule code generator. + +Please note that running these examples will result in charges to your GCP +account. diff --git a/templates/example/compiled_file b/templates/example/compiled_file new file mode 100644 index 000000000000..d2db80dca738 --- /dev/null +++ b/templates/example/compiled_file @@ -0,0 +1,19 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> +This file was compiled. That's because there's some Ruby code to inject the +above license and autogenerated notice! diff --git a/templates/example/copied_file b/templates/example/copied_file new file mode 100644 index 000000000000..a4501b82b7b4 --- /dev/null +++ b/templates/example/copied_file @@ -0,0 +1,2 @@ +This file was copied. Absolutely no changes exist between this file and the +file in the templates/example directory. diff --git a/templates/example/resource.erb b/templates/example/resource.erb new file mode 100644 index 000000000000..661d40dec3d0 --- /dev/null +++ b/templates/example/resource.erb @@ -0,0 +1,23 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +Resource Name: <%= object.name %> +Resource Properties: +<% object.all_user_properties.each do |prop| -%> + - <%= prop.name %> +<% end -%> diff --git a/templates/expand_variables.erb b/templates/expand_variables.erb new file mode 100644 index 000000000000..f91bc72a720b --- /dev/null +++ b/templates/expand_variables.erb @@ -0,0 +1,19 @@ +def self.extract_variables(template) + template.scan(/{{[^}]*}}/).map { |v| v.gsub(/{{([^}]*)}}/, '\1') } + .map(&:to_sym) +end + +def self.expand_variables(template, var_data, extra_data = {}) + data = if var_data.class <= Hash + var_data.merge(extra_data) + else + resource_to_hash(var_data).merge(extra_data) + end + extract_variables(template).each do |v| + unless data.key?(v) + raise "Missing variable :#{v} in #{data} on #{caller.join("\n")}}" + end + template.gsub!(/{{#{v}}}/, CGI.escape(data[v].to_s)) + end + template +end diff --git a/templates/fake_auth.erb b/templates/fake_auth.erb new file mode 100644 index 000000000000..392b53ccb831 --- /dev/null +++ b/templates/fake_auth.erb @@ -0,0 +1,28 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile('templates/license.erb') -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +module Google + # A dummy authorization handler that responds to the same authorize interface + # as a gauth_credential. Useful for testing code without the need to take a + # dependency on the real resource provider. + class FakeAuthorization + def authorize(request) + request + end + end +end diff --git a/templates/global_async.yaml.erb b/templates/global_async.yaml.erb new file mode 100644 index 000000000000..cf6771cc6a2c --- /dev/null +++ b/templates/global_async.yaml.erb @@ -0,0 +1,18 @@ +async: !ruby/object:Api::Async + operation: !ruby/object:Api::Async::Operation + kind: 'compute#operation' + path: 'name' + base_url: 'projects/{{project}}/global/operations/{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::Async::Result + path: 'targetLink' + status: !ruby/object:Api::Async::Status + path: 'status' + complete: 'DONE' + allowed: + - 'PENDING' + - 'RUNNING' + - 'DONE' + error: !ruby/object:Api::Async::Error + path: 'error/errors' + message: 'message' diff --git a/templates/license.erb b/templates/license.erb new file mode 100644 index 000000000000..4e6ad5fdc038 --- /dev/null +++ b/templates/license.erb @@ -0,0 +1,12 @@ +# Copyright <%= Time.now.year -%> Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/templates/network/base.rb.erb b/templates/network/base.rb.erb new file mode 100644 index 000000000000..30c2bb3fc6c9 --- /dev/null +++ b/templates/network/base.rb.erb @@ -0,0 +1,65 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'net/http' +require 'net/https' + +module Google + module <%= product_ns %> + module Network + # A handler for authenticated network request + class Base + def initialize(link, cred) + @link = link + @cred = cred + end + + def builder + Net::HTTP.const_get(self.class.name.split('::').last) + end + + def send + request = @cred.authorize(builder.new(@link)) + request['User-Agent'] = generate_user_agent + response = transport(request).request(request) + unless ENV['GOOGLE_HTTP_VERBOSE'].nil? + puts ["network(#{request}: [#{response.code}]", + response.body.split("\n").map(&:strip).join(' ')].join(' ') + end + response + end + + def transport(request) + uri = request.uri + puts "network(#{request}: #{uri})" \ + unless ENV['GOOGLE_HTTP_VERBOSE'].nil? + transport = Net::HTTP.new(uri.host, uri.port) + transport.use_ssl = uri.is_a?(URI::HTTPS) + transport.verify_mode = OpenSSL::SSL::VERIFY_PEER + transport.set_debug_output $stderr \ + unless ENV['GOOGLE_HTTP_DEBUG'].nil? + transport + end + + private + +<%= lines(indent(generate_user_agent(product_ns_dir, name), 8)) -%> + end + end + end +end diff --git a/templates/network/delete.rb.erb b/templates/network/delete.rb.erb new file mode 100644 index 000000000000..3132cd834c11 --- /dev/null +++ b/templates/network/delete.rb.erb @@ -0,0 +1,29 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= product_ns_dir -%>/<%= Compile::Libraries::NETWORK -%>/base' + +module Google + module <%= product_ns %> + module Network + # A wrapper class for a Get Request + class Delete < Google::<%= product_ns -%>::Network::Base + end + end + end +end diff --git a/templates/network/delete_spec.rb.erb b/templates/network/delete_spec.rb.erb new file mode 100644 index 000000000000..d8aa16a2fbb7 --- /dev/null +++ b/templates/network/delete_spec.rb.erb @@ -0,0 +1,57 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'spec_helper' + +class TestCred + def authorize(request) + request + end +end + +<% blocker = "Google::#{product_ns}::NetworkBlocker.instance" -%> +describe Google::<%= product_ns -%>::Network::Delete do + let(:credential) { TestCred.new } + let(:uri) { Google::<%= product_ns -%>::NetworkBlocker::ALLOWED_TEST_URI } + + context 'verify proper request' do +<%= + lines(format([ + ["before(:each) { #{blocker}.allow_delete(uri) }"], + [ + 'before(:each) do', + indent("#{blocker}.allow_delete(uri)", 2), + 'end' + ] + ], 4), 1) +-%> + subject { described_class.new(uri, credential).send } + + it { is_expected.to be_a_kind_of(Net::HTTPNoContent) } + it { is_expected.to have_attributes(code: 204) } + end + + context 'failed request' do + before(:each) { <%= blocker -%>.deny(uri) } + + subject { described_class.new(uri, credential).send } + + it { is_expected.to be_a_kind_of(Net::HTTPNotFound) } + it { is_expected.to have_attributes(code: 404) } + end +end diff --git a/templates/network/get.rb.erb b/templates/network/get.rb.erb new file mode 100644 index 000000000000..1d1b97657ce7 --- /dev/null +++ b/templates/network/get.rb.erb @@ -0,0 +1,29 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= product_ns_dir -%>/<%= Compile::Libraries::NETWORK -%>/base' + +module Google + module <%= product_ns %> + module Network + # A wrapper class for a Get Request + class Get < Google::<%= product_ns -%>::Network::Base + end + end + end +end diff --git a/templates/network/get_spec.rb.erb b/templates/network/get_spec.rb.erb new file mode 100644 index 000000000000..648872f1a915 --- /dev/null +++ b/templates/network/get_spec.rb.erb @@ -0,0 +1,57 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'spec_helper' +require 'uri' + +class TestCred + def authorize(request) + request + end +end + +<% blocker = "Google::#{product_ns}::NetworkBlocker.instance" -%> +describe Google::<%= product_ns -%>::Network::Get do + let(:credential) { TestCred.new } + let(:uri) { Google::<%= product_ns -%>::NetworkBlocker::ALLOWED_TEST_URI } + + context 'successful request' do + before(:each) do + <%= blocker -%>.allow_get( + uri, 200, 'application/myfooapp', { field1: 'FOOBAR' }.to_json + ) + end + + subject { described_class.new(uri, credential).send } + + it { is_expected.to be_a_kind_of(Net::HTTPResponse) } + it { is_expected.to have_attributes(body: { field1: 'FOOBAR' }.to_json) } + it { is_expected.to have_attributes(code: 200) } + it { is_expected.to have_attributes(content_type: 'application/myfooapp') } + it { is_expected.to have_attributes(uri: uri) } + end + + context 'failed request' do + before(:each) { <%= blocker -%>.deny(uri) } + + subject { described_class.new(uri, credential).send } + + it { is_expected.to be_a_kind_of(Net::HTTPNotFound) } + it { is_expected.to have_attributes(code: 404) } + end +end diff --git a/templates/network/network_blocker.rb.erb b/templates/network/network_blocker.rb.erb new file mode 100644 index 000000000000..785d694659ec --- /dev/null +++ b/templates/network/network_blocker.rb.erb @@ -0,0 +1,153 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'net/http' +require 'singleton' + +module Google + module <%= product_ns %> + # A helper class to block access to the network during tests, while + # providing a whitelist escape for the + # Google::<%= product_ns -%>::Network::* classes to test themselves, also + # without accessing the network. + class NetworkBlocker + include Singleton + + ALLOWED_TEST_URI = URI.parse('https://www.unreachable-test-host.com/blah') + + attr_reader :allowed_test_hosts + attr_reader :allowed_request + attr_reader :canned_response + + def initialize + @allowed_test_hosts = [ + { host: ALLOWED_TEST_URI.host, port: ALLOWED_TEST_URI.port } + ] + end + + def allow_get(uri, code, type, body) + @allowed_request = { uri: uri } + @canned_response = response(uri, code, type, body) + end + + def allow_delete(uri) + @allowed_request = { uri: uri } + @canned_response = Net::HTTPNoContent.new(1.0, 204, 'No Content') + end + + def allow_post(args) + @allowed_request = { + uri: args[:uri_in], + type: args[:type_in], + body: args[:body_in] + } + @canned_response = response(args[:uri_out], args[:code], + args[:type_out], args[:body_out]) + end + + def allow_put(args) + allow_post(args) # PUT uses same interface as POST + end + + def deny(uri, code = 404) + case code + when 404 + @allowed_request = { uri: uri } + @canned_response = Net::HTTPNotFound.new(1.0, 404, 'Not Found') + else + raise ArgumentError, "Unknown error code #{code}" + end + end + + private + + def response(uri, code, type, body) + response = Net::HTTPOK.new(1.0, code, 'OK') + response.uri = uri + response.content_type = type + response.body = body + response.instance_variable_set(:@read, true) + response + end + end + end +end + +# Monkey patching of core Net::HTTP components to trap and block all network +# access, except to return canned responses when using the magic +# ALLOWED_TEST_URI URL. +module Net + class HTTP + define_method(:initialize) do |*args| + blocker = Google::<%= product_ns -%>::NetworkBlocker.instance + unless blocker.allowed_test_hosts.map { |h| h[:host] }.include?(args[0]) + message = [self, __method__, ':', + 'Network traffic is blocked during tests', ':', + args[0]].join(' ') + if ENV['RSPEC_DEBUG'] + module_dir = File.expand_path('..', File.dirname(__FILE__)) + puts [message, caller.select { |c| c.include?(module_dir) } + .join("\n").gsub(/^/, ' ')].join("\n") + end + raise IOError, message + end + end + + instance_methods.each do |m| + unless %i[get copy delete finish get get2 head head2 lock mkcol move + options patch post post2 propfind proppatch put put2 request + request_get request_head request_post request_put send + send_request start trace unlock].include?(m) + next + end + +<%# TODO(alexstephen): Find a better way to handle this. -%> +<% if self.class != Provider::Puppet -%> + # rubocop:disable Metrics/MethodLength +<% end -%> + define_method(m) do |*args| + request_allowed = true + + blocker = Google::<%= product_ns -%>::NetworkBlocker.instance + if !args.empty? && args[0].is_a?(Net::HTTPGenericRequest) + allow_terms = blocker.allowed_request + allow_terms.each_key do |key| + case key + when :uri + request_allowed &&= args[0].uri == allow_terms[:uri] + when :type + request_allowed &&= args[0].content_type == allow_terms[:type] + when :body + request_allowed &&= args[0].body == allow_terms[:body] + end + end + end +<%# TODO(alexstephen): Find a better way to handle this. -%> +<% if self.class != Provider::Puppet -%> + # rubocop:enable Metrics/MethodLength +<% end -%> + + return blocker.canned_response if request_allowed + + raise IOError, [self, __method__, ':', + 'Network traffic is blocked during tests', ':', + args[0]].join(' ') + end + end + end +end diff --git a/templates/network/network_blocker_spec.rb.erb b/templates/network/network_blocker_spec.rb.erb new file mode 100644 index 000000000000..7f1a5a44b239 --- /dev/null +++ b/templates/network/network_blocker_spec.rb.erb @@ -0,0 +1,156 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'spec_helper' + +TEST_URI = URI.parse('https://google.com') + +describe Google::<%= product_ns -%>::NetworkBlocker do + let(:uri) { described_class::ALLOWED_TEST_URI } + + context '#allow_get' do + before(:each) do + described_class.instance.allow_get(uri, 200, 'text/html', 'hello') + end + + subject { Net::HTTP.get(uri) } + + it { is_expected.to eq 'hello' } + end + + context '#allow_post' do + before(:each) do + described_class.instance.allow_post( + uri_in: uri, type_in: 'text/plain', body_in: 'my input', + code: 200, uri_out: uri, type_out: 'text/html', body_out: '' + ) + end + + subject { Net::HTTP.post_form(uri, q: 'query') } + + it { is_expected.to be_a Net::HTTPOK } + it { is_expected.to have_attributes content_type: 'text/html' } + it { is_expected.to have_attributes code: 200 } + it { is_expected.to have_attributes body: '' } + end + + context '#allow_delete' do + before(:each) do + described_class.instance.allow_delete(uri) + end + + subject do + Net::HTTP.new(uri.host, uri.port).request(Net::HTTP::Delete.new(uri)) + end + + it { is_expected.to be_a Net::HTTPNoContent } + end + + context '#allowed_test_hosts' do + let(:uri) { URI.parse('http://some-other-site.com') } + + before(:each) do + described_class.instance.allow_get(uri, 200, 'text/html', 'hello') + end + + context 'failed without #allowed_test_hosts update' do + subject { -> { Net::HTTP.get(uri) } } + it { is_expected.to raise_error(IOError) } + end + + context '#allowed_test_hosts' do + before(:each) do + described_class.instance.allowed_test_hosts << \ + { host: uri.host, port: uri.port } + end + + subject { -> { Net::HTTP.get(uri) } } + it { is_expected.not_to raise_error } + end + end +end + +describe Net::HTTP do + context '#new' do + subject { -> { described_class.new(TEST_URI) } } + + it { is_expected.to raise_error(IOError, /traffic.*blocked/) } + end + + # Shortcut form for Net::HTTP::Get + context '#get' do + subject { -> { described_class.get(TEST_URI) } } + + it { is_expected.to raise_error(IOError, /traffic.*blocked/) } + end + + # Shortcut form for Net::HTTP::Get + context '#get_response' do + subject { -> { described_class.get_response(TEST_URI) } } + + it { is_expected.to raise_error(IOError, /traffic.*blocked/) } + end + + # Shortcut form for Net::HTTP::Post + context '#post_form' do + subject do + lambda do + described_class.post_form(TEST_URI, q: 'My query', per_page: 50) + end + end + + it { is_expected.to raise_error(IOError, /traffic.*blocked/) } + end +end + +context Net::HTTP::Get do + subject do + lambda do + http = Net::HTTP.new(TEST_URI.host, TEST_URI.port) + http.request(Net::HTTP::Get.new(TEST_URI.request_uri)) + end + end + + it { is_expected.to raise_error(IOError, /traffic.*blocked/) } +end + +context Net::HTTP::Post do + subject do + lambda do + http = Net::HTTP.new(TEST_URI.host, TEST_URI.port) + + request = Net::HTTP::Post.new(TEST_URI.request_uri) + request.set_form_data(q: 'My query', per_page: 50) + + http.request(request) + end + end + + it { is_expected.to raise_error(IOError, /traffic.*blocked/) } +end + +context Net::HTTP::Delete do + subject do + lambda do + http = Net::HTTP.new(TEST_URI.host, TEST_URI.port) + http.request(Net::HTTP::Delete.new(TEST_URI.request_uri)) + end + end + + it { is_expected.to raise_error(IOError, /traffic.*blocked/) } +end diff --git a/templates/network/patch.rb.erb b/templates/network/patch.rb.erb new file mode 100644 index 000000000000..71b76fa18e2d --- /dev/null +++ b/templates/network/patch.rb.erb @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= product_ns_dir -%>/<%= Compile::Libraries::NETWORK -%>/base' + +module Google + module <%= product_ns %> + module Network + # A wrapper class for a PATCH Request + class Patch < Google::<%= product_ns -%>::Network::Base + def initialize(link, cred, type, body) + super(link, cred) + @type = type + @body = body + end + + def transport(request) + request.content_type = @type + request.body = @body + puts "network(#{request}: body(#{@body}))" \ + unless ENV['GOOGLE_HTTP_VERBOSE'].nil? + super(request) + end + end + end + end +end diff --git a/templates/network/patch_spec.rb.erb b/templates/network/patch_spec.rb.erb new file mode 100644 index 000000000000..d03101ba0f52 --- /dev/null +++ b/templates/network/patch_spec.rb.erb @@ -0,0 +1,63 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'spec_helper' +require 'uri' + +class TestCred + def authorize(request) + request + end +end + +<% blocker = "Google::#{product_ns}::NetworkBlocker.instance" -%> +describe Google::<%= product_ns -%>::Network::Patch do + let(:credential) { TestCred.new } + let(:uri_in) { Google::<%= product_ns -%>::NetworkBlocker::ALLOWED_TEST_URI } + let(:uri_out) { URI.parse('https://somewhere.else.com/some/path') } + let(:type_in) { 'application/myapp-request' } + let(:type_out) { 'application/myapp-response' } + let(:body_in) { { 'test1' => 'test' }.to_json } + let(:body_out) { { field1: 'WORKS' }.to_json } + + context 'successful request' do + before(:each) do + <%= blocker -%>.allow_put( + uri_in: uri_in, type_in: type_in, body_in: body_in, + code: 200, uri_out: uri_out, type_out: type_out, body_out: body_out + ) + end + + subject { described_class.new(uri_in, credential, type_in, body_in).send } + + it { is_expected.to be_a_kind_of(Net::HTTPOK) } + it { is_expected.to have_attributes(code: 200) } + it { is_expected.to have_attributes(uri: uri_out) } + it { is_expected.to have_attributes(content_type: type_out) } + it { is_expected.to have_attributes(body: body_out) } + end + + context 'failed request' do + before(:each) { <%= blocker -%>.deny(uri_in) } + + subject { described_class.new(uri_in, credential, type_in, body_in).send } + + it { is_expected.to be_a_kind_of(Net::HTTPNotFound) } + it { is_expected.to have_attributes(code: 404) } + end +end diff --git a/templates/network/post.rb.erb b/templates/network/post.rb.erb new file mode 100644 index 000000000000..c7c1dbbbb418 --- /dev/null +++ b/templates/network/post.rb.erb @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= product_ns_dir -%>/<%= Compile::Libraries::NETWORK -%>/base' + +module Google + module <%= product_ns %> + module Network + # A wrapper class for a Post Request + class Post < Google::<%= product_ns -%>::Network::Base + def initialize(link, cred, type, body) + super(link, cred) + @type = type + @body = body + end + + def transport(request) + request.content_type = @type + request.body = @body + puts "network(#{request}: body(#{@body}))" \ + unless ENV['GOOGLE_HTTP_VERBOSE'].nil? + super(request) + end + end + end + end +end diff --git a/templates/network/post_spec.rb.erb b/templates/network/post_spec.rb.erb new file mode 100644 index 000000000000..24ac3f5717a5 --- /dev/null +++ b/templates/network/post_spec.rb.erb @@ -0,0 +1,63 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'spec_helper' +require 'uri' + +class TestCred + def authorize(request) + request + end +end + +<% blocker = "Google::#{product_ns}::NetworkBlocker.instance" -%> +describe Google::<%= product_ns -%>::Network::Post do + let(:credential) { TestCred.new } + let(:uri_in) { Google::<%= product_ns -%>::NetworkBlocker::ALLOWED_TEST_URI } + let(:uri_out) { URI.parse('https://somewhere.else.com/some/path') } + let(:type_in) { 'application/myapp-request' } + let(:type_out) { 'application/myapp-response' } + let(:body_in) { { 'test1' => 'test' }.to_json } + let(:body_out) { { field1: 'WORKS' }.to_json } + + context 'successful request' do + before(:each) do + <%= blocker -%>.allow_post( + uri_in: uri_in, type_in: type_in, body_in: body_in, + code: 200, uri_out: uri_out, type_out: type_out, body_out: body_out + ) + end + + subject { described_class.new(uri_in, credential, type_in, body_in).send } + + it { is_expected.to be_a_kind_of(Net::HTTPOK) } + it { is_expected.to have_attributes(code: 200) } + it { is_expected.to have_attributes(uri: uri_out) } + it { is_expected.to have_attributes(content_type: type_out) } + it { is_expected.to have_attributes(body: body_out) } + end + + context 'failed request' do + before(:each) { <%= blocker -%>.deny(uri_in) } + + subject { described_class.new(uri_in, credential, type_in, body_in).send } + + it { is_expected.to be_a_kind_of(Net::HTTPNotFound) } + it { is_expected.to have_attributes(code: 404) } + end +end diff --git a/templates/network/put.rb.erb b/templates/network/put.rb.erb new file mode 100644 index 000000000000..494dea4ca3a7 --- /dev/null +++ b/templates/network/put.rb.erb @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= product_ns_dir -%>/<%= Compile::Libraries::NETWORK -%>/base' + +module Google + module <%= product_ns %> + module Network + # A wrapper class for a PUT Request + class Put < Google::<%= product_ns -%>::Network::Base + def initialize(link, cred, type, body) + super(link, cred) + @type = type + @body = body + end + + def transport(request) + request.content_type = @type + request.body = @body + puts "network(#{request}: body(#{@body}))" \ + unless ENV['GOOGLE_HTTP_VERBOSE'].nil? + super(request) + end + end + end + end +end diff --git a/templates/network/put_spec.rb.erb b/templates/network/put_spec.rb.erb new file mode 100644 index 000000000000..cb05f8abd0fb --- /dev/null +++ b/templates/network/put_spec.rb.erb @@ -0,0 +1,63 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'spec_helper' +require 'uri' + +class TestCred + def authorize(request) + request + end +end + +<% blocker = "Google::#{product_ns}::NetworkBlocker.instance" -%> +describe Google::<%= product_ns -%>::Network::Put do + let(:credential) { TestCred.new } + let(:uri_in) { Google::<%= product_ns -%>::NetworkBlocker::ALLOWED_TEST_URI } + let(:uri_out) { URI.parse('https://somewhere.else.com/some/path') } + let(:type_in) { 'application/myapp-request' } + let(:type_out) { 'application/myapp-response' } + let(:body_in) { { 'test1' => 'test' }.to_json } + let(:body_out) { { field1: 'WORKS' }.to_json } + + context 'successful request' do + before(:each) do + <%= blocker -%>.allow_put( + uri_in: uri_in, type_in: type_in, body_in: body_in, + code: 200, uri_out: uri_out, type_out: type_out, body_out: body_out + ) + end + + subject { described_class.new(uri_in, credential, type_in, body_in).send } + + it { is_expected.to be_a_kind_of(Net::HTTPOK) } + it { is_expected.to have_attributes(code: 200) } + it { is_expected.to have_attributes(uri: uri_out) } + it { is_expected.to have_attributes(content_type: type_out) } + it { is_expected.to have_attributes(body: body_out) } + end + + context 'failed request' do + before(:each) { <%= blocker -%>.deny(uri_in) } + + subject { described_class.new(uri_in, credential, type_in, body_in).send } + + it { is_expected.to be_a_kind_of(Net::HTTPNotFound) } + it { is_expected.to have_attributes(code: 404) } + end +end diff --git a/templates/network_mocks.erb b/templates/network_mocks.erb new file mode 100644 index 000000000000..351ea0c901f3 --- /dev/null +++ b/templates/network_mocks.erb @@ -0,0 +1,189 @@ +<% if !true?(Google::HashUtils.navigate(tests, %w[expectations custom + get success])) -%> +def expect_network_get_success(id, data = {}) + id_data = data.fetch(:name, '').include?('title') ? 'title' : 'name' + body = load_network_result("success#{id}~#{id_data}.yaml").to_json + + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + + debug_network "!! GET #{self_link(uri_data(id).merge(data))}" + expect(Google::<%= product_ns -%>::Network::Get).to receive(:new) + .with(self_link(uri_data(id).merge(data)), + instance_of(Google::FakeAuthorization)) do |args| + debug_network ">> GET #{args}" + request + end +end + +def http_success(body) + response = Net::HTTPOK.new(1.0, 200, 'OK') + response.body = body + response.instance_variable_set(:@read, true) + response +end +<% end -%> +<% if object.async -%> + +def expect_network_get_async(id, data = {}) +<% if object.self_link_query.nil? -%> +<% if object.kind? -%> + body = { kind: '<%= object.kind -%>' }.to_json +<% else # object.kind? -%> + body = {}.to_json +<% end # object.kind? -%> +<% else # object.self_link_query.nil? -%> +<% if object.self_link_query.kind.nil? -%> + body = { }.to_json +<% else -%> + body = { kind: '<%= object.self_link_query.kind -%>' }.to_json +<% end # object.self_link_query.kind.nil? -%> +<% end # object.self_link_query.nil? -%> + + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + + debug_network "!! #{self_link(uri_data(id).merge(data))}" + expect(Google::<%= product_ns -%>::Network::Get).to receive(:new) + .with(self_link(uri_data(id).merge(data)), + instance_of(Google::FakeAuthorization)) do |args| + debug_network ">> GET #{args}" + request + end +end +<% end -%> +<% if !true?(Google::HashUtils.navigate(tests, %w[expectations custom + get failed])) -%> + +def expect_network_get_failed(id, data = {}) + request = double('request') + allow(request).to receive(:send).and_return(http_failed_object_missing) + + debug_network "!! #{self_link(uri_data(id).merge(data))}" + expect(Google::<%= product_ns -%>::Network::Get).to receive(:new) + .with(self_link(uri_data(id).merge(data)), + instance_of(Google::FakeAuthorization)) do |args| + debug_network ">> GET [failed] #{args}" + request + end +end + +def http_failed_object_missing + Net::HTTPNotFound.new(1.0, 404, 'Not Found') +end +<% end -%> +<% unless object.virtual -%> +<% if !true?(Google::HashUtils.navigate(tests, %w[expectations custom + create])) -%> + +<%= + lines(emit_rubocop(binding, :function, 'expect_network_create', :disabled)) +-%> +def expect_network_create(id, expected_body, data = {}) + merged_uri = uri_data(id).merge(data) +<% unless object.async.nil? -%> +<% if object.async.operation.kind.nil? -%> + body = { +<% else # object.async.operation.kind.nil? -%> + body = { kind: '<%= object.async.operation.kind -%>', +<% end # object.async.operation.kind.nil? -%> +<% status = object.async.status.complete -%> + status: '<%= status -%>', targetLink: self_link(merged_uri) }.to_json +<% else -%> +<% if object.kind? -%> + body = { kind: '<%= object.kind -%>' }.to_json +<% else # object.kind? -%> + body = {}.to_json +<% end # object.kind? -%> +<% end -%> + +<% unless object.all_resourcerefs.empty? -%> + # Remove refs that are also part of the body + expected_body = Hash[expected_body.map do |k, v| + [k.is_a?(Symbol) ? k.id2name : k, v] + end] + +<% end # all_resourcerefs.empty? -%> +<% if object.encoder? -%> + # Encode the object to conform with the API +<% encoder = object.transport.encoder -%> + expected_body = <%= encoder -%>(expected_body) +<% end # object.encoder? -%> + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + +<% if object.create_verb.nil? || object.create_verb == :POST -%> + debug_network "!! POST #{collection(merged_uri)}" + expect(Google::<%= product_ns -%>::Network::Post).to receive(:new) + .with(collection(merged_uri), instance_of(Google::FakeAuthorization), + 'application/json', expected_body.to_json) do |args| + debug_network ">> POST #{args} = body(#{body})" +<% elsif object.create_verb == :PUT -%> + debug_network "!! PUT #{self_link(merged_uri)}" + expect(Google::<%= product_ns -%>::Network::Put).to receive(:new) + .with(self_link(merged_uri), instance_of(Google::FakeAuthorization), + 'application/json', expected_body.to_json) do |args| + debug_network ">> PUT #{args} = body(#{body})" +<% end -%> + request + end +end +<%= + lines(emit_rubocop(binding, :function, 'expect_network_create', :enabled)) +-%> +<% end -%> +<% if !true?(Google::HashUtils.navigate(tests, %w[expectations custom + delete])) -%> + +def expect_network_delete(id, name = nil, data = {}) + delete_data = uri_data(id).merge(data) + delete_data[:name] = name unless name.nil? +<% unless object.async.nil? -%> +<% if object.async.operation.kind.nil? -%> + body = { +<% else # object.async.operation.kind.nil? -%> + body = { kind: '<%= object.async.operation.kind -%>', +<% end # object.async.operation.kind.nil? -%> + status: '<%= object.async.status.complete -%>', + targetLink: self_link(delete_data) }.to_json +<% else -%> +<% if object.kind? -%> + body = { kind: '<%= object.kind -%>' }.to_json +<% else # object.kind? -%> + body = {}.to_json +<% end # object.kind? -%> +<% end -%> + + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + + debug_network "!! DELETE #{self_link(delete_data)}" + expect(Google::<%= product_ns -%>::Network::Delete).to receive(:new) + .with(self_link(delete_data), + instance_of(Google::FakeAuthorization)) do |args| + debug_network ">> DELETE #{args}" + request + end +end +<% end -%> +<% end -%> + +def load_network_result(file) + results = File.join(File.dirname(__FILE__), 'data', 'network', + '<%= object.out_name -%>', file) + debug("Loading result file: #{results}") + raise "Network result data file #{results}" unless File.exist?(results) + data = YAML.safe_load(File.read(results)) + raise "Invalid network results #{results}" unless data.class <= Hash + data +end + +<%= lines(compile('templates/resourceref_mocks.erb'), 1) -%> +def debug(message) + puts(message) if ENV['RSPEC_DEBUG'] +end + +def debug_network(message) + puts("Network #{message}") \ + if ENV['RSPEC_DEBUG'] || ENV['RSPEC_HTTP_VERBOSE'] +end diff --git a/templates/network_spec.yaml.erb b/templates/network_spec.yaml.erb new file mode 100644 index 000000000000..325674461904 --- /dev/null +++ b/templates/network_spec.yaml.erb @@ -0,0 +1,19 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= test_data.to_yaml -%> diff --git a/templates/provider_helpers.erb b/templates/provider_helpers.erb new file mode 100644 index 000000000000..ae1c3f4b6803 --- /dev/null +++ b/templates/provider_helpers.erb @@ -0,0 +1,6 @@ +<%= + lines(Google::HashUtils.navigate(config, + %w[provider_helpers include], []).map do |f| + lines(compile(f)) + end.join("\n")) +-%> diff --git a/templates/puppet/.rubocop.yml b/templates/puppet/.rubocop.yml new file mode 100644 index 000000000000..10abee3b2420 --- /dev/null +++ b/templates/puppet/.rubocop.yml @@ -0,0 +1,17 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +inherit_from: ../../.rubocop.yml + +Style/FileName: + Enabled: false diff --git a/templates/puppet/Gemfile b/templates/puppet/Gemfile new file mode 100644 index 000000000000..76b86ce5a06f --- /dev/null +++ b/templates/puppet/Gemfile @@ -0,0 +1,39 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +source 'https://rubygems.org' +group :test do + gem 'metadata-json-lint' + gem 'puppet', ENV['PUPPET_GEM_VERSION'] || '>= 4.2.0' + gem 'puppet-lint' + gem 'puppet-lint-unquoted_string-check' + gem 'puppet-syntax' + gem 'puppetlabs_spec_helper' + gem 'rake', '~> 10.0' + gem 'rspec' + gem 'rspec-mocks' + gem 'rspec-puppet' + # TODO(alexstephen): Monitor rubocop upsteam changes + # https://github.com/bbatsov/rubocop/pull/4329 + # Change will allow rubocop to use --ignore-parent-exclusion flag + # Current rubocop upstream will not check Chef files because of + # AllCops/Exclude + gem 'rubocop', git: 'https://github.com/nelsonjr/rubocop.git', + branch: 'feature/ignore-parent-exclude' + gem 'simplecov' +end diff --git a/templates/puppet/Gemfile.lock b/templates/puppet/Gemfile.lock new file mode 100644 index 000000000000..5c2c4064525b --- /dev/null +++ b/templates/puppet/Gemfile.lock @@ -0,0 +1,108 @@ +GIT + remote: https://github.com/nelsonjr/rubocop.git + revision: 178b3150b7610195c327783575177520316e1985 + branch: feature/ignore-parent-exclude + specs: + rubocop (0.50.0) + parallel (~> 1.10) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 2.2.2, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.3.0) + diff-lcs (1.3) + docile (1.1.5) + facter (2.4.6) + fast_gettext (1.1.0) + gettext (3.2.2) + locale (>= 2.0.5) + text (>= 1.3.0) + gettext-setup (0.18) + fast_gettext (~> 1.1.0) + gettext (>= 3.0.2) + locale + hiera (3.3.1) + json (2.0.3) + json_pure (1.8.6) + locale (2.1.2) + metaclass (0.0.4) + metadata-json-lint (1.1.0) + json + semantic_puppet (>= 0.1.2, < 2.0.0) + spdx-licenses (~> 1.0) + mocha (1.2.1) + metaclass (~> 0.0.1) + parallel (1.12.0) + parser (2.4.0.0) + ast (~> 2.2) + powerpack (0.1.1) + puppet (4.10.0) + facter (> 2.0, < 4) + gettext-setup (>= 0.10, < 1) + hiera (>= 2.0, < 4) + json_pure (~> 1.8) + locale (~> 2.1) + puppet-lint (2.1.1) + puppet-lint-unquoted_string-check (0.3.0) + puppet-lint (>= 1.0, < 3.0) + puppet-syntax (2.4.0) + rake + puppetlabs_spec_helper (2.1.0) + mocha (~> 1.0) + puppet-lint (~> 2.0) + puppet-syntax (~> 2.0) + rspec-puppet (~> 2.0) + rainbow (2.2.2) + rake + rake (10.5.0) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-puppet (2.5.0) + rspec + rspec-support (3.5.0) + ruby-progressbar (1.9.0) + semantic_puppet (0.1.4) + gettext-setup (>= 0.3) + simplecov (0.14.1) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + spdx-licenses (1.1.0) + text (1.3.1) + unicode-display_width (1.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + metadata-json-lint + puppet (>= 4.2.0) + puppet-lint + puppet-lint-unquoted_string-check + puppet-syntax + puppetlabs_spec_helper + rake (~> 10.0) + rspec + rspec-mocks + rspec-puppet + rubocop! + simplecov + +BUNDLED WITH + 1.15.3 diff --git a/templates/puppet/README.md.erb b/templates/puppet/README.md.erb new file mode 100644 index 000000000000..953b4847c482 --- /dev/null +++ b/templates/puppet/README.md.erb @@ -0,0 +1,333 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% + require 'provider/puppet/outline' + DEPTH = 5 + outline = Provider::PuppetOutline.new(self) + # TODO(alexstephen): Move this function to Puppet provider. + # Builds the properties for a nested object of any depth + # This returns an arrays of strings that represent Markdown formatted + # properties for the nested object and all nested objects beneath it + # Requires: + # prop: A property of type nested object. + # current_path: A string representing all layers above this current property. + # This string will usually be the output names of all + # properties above the current property joined by '/' + # (ex. first_level/second_level) or an array denoted by [] + # (ex. array_of_nested_props[]) + def build_nested_object(prop, current_path) + object_lines = [] + prop.properties.each do |nested_prop| + next_path = "#{current_path}/#{nested_prop.out_name}" + object_lines << lines(['#' * DEPTH, next_path].join(' ')) + # TODO(alexstephen): Fix formatting. Required/Output only should be tabbed + # in two spaces + only one space in between required/output + description + object_lines << 'Required.' if nested_prop.required + object_lines << 'Output only.' if nested_prop.output + object_lines << lines(wrap_field(nested_prop.description, 0), 1) + + if nested_prop.is_a? Api::Type::NestedObject + object_lines.concat(build_nested_object(nested_prop, next_path)) + elsif nested_prop.is_a? Api::Type::Array and + nested_prop.item_type.is_a? Api::Type::NestedObject + object_lines.concat(build_nested_object(nested_prop.item_type, + "#{next_path}[]")) + end + end + object_lines + end +-%> +# <%= product.name %> Puppet Module + +<%= lines(['[![Puppet Forge](', + "http://img.shields.io/puppetforge/v/google/#{@api.prefix}.svg", + ")](https://forge.puppetlabs.com/google/#{@api.prefix})"].join) -%> + +#### Table of Contents + +1. [Module Description - What the module does and why it is useful]( + #module-description) +2. [Setup - The basics of getting started with <%= product.name -%>](#setup) +3. [Usage - Configuration options and additional functionality](#usage) +4. [Reference - An under-the-hood peek at what the module is doing and how]( + #reference) +<% unless @config.functions.nil? && @config.bolt_tasks.nil? -%> + - [Classes](#classes) +<% end -%> +<% unless @config.functions.nil? -%> + - [Functions](#functions) +<% end -%> +<% unless @config.bolt_tasks.nil? -%> + - [Bolt Tasks](#bolt-tasks) +<% end -%> +5. [Limitations - OS compatibility, etc.](#limitations) +6. [Development - Guide for contributing to the module](#development) + +## Module Description + +This Puppet module manages the resource of <%= @api.name -%>. +You can manage its resources using standard Puppet DSL and the module will, +under the hood, ensure the state described will be reflected in the Google +Cloud Platform resources. + +## Setup + +To install this module on your Puppet Master (or Puppet Client/Agent), use the +Puppet module installer: + + puppet module install google-<%= @api.prefix %> + +Optionally you can install support to _all_ Google Cloud Platform products at +once by installing our "bundle" [`google-cloud`][bundle-forge] module: + + puppet module install google-cloud + +## Usage + +### Credentials + +All Google Cloud Platform modules use an unified authentication mechanism, +provided by the [`google-gauth`][] module. Don't worry, it is automatically +installed when you install this module. + +```puppet +<%= compile 'templates/puppet/examples~credential.pp.erb' -%> +``` + +Please refer to the [`google-gauth`][] module for further requirements, i.e. +required gems. + +### Examples + +<% objects = product.objects.select { |o| !o.exclude } -%> +<% objects.each do |object| -%> +#### `<%= object.out_name -%>` + +```puppet +<%= compile ["products/#{@api.prefix[1..-1]}/files/examples", + "#{Google::StringUtils.underscore(object.name)}.pp"].join('~') %> +``` + +<% end # objects.each -%> + +### Classes + +#### Public classes + +<% objects.each do |object| -%> +* [`<%= object.out_name -%>`][]: +<%= lines(indent(wrap_field(object.description, 6), 2)) -%> +<% end -%> + +<% if objects.any? { |o| o.all_user_properties.select(&:output).any? } -%> +### About output only properties + +Some fields are output-only. It means you cannot set them because they are +provided by the Google Cloud Platform. Yet they are still useful to ensure the +value the API is assigning (or has assigned in the past) is still the value you +expect. + +For example in a DNS the name servers are assigned by the Google Cloud DNS +service. Checking these values once created is useful to make sure your upstream +and/or root DNS masters are in sync. Or if you decide to use the object ID, +e.g. the VM unique ID, for billing purposes. If the VM gets deleted and +recreated it will have a different ID, despite the name being the same. If that +detail is important to you you can verify that the ID of the object did not +change by asserting it in the manifest. + +<% end # objects...:output...any? -%> +### Parameters + +<% objects.each do |object| -%> +#### `<%= object.out_name -%>` + +<%= object.description %> + +#### Example + +```puppet +<%= compile ["products/#{@api.prefix[1..-1]}/files/examples", + "#{Google::StringUtils.underscore(object.name)}.pp"].join('~') %> +``` + +#### Reference + +<% outline = Provider::PuppetOutline.new(self) -%> +```puppet +<%= lines(outline.generate(object)) -%> +``` + +<% object.all_user_properties.reject(&:output).each do |property| -%> +##### `<%= property.out_name -%>` + +<%= 'Required.' if property.required -%> +<%= lines(wrap_field(property.description, 0), 1) -%> +<% if property.is_a? Api::Type::NestedObject -%> +<%= build_nested_object(property, property.out_name).join -%> +<% elsif property.is_a? Api::Type::Array and \ + property.item_type.is_a? Api::Type::NestedObject -%> +<%= build_nested_object(property.item_type, "#{property.out_name}[]").join -%> +<% elsif property.is_a? Api::Type::Array and \ + property.item_type.is_a? Api::Type::Array + raise "Array of arrays. Not supported." +-%> +<% end # property.is_a? Api::Type::NestedObject -%> +<% end # object.all_user_properties.each -%> + +<% output_only = object.all_user_properties.select(&:output) -%> +<% unless output_only.empty? -%> +##### Output-only properties + +<% output_only.each do |property| -%> +* `<%= property.out_name -%>`: <%= 'Output only.' if property.output %> +<%= lines(wrap_field(property.description, 0), 1) -%> +<% if property.is_a? Api::Type::NestedObject -%> +<%= build_nested_object(property, property.out_name).join -%> +<% elsif property.is_a? Api::Type::Array and \ + property.item_type.is_a? Api::Type::NestedObject -%> +<%= build_nested_object(property.item_type, "#{property.out_name}[]").join -%> +<% elsif property.is_a? Api::Type::Array and \ + property.item_type.is_a? Api::Type::Array + raise "Array of arrays. Not supported." +-%> +<% end # property.is_a? Api::Type::NestedObject -%> +<% end # output_only.each -%> +<% end # output_only.empty? -%> +<% end # objects.each -%> + +<% unless @config.functions.nil? -%> +### Functions + +<% @config.functions.each do |fn| -%> + +#### `<%= fn.name -%>` + +<%= lines(wrap_field(fn.description, 0)) -%> + +##### Arguments + +<% fn.arguments.each do |arg| -%> + - `<%= arg.name -%>`: +<%= lines(indent(wrap_field(arg.description, 6), 2), 1) -%> +<% end # fn.arguments.each -%> +<% unless fn.examples.nil? || fn.examples.empty? -%> +##### Examples + +<%= lines(fn.examples.map do |eg| + lines(['```puppet', expand_function_vars(fn, eg), '```']) + end, 1) -%> +<% end # fn.examples.nil? || fn.examples.empty? -%> +<% unless fn.notes.nil? -%> +##### Notes + +<%= lines(wrap_field(expand_function_vars(fn, fn.notes), 0), 1) -%> +<% end # fn.examples.nil? || fn.examples.empty? -%> +<% end # @config.functions.each -%> + +<% end # config.functions.nil? -%> +<% unless @config.bolt_tasks.nil? -%> +### Bolt Tasks + +<% @config.bolt_tasks.each do |task| -%> + +#### `<%= task.target_file -%>` + +<%= lines(wrap_field(task.description, 0)) -%> + +<% if task.input == :stdin -%> +This task takes inputs as JSON from standard input. +<% else # if task.input == :stdin -%> +<% raise "Documentation not supported for '#{task.input}' type" -%> +<% end # if task.input == :stdin -%> + +##### Arguments + +<% task.arguments.each do |arg| -%> + - `<%= arg.name -%>`: +<%= lines(indent(wrap_field(arg.description_display, 6), 2), 1) -%> +<% end # task.arguments.each -%> +<% end # @config.bolt_tasks.each -%> + +<% end # config.bolt_tasks.nil? -%> +## Limitations + +This module has been tested on: + +<%= lines(manifest.operating_systems.map do |os| + ['* ', os.name, ' ', os.versions.join(', ')].join + end.join("\n")) -%> + +Testing on other platforms has been minimal and cannot be guaranteed. + +## Development + +### Automatically Generated Files + +Some files in this package are automatically generated by <%= compiler -%>. + +We use a code compiler to produce this module in order to avoid repetitive tasks +and improve code quality. This means all Google Cloud Platform Puppet modules +use the same underlying authentication, logic, test generation, style checks, +etc. + +Note: Currently `<%= compiler -%>` is not yet generally available, but it will +be made open source soon. Stay tuned. Please learn more about the way to change +autogenerated files by reading the [CONTRIBUTING.md][] file. + +### Contributing + +Contributions to this library are always welcome and highly encouraged. + +See [CONTRIBUTING.md][] for more information on how to get +started. + +### Running tests + +This project contains tests for [rspec][], [rspec-puppet][] and [rubocop][] to +verify functionality. For detailed information on using these tools, please see +their respective documentation. + +#### Testing quickstart: Ruby > 2.0.0 + +<%= compile 'templates/puppet/run-tests-steps.erb' -%> + +#### Debugging Tests + +In case you need to debug tests in this module you can set the following +variables to increase verbose output: + +Variable | Side Effect +------------------------|--------------------------------------------------- +`PUPPET_HTTP_VERBOSE=1` | Prints network access information by Puppet provier. +`PUPPET_HTTP_DEBUG=1` | Prints the payload of network calls being made. +`GOOGLE_HTTP_VERBOSE=1` | Prints debug related to the network calls being made. +`GOOGLE_HTTP_DEBUG=1` | Prints the payload of network calls being made. + +During test runs (using [rspec][]) you can also set: + +Variable | Side Effect +------------------------|--------------------------------------------------- +`RSPEC_DEBUG=1` | Prints debug related to the tests being run. +`RSPEC_HTTP_VERBOSE=1` | Prints network expectations and access. + +[CONTRIBUTING.md]: CONTRIBUTING.md +[bundle-forge]: https://forge.puppet.com/google/cloud +[`google-gauth`]: https://github.com/GoogleCloudPlatform/puppet-google-auth +[rspec]: http://rspec.info/ +[rspec-puppet]: http://rspec-puppet.com/ +[rubocop]: https://rubocop.readthedocs.io/en/latest/ +<% objects.each do |object| -%> +[`<%= object.out_name -%>`]: #<%= object.out_name %> +<% end -%> diff --git a/templates/puppet/bolt~README.md.erb b/templates/puppet/bolt~README.md.erb new file mode 100644 index 000000000000..65ca9c4d60fa --- /dev/null +++ b/templates/puppet/bolt~README.md.erb @@ -0,0 +1,33 @@ +# Puppet Bolt Tasks + +This folder contains tasks for [Puppet Bolt][puppet-bolt] that automate +<%= @api.name -%> operations. + +This module contains the following tasks: + +<% @config.bolt_tasks.each do |task| -%> +- `<%= task.target_file -%>`: +<%= lines(wrap_field(task.description, 3)) -%> +<% end # @config.bolt_tasks.each -%> + +## Debugging Tasks + +In case you need to debug tests in this module you can set the following +variables to increase verbose output: + +Variable | Side Effect +------------------------|--------------------------------------------------- +`GOOGLE_HTTP_VERBOSE=1` | Prints debug related to the network calls being made. +`GOOGLE_HTTP_DEBUG=1` | Prints the payload of network calls being made. + +During execution of Puppet based tasks you can also set: + +Variable | Side Effect +------------------------|--------------------------------------------------- +`PUPPET_HTTP_VERBOSE=1` | Prints network access information by Puppet provider. +`PUPPET_HTTP_DEBUG=1` | Prints the payload of network calls being made. +`BOLT_VERBOSE=1` | Run puppet with --verbose and outputs in case of error +`BOLT_DEBUG=1` | Run puppet with --debug and outputs in case of error + + +[puppet-bolt]: https://puppet.com/docs/bolt/ diff --git a/templates/puppet/bolt~task.json.erb b/templates/puppet/bolt~task.json.erb new file mode 100644 index 000000000000..62793ec74924 --- /dev/null +++ b/templates/puppet/bolt~task.json.erb @@ -0,0 +1,33 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +{ + "description": "<%= task.description_display -%>", + "supports_noop": false, + "input_method": "<%= task.input.id2name -%>", + "parameters": { +<%= + lines(indent_list(task.arguments.map do |arg| + [ + "\"#{arg.name}\": {", + indent([ + "\"description\": \"#{arg.description_display}\",", + "\"type\": \"#{arg.type_metadata(self)}\"" + ], 2), + '}' + ].join("\n") + end, 4)) +-%> + } +} diff --git a/templates/puppet/bolt~task.pp.erb b/templates/puppet/bolt~task.pp.erb new file mode 100644 index 000000000000..f3c4be36ccdc --- /dev/null +++ b/templates/puppet/bolt~task.pp.erb @@ -0,0 +1,128 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +#!/bin/bash +<%= compile 'templates/license.erb' -%> + +declare -r puppet='/opt/puppetlabs/bin/puppet' +declare -r ruby='/opt/puppetlabs/puppet/bin/ruby' + +declare -r parser=$(cat < e + puts({ status: 'failure', error: e.message }.to_json) +end +EOF +) + +declare -r input="$(${ruby} -e "${parser}")" +declare -r run_log="$(mktemp /tmp/bolt-run-XXXXXX)" + +if [[ $input =~ status.*failure ]]; then + echo "${input}" + exit 1 +fi + +declare puppet_args +[[ $BOLT_VERBOSE -ne 1 ]] || puppet_args="${puppet_args} --verbose" +[[ $BOLT_DEBUG -ne 1 ]] || puppet_args="${puppet_args} --debug" +readonly puppet_args + +trap "rm '${run_log}'" TERM EXIT + +$puppet apply ${puppet_args} 1>${run_log} 2>&1 < +# +# Command line arguments: JSON object from STDIN with the following fields: +# +<% task.arguments.each do |arg| -%> +# - <%= arg.name -%>: <%= arg.description_display %> +<% end # task.arguments.each -%> + +# Load parameters provided by task executor +\$params = gcompute_task_load_params('${input}') + +# Validate and parse user input parameters +<%= + lines(task.arguments.map do |arg| + f_name = arg.name == 'name' ? '_name' : arg.name + if arg.default? + format( + [ + [ + (wrap_field(arg.comment, 4).split("\n").map { |c| "# #{c.strip}" } \ + unless arg.comment.nil?), + ["\\$#{f_name} = #{@api.prefix}_task_validate_param(", + ['\\$params', quote_string(arg.name), + quote_string(arg.default.code.to_s.strip)].join(', '), + ')'].join + ].compact, + [ + (wrap_field(arg.comment, 4).split("\n").map { |c| "# #{c.strip}" } \ + unless arg.comment.nil?), + "\\$#{f_name} = #{@api.prefix}_task_validate_param(", + indent( + ['\\$params', quote_string(arg.name), + quote_string(arg.default.code.to_s.strip)].join(', '), 2), + ')' + ].compact + ] + ) + else + format( + [ + [ + (wrap_field(arg.comment, 4).split("\n").map { |c| "# #{c.strip}" } \ + unless arg.comment.nil?), + ["\\$#{f_name} = ", + "#{@api.prefix}_task_validate_param(\\$params, '#{arg.name}', ", + quote_string(Provider::Puppet::BOLT_UNDEF_MAGIC), ')'].join + ].compact, + [ + (wrap_field(arg.comment, 4).split("\n").map { |c| "# #{c.strip}" } \ + unless arg.comment.nil?), + "\\$#{f_name} = #{@api.prefix}_task_validate_param(", + indent([ + "\\$params", quote_string(arg.name), + quote_string(Provider::Puppet::BOLT_UNDEF_MAGIC) + ].join(', '), 2), + ')' + ].compact + ] + ) + end + end) +-%> +<%= + lines(compile(task.manifest).gsub('$', '\\$').gsub('\${input}', '${input}')) +-%> +EOF + +declare -r success_line=$(grep 'Notice:.*task:success.*define.*as' "${run_log}") +if [[ ! -z ${success_line} ]]; then +<%= lines(indent(task.code, 2)) -%> + exit 0 +else + echo "{ \"status\": \"failure\" }" + [[ $BOLT_VERBOSE -ne 1 ]] || cat "${run_log}" + exit 1 +fi diff --git a/templates/puppet/bolt~task.rb.erb b/templates/puppet/bolt~task.rb.erb new file mode 100644 index 000000000000..f23e2fd35e68 --- /dev/null +++ b/templates/puppet/bolt~task.rb.erb @@ -0,0 +1,82 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +#!/opt/puppetlabs/puppet/bin/ruby +<%= compile 'templates/license.erb' -%> + +<%= + lines(wrap_field(task.description_display, 0).split("\n") + .map { |d| "# #{d.strip}" }) +-%> +# +# Command line arguments: JSON object from STDIN with the following fields: +# +<%= + lines(task.arguments.map do |arg| + format( + [ + "# - #{arg.name}: #{arg.description_display}", + [ + "# - #{arg.name}:", + wrap_field(arg.description, 4).split("\n").map do |d| + "# #{d.strip}" + end, + "# (default: #{arg.default.display})" + ], + [ + "# - #{arg.name}:", + wrap_field(arg.description_display, 4).split("\n").map do |d| + "# #{d.strip}" + end + ] + ] + ) + end) # task.arguments.each +-%> + +<%= @api.prefix[1..-1].upcase -%>_ADM_SCOPES = [ +<%= lines(indent_list(@api.scopes.map { |scope| quote_string(scope) }, 2)) -%> +].freeze + +require 'puppet' + +# We want to re-use code already written for the GCP modules +Puppet.initialize_settings + +# Puppet apply does special stuff to load library code stored in modules +# but that magic is available in Bolt so we emulate it here. We look in +# the local user's .puppetlabs directory or if running at "root" we look +# in the directory where Puppet pluginsyncs to. +libdir = if Puppet.run_mode.user? + Dir["#{Puppet.settings[:codedir]}/modules/*/lib"] + else + File.path("#{Puppet.settings[:vardir]}/lib").to_a + end +libdir << File.expand_path("#{File.dirname(__FILE__)}/../lib") +libdir.each { |l| $LOAD_PATH.unshift(l) unless $LOAD_PATH.include?(l) } + +<%= lines(emit_requires(task.requires)) -%> + +# Validates user input +def validate(params, arg_id, default = nil) + arg = arg_id.id2name + raise "Missing parameter '#{arg}' from <%= task.input -%>" \ + if default.nil? && !params.key?(arg) + params.key?(arg) ? params[arg] : default +end + +# Parses and validates user input +<%= lines(emit_bolt_params_ruby(task)) -%> + +<%= lines(task.code) -%> diff --git a/templates/puppet/bolt~task_load_params.rb.erb b/templates/puppet/bolt~task_load_params.rb.erb new file mode 100644 index 000000000000..725a97b0821d --- /dev/null +++ b/templates/puppet/bolt~task_load_params.rb.erb @@ -0,0 +1,32 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'puppet' +require 'puppet/error' +require 'json' +require 'timeout' + +# Load a JSON params object for running a task +<% fn_name = "#{@api.prefix}_task_load_params" -%> +Puppet::Functions.create_function(:<%= fn_name -%>) do + dispatch :<%= fn_name -%> do + param 'String', :input + end + + # Load parameters from STDIN in JSON format + def <%= fn_name -%>(input) + JSON.parse(input) + rescue JSON::ParserError => e + throw "Couldn't parse JSON from: #{input}: #{e.message}" + end +end diff --git a/templates/puppet/bolt~task_validate_param.rb.erb b/templates/puppet/bolt~task_validate_param.rb.erb new file mode 100644 index 000000000000..d7632993ae3c --- /dev/null +++ b/templates/puppet/bolt~task_validate_param.rb.erb @@ -0,0 +1,35 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'puppet' +require 'json' +require 'timeout' + +# Load a JSON params object for running a task +<% fn_name = "#{@api.prefix}_task_validate_param" -%> +<% magic = Provider::Puppet::BOLT_UNDEF_MAGIC -%> +Puppet::Functions.create_function(:<%= fn_name -%>) do + dispatch :<%= fn_name -%> do + param 'Hash', :params + param 'String', :variable + param 'String', :default + end + + # Load parameters from STDIN in JSON format + def <%= fn_name -%>(params, arg, default) + raise "Missing parameter '#{arg}'" \ + if default == <%= quote_string(magic) -%> && !params.key?(arg) + puts "p(#{arg}) = #{params.key?(arg) ? params[arg] : default}" + params.key?(arg) ? params[arg] : default + end +end diff --git a/templates/puppet/examples~credential.pp.erb b/templates/puppet/examples~credential.pp.erb new file mode 100644 index 000000000000..0c37595c70cf --- /dev/null +++ b/templates/puppet/examples~credential.pp.erb @@ -0,0 +1,45 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if name != "README.md" -%> +# Defines a credential to be used when communicating with Google Cloud +# Platform. The title of this credential is then used as the 'credential' +# parameter in the gdns_managed_zone type. +# +# For more information on the gauth_credential parameters and providers please +# refer to its detailed documentation at: +# +# https://forge.puppet.com/google/gauth +# +# For the sake of this example we set the parameter 'path' to point to the file +# that contains your credential in JSON format. And for convenience this example +# allows a variable named $cred_path to be provided to it. If running from the +# command line you can pass it via Facter: +# +# FACTER_cred_path=/path/to/my/cred.json \ +# puppet apply <%= file_relative %> +# +# For convenience you optionally can add it to your ~/.bash_profile (or the +# respective .profile settings) environment: +# +# export FACTER_cred_path=/path/to/my/cred.json +# +<% end # name != README.md -%> +gauth_credential { 'mycred': + path => $cred_path, # e.g. '/home/nelsonjr/my_account.json' + provider => serviceaccount, + scopes => [ +<%= lines(indent_list(data[:scopes].map { |s| quote_string(s) }, 4, true)) -%> + ], +} diff --git a/templates/puppet/function.erb b/templates/puppet/function.erb new file mode 100644 index 000000000000..ed465a531bca --- /dev/null +++ b/templates/puppet/function.erb @@ -0,0 +1,41 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<%= + lines(fn.requires.sort.map { |r| "require '#{r}'" }, 1) \ + unless fn.requires.nil? +-%> +<%= lines(emit_function_doc(fn)) -%> +Puppet::Functions.create_function(:<%= fn.name -%>) do + dispatch :<%= fn.name -%> do +<% fn.arguments.each do |arg| -%> +<% type_parts = arg.type.split('::') -%> +<% if type_parts[0, 2] == %w[Api Type] -%> +<% type = Google::StringUtils.camelize(type_parts.last, :upper) -%> + param '<%= type -%>', :<%= arg.name %> +<% else # arg.type.starts_with?('Api::Type') -%> + param 'Runtime[ruby, "<%= arg.type -%>"]', :<%= arg.name %> +<% end # arg.type.starts_with?('Api::Type') -%> +<% end # fn.arguments.each -%> + end + + def <%= fn.name -%>(<%= fn.arguments.map { |a| a.name }.join(', ') -%>) +<%= lines(indent(fn.code, 4)) -%> + end +<%= lines(lines_before(indent(fn.helpers,2))) unless fn.helpers.nil? -%> +end diff --git a/templates/puppet/metadata.json.erb b/templates/puppet/metadata.json.erb new file mode 100644 index 000000000000..eaf54d4865e8 --- /dev/null +++ b/templates/puppet/metadata.json.erb @@ -0,0 +1,29 @@ +<% require 'json' -%> +<% + os_support = manifest.operating_systems.map do |os| + { operatingsystem: os.name, operatingsystemrelease: os.versions } + end + + requirements = manifest.requires.map do |req| + { name: req.name, version_requirement: req.versions } + end + + output = { + name: "google-#{product.prefix}", + version: manifest.version, + author: 'Google', + summary: manifest.summary, + license: 'Apache-2.0', + source: manifest.source, + project_page: manifest.homepage, + issues_url: manifest.issues, + operatingsystem_support: os_support, + dependencies: requirements, + tags: manifest.tags, + data_provider: nil, + # TODO(nelsonjr): Similar to dependencies vs. requirements, check + # description vs. summary + description: manifest.summary + } +-%> +<%= JSON.pretty_generate(output) -%> diff --git a/templates/puppet/poor_example.pp b/templates/puppet/poor_example.pp new file mode 100644 index 000000000000..d2de4bbc8741 --- /dev/null +++ b/templates/puppet/poor_example.pp @@ -0,0 +1,7 @@ +# This is a poor example that will not pass puppet-lint. +# The arrows are not properly aligned. +file { '/tmp/test': + ensure => file, # this should have => aligned with longest + owner => 'root', # ditto + content => 'test content', +} diff --git a/templates/puppet/property/array.rb.erb b/templates/puppet/property/array.rb.erb new file mode 100644 index 000000000000..fff55589eba6 --- /dev/null +++ b/templates/puppet/property/array.rb.erb @@ -0,0 +1,61 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= prop_ns_dir -%>/property/base' + +<% full_name = ['Google', product_ns, 'Property', 'Array'].join('::') -%> +module Google + module <%= product_ns %> + module Property + # A Puppet property that can compare its values + class Array < Google::<%= product_ns -%>::Property::Base +<%= + lines(format([ + ["# Sets #{full_name} to match all elements, not any."], + [ + "# Sets #{full_name} to match all elements, not", + '# any.' + ] + ], 8)) +-%> + def self.match_all_array + self.array_matching = :all + end + + # When the user specifies an array for a property Puppet by default + # ensures that at least 1 of the values match. This is useful when you + # have various options and any match is good, e.g. various possible file + # sources, say from Corp or Internet. + # + # However our arrays require that all values match so we define the + # array_matching = :all to instruct Puppet to do this. This could have + # been specified in the Puppet::Type, but putting it here makes it + # simpler to not "forget" to define it. + match_all_array + + def match_all? + true + end + + def self.api_munge(value) + value + end + end + end + end +end diff --git a/templates/puppet/property/array_typed.rb.erb b/templates/puppet/property/array_typed.rb.erb new file mode 100644 index 000000000000..12a9d2a27717 --- /dev/null +++ b/templates/puppet/property/array_typed.rb.erb @@ -0,0 +1,54 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile('templates/license.erb') -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= prop_ns_dir -%>/property/array' + +module Google + module <%= product_ns %> + module Property + # A Puppet property that can compare its values + class <%= type -%>Array < Google::<%= product_ns -%>::Property::Array + def self.unsafe_munge(value) + validate value + value + end + + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + + def self.api_munge(value) + validate value + value + end + + def self.validate(value) + return if value.nil? || value.is_a?(::<%= type -%>) + unless value.is_a? ::Array +<% + message = + "Expected #{type.downcase} but found \#{value.class} instead: \#{value}" +-%> + raise "<%= message -%>" + end + value.each { |v| validate v } + end + end + end + end +end diff --git a/templates/puppet/property/base.rb.erb b/templates/puppet/property/base.rb.erb new file mode 100644 index 000000000000..c42f739c4690 --- /dev/null +++ b/templates/puppet/property/base.rb.erb @@ -0,0 +1,38 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'puppet/property' + +module Google + module <%= product_ns %> + module Property + # A Puppet property that can compare its values + class Base < Puppet::Property + def insync?(is) + debug("insync? #{name}: '#{is}' == '#{should}'") + insync = false + insync = true if is == :absent && should == :absent + insync = (is == should) unless is == :absent || should == :absent + debug("insync? #{name}: '#{is}' == '#{should}': #{insync}") + resource.provider.dirty name, is, should unless insync + insync + end + end + end + end +end diff --git a/templates/puppet/property/boolean.rb.erb b/templates/puppet/property/boolean.rb.erb new file mode 100644 index 000000000000..91b6ce7bba76 --- /dev/null +++ b/templates/puppet/property/boolean.rb.erb @@ -0,0 +1,55 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= prop_ns_dir -%>/property/base' + +module Google + module <%= product_ns %> + module Property + # A Puppet property that holds a string + class Boolean < Google::<%= product_ns %>::Property::Base + def self.unsafe_munge(value) + return if value.nil? + value + end + + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + + def self.api_munge(value) + return if value.nil? + value + end + + def insync?(is) + test_is = Puppet::Coercion.boolean(is) + test_should = Puppet::Coercion.boolean(should) + debug("insync? #{name}: '#{test_is}' == '#{test_should}'") + insync = false + insync = true if test_is == :absent && test_should == :absent + insync = (test_is == test_should) \ + unless test_is == :absent || test_should == :absent + debug("insync? #{name}: '#{test_is}' == '#{test_should}': #{insync}") + resource.provider.dirty name, is, should unless insync + insync + end + end + end + end +end diff --git a/templates/puppet/property/double.rb.erb b/templates/puppet/property/double.rb.erb new file mode 100644 index 000000000000..7cc4ac546944 --- /dev/null +++ b/templates/puppet/property/double.rb.erb @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= prop_ns_dir -%>/property/base' + +module Google + module <%= product_ns %> + module Property + # A Puppet property that holds a double + class Double < Google::<%= product_ns %>::Property::Base + def self.unsafe_munge(value) + return if value.nil? + value.to_f + end + + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + + def self.api_munge(value) + return if value.nil? + value.to_f + end + end + end + end +end diff --git a/templates/puppet/property/enum.rb.erb b/templates/puppet/property/enum.rb.erb new file mode 100644 index 000000000000..c3da4c6e8d2e --- /dev/null +++ b/templates/puppet/property/enum.rb.erb @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= prop_ns_dir -%>/property/base' + +module Google + module <%= product_ns %> + module Property + # A Puppet property that holds an enum + class Enum < Google::<%= product_ns %>::Property::Base + def self.unsafe_munge(value) + return if value.nil? + value + end + + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + + def self.api_munge(value) + return if value.nil? + value + end + end + end + end +end diff --git a/templates/puppet/property/integer.rb.erb b/templates/puppet/property/integer.rb.erb new file mode 100644 index 000000000000..10712a90ca9c --- /dev/null +++ b/templates/puppet/property/integer.rb.erb @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= prop_ns_dir -%>/property/base' + +module Google + module <%= product_ns %> + module Property + # A Puppet property that holds an integer + class Integer < Google::<%= product_ns %>::Property::Base + def self.unsafe_munge(value) + return if value.nil? + value.to_i + end + + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + + def self.api_munge(value) + return if value.nil? + value.to_i + end + end + end + end +end diff --git a/templates/puppet/property/namevalues.rb.erb b/templates/puppet/property/namevalues.rb.erb new file mode 100644 index 000000000000..5833a0dca752 --- /dev/null +++ b/templates/puppet/property/namevalues.rb.erb @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= prop_ns_dir -%>/property/base' + +module Google + module <%= product_ns %> + module Property + # A Puppet property that holds a string + class NameValues < Google::<%= product_ns %>::Property::Base + def self.api_munge(value) + return if value.nil? + value + end + + def self.unsafe_munge(value) + return if value.nil? + value + end + + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + end + end + end +end diff --git a/templates/puppet/property/nested_object.rb.erb b/templates/puppet/property/nested_object.rb.erb new file mode 100644 index 000000000000..07df3ebdc357 --- /dev/null +++ b/templates/puppet/property/nested_object.rb.erb @@ -0,0 +1,246 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% + def emit_parse_assignment(prop, method, source) + parser = [prop.property_type, method].join('.') + format( + [ + ["@#{prop.out_name} = #{parser}(args['#{source}'])"], + [ + "@#{prop.out_name} =", + indent("#{parser}(args['#{source}'])", 2) + ], + [ + "@#{prop.out_name} = #{parser}(", + indent("args['#{source}']", 2), + ')' + ], + [ + "@#{prop.out_name} =", + indent([ + "#{parser}(", + indent("args['#{source}']", 2), + ')' + ], 2) + ] + ], 0, 10 + ) + end +-%> +<%= compile('templates/license.erb') -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% if emit_array -%> +require 'google/<%= prop_ns_dir -%>/property/array' +<% end -%> +require 'puppet/property' + +module Google + module <%= product_ns %> + module Data +<%= lines(indent( + emit_rubocop(binding, :class, + ['Google', product_ns, 'Data', class_name].join('::'), + :disabled), + 6)) -%> +<%= + lines(format([ + ["# A class to manage data for #{field} for #{obj_name}."], + [ + "# A class to manage data for #{field} for", + "# #{obj_name}." + ] + ], 6)) +-%> + class <%= class_name %> + include Comparable + +<% if !nested_properties.empty? -%> +<% nested_properties.each do |prop| -%> + attr_reader :<%= prop.out_name %> +<% end # nested_properties.each -%> + +<% end # if !nested_properties.empty? -%> +<% + prop_to_json = [] + prop_to_s = [] + nested_properties.each do |prop| + prop_to_json << "'#{prop.field_name}' => #{prop.out_name}" + prop_to_s << + if prop.is_a?(Api::Type::Array) + if prop.item_type.is_a?(Api::Type::NestedObject) + [ + "#{prop.out_name}: ['[',", + indent([ + "#{prop.out_name}.map(&:to_json).join(', '),", + "']'].join(' ')" + ], prop.out_name.length + 3), # 3 = ": [" + ] + elsif prop.item_type.is_a?(::String) + "#{prop.out_name}: #{prop.out_name}" + else + raise "Unknown array type" + end + else + "#{prop.out_name}: #{prop.out_name}" + end + end + + json_code = [] + json_code << '{' + json_code << indent_list(prop_to_json, 2) + json_code << '}.reject { |_k, v| v.nil? }.to_json' + + to_s_code = [] + to_s_code << '{' + to_s_code << indent_list(prop_to_s, 2) + to_s_code << + '}.reject { |_k, v| v.nil? }.map { |k, v| "#{k}: #{v}" }.join(\', \')' +-%> +<%= lines(indent(emit_method('to_json', ['_arg = nil'], json_code, + file_relative), 8), + 1) -%> +<%= lines(indent(emit_method('to_s', %w[], to_s_code, file_relative), 8), 1) -%> + def ==(other) + return false unless other.is_a? <%= class_name %> + compare_fields(other).each do |compare| + next if compare[:self].nil? || compare[:other].nil? + return false if compare[:self] != compare[:other] + end + true + end + + def <=>(other) + return false unless other.is_a? <%= class_name %> + compare_fields(other).each do |compare| + next if compare[:self].nil? || compare[:other].nil? + result = compare[:self] <=> compare[:other] + return result unless result.zero? + end + 0 + end + + private + +<% + compare_code = [] + compare_code << '[' + compare_code << indent_list(nested_properties.map do |prop| + format( + [ + ["{ self: #{prop.out_name}", + "other: other.#{prop.out_name} }"] + .join(', '), + ['{', + indent_list([ + "self: #{prop.out_name}", + "other: other.#{prop.out_name}" + ], 2), + '}' + ] + ], 0, 12 + ) + end, 2) + compare_code << ']' +-%> +<%= lines(indent(emit_method('compare_fields', %w[other], compare_code, + file_relative), 8)) -%> + end + + # Manages a <%= class_name -%> nested object + # Data is coming from the GCP API + class <%= class_name -%>Api < <%= class_name %> +<% + init_code = [] + init_code.concat(nested_properties.map do |prop| + emit_parse_assignment(prop, 'api_munge', prop.field_name) + end) +-%> +<%= lines(indent(emit_method('initialize', %w[args], init_code, file_relative, + class_name: "#{class_name}Api"), 8)) -%> + end + + # Manages a <%= class_name -%> nested object + # Data is coming from the Puppet manifest + class <%= class_name -%>Catalog < <%= class_name %> +<% + init_code = [] + init_code.concat(nested_properties.map do |prop| + emit_parse_assignment(prop, 'unsafe_munge', prop.out_name) + end) +-%> +<%= lines(indent(emit_method('initialize', %w[args], init_code, file_relative, + class_name: "#{class_name}Catalog"), 8)) -%> + end + end + + module Property +<%= + lines(format([ + ["# A class to manage input to #{field} for #{obj_name}."], + [ + "# A class to manage input to #{field} for", + "# #{obj_name}." + ] + ], 6)) +-%> + class <%= class_name %> < Puppet::Property + # Used for parsing Puppet catalog + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + + # Used for parsing Puppet catalog + def self.unsafe_munge(value) + return if value.nil? + Data::<%= class_name %>Catalog.new(value) + end + + # Used for parsing GCP API responses + def self.api_munge(value) + return if value.nil? + Data::<%= class_name %>Api.new(value) + end + end +<% if emit_array -%> + + # A Puppet property that holds an integer + class <%= class_name %>Array < Google::<%= product_ns -%>::Property::Array + # Used for parsing Puppet catalog + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + + # Used for parsing Puppet catalog + def self.unsafe_munge(value) + return if value.nil? + return <%= class_name %>.unsafe_munge(value) \ + unless value.is_a?(::Array) + value.map { |v| <%= class_name %>.unsafe_munge(v) } + end + + # Used for parsing GCP API responses + def self.api_munge(value) + return if value.nil? + return <%= class_name %>.api_munge(value) \ + unless value.is_a?(::Array) + value.map { |v| <%= class_name %>.api_munge(v) } + end + end +<% end # if emit_array -%> + end + end +end diff --git a/templates/puppet/property/resourceref.rb.erb b/templates/puppet/property/resourceref.rb.erb new file mode 100644 index 000000000000..39e31d757864 --- /dev/null +++ b/templates/puppet/property/resourceref.rb.erb @@ -0,0 +1,171 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%# Requires: resource - name of resource being fetched -%> +<%# imports - name of property being fetched -%> +<%= compile('templates/license.erb') -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% + requires = [] + requires << 'puppet/property' + requires << "google/#{prop_ns_dir}/property/array" if emit_array + requires << 'google/object_store' +-%> +<%= lines(emit_requires(requires)) -%> + +module Google + module <%= product_ns %> + module Data + # Base class for ResourceRefs + # Imports <%= imports -%> from <%= resource %> +<%= + lines(indent(emit_rubocop(binding, :class, + ['Google', product_ns, 'Data', + class_name].join('::'), + :disabled), 6)) +-%> + class <%= class_name %> + include Comparable + + def ==(other) + return false unless other.is_a? <%= class_name %> + return false if resource != other.resource + true + end + + def <=>(other) + resource <=> other.resource + end + end + + # A class to fetch the resource value from a referenced block + # Will return the value exported from a different Puppet resource +<%= + lines(indent(emit_rubocop(binding, :class, + ['Google', product_ns, 'Data', + "#{class_name}Title"].join('::'), + :disabled), 6)) +-%> + class <%= class_name %>Catalog < <%= class_name %> + def initialize(title) + @title = title + end + + # Puppet requires the title for autorequiring + def autorequires + [@title] + end + + def to_s + resource.to_s + end + + def to_json(_arg = nil) + return if resource.nil? + resource.to_json + end + + def resource +<% out_name = property.resource_ref.out_name -%> +<%= + lines(format( + [ + ["Google::ObjectStore.instance[:#{out_name}].each do |entry|"], + [ + "Google::ObjectStore.instance[:#{out_name}]", + ' .each do |entry|' + ], + ], 10)) +-%> + return entry.exports[:<%= imports -%>] if entry.title == @title + end + raise ArgumentError, "<%= out_name -%>[#{@title}] required" + end + end + + # A class to manage a JSON blob from GCP API + # Will immediately return value from JSON blob without changes +<%= + lines(indent(emit_rubocop(binding, :class, + ['Google', product_ns, 'Data', + "#{class_name}Resource"].join('::'), + :disabled), 6)) +-%> + class <%= class_name %>Api < <%= class_name %> + attr_reader :resource + + def initialize(resource) + @resource = resource + end + + def to_s + @resource.to_s + end + + def to_json(_arg = nil) + @resource.to_json + end + end + end + + module Property + # A class to manage fetching <%= imports -%> from a <%= resource %> + class <%= class_name %> < Puppet::Property + # Used for catalog values + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + + def self.unsafe_munge(value) + return if value.nil? + Data::<%= class_name %>Catalog.new(value) + end + + # Used for fetched JSON values + def self.api_munge(value) + return if value.nil? + Data::<%= class_name %>Api.new(value) + end + end +<% if emit_array -%> + + # A Puppet property that holds an integer + class <%= class_name %>Array < Google::<%= product_ns -%>::Property::Array + # Used for parsing Puppet catalog + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + + # Used for parsing Puppet catalog + def self.unsafe_munge(value) + return if value.nil? + return <%= class_name %>.unsafe_munge(value) \ + unless value.is_a?(::Array) + value.map { |v| <%= class_name %>.unsafe_munge(v) } + end + + # Used for parsing GCP API responses + def self.api_munge(value) + return if value.nil? + return <%= class_name %>.api_munge(value) \ + unless value.is_a?(::Array) + value.map { |v| <%= class_name %>.api_munge(v) } + end + end +<% end # if emit_array -%> + end + end +end diff --git a/templates/puppet/property/string.rb.erb b/templates/puppet/property/string.rb.erb new file mode 100644 index 000000000000..364d80f11b52 --- /dev/null +++ b/templates/puppet/property/string.rb.erb @@ -0,0 +1,42 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= prop_ns_dir -%>/property/base' + +module Google + module <%= product_ns %> + module Property + # A Puppet property that holds a string + class String < Google::<%= product_ns %>::Property::Base + def self.api_munge(value) + return if value.nil? + value + end + + def self.unsafe_munge(value) + return if value.nil? + value + end + + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + end + end + end +end diff --git a/templates/puppet/property/time.rb.erb b/templates/puppet/property/time.rb.erb new file mode 100644 index 000000000000..1218c19676a1 --- /dev/null +++ b/templates/puppet/property/time.rb.erb @@ -0,0 +1,61 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'google/<%= prop_ns_dir -%>/property/base' + +module Google + module <%= product_ns %> + module Data + # A Time that always returns a ISO8601 String + class Time < ::Time +<%# TODO(alexstephen): Add a .to_resource method to replace .to_s -%> +<% + # Overriden .to_s ensures that value coercison does not need to be placed + # within the providers. Providers do not have to worry about the time formats +-%> + def to_s + # All GCP APIs expect timestamps in the ISO-8601 / RFC3339 format + + # Overriding the .to_s method ensures that Ruby will get a + # ISO-8601 timestamp at the last moment and ensures the timestamp + # format is abstracted away. + iso8601 + end + end + end + + module Property + # A Puppet property that holds a date & time value + class Time < Google::<%= product_ns %>::Property::Base + def self.unsafe_munge(value) + return if value.nil? + Data::Time.parse(value) + end + + def unsafe_munge(value) + self.class.unsafe_munge(value) + end + + def self.api_munge(value) + return if value.nil? + Data::Time.parse(value) + end + end + end + end +end diff --git a/templates/puppet/provider.erb b/templates/puppet/provider.erb new file mode 100644 index 000000000000..81831b75fcda --- /dev/null +++ b/templates/puppet/provider.erb @@ -0,0 +1,527 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% + inside_indent = 2 + + upd_method = Google::HashUtils.navigate(config, %w[methods update]) + + requires = generate_requires(object.all_user_properties) + requires << 'google/hash_utils' + requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'get') + requires << 'puppet' + unless object.exports.nil? + requires << 'google/object_store' + end + unless object.readonly + requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'delete') + requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'post') + requires << emit_google_lib(binding, Compile::Libraries::NETWORK, 'put') + end + unless upd_method.nil? + requires << emit_google_lib(binding, Compile::Libraries::NETWORK, + upd_method.downcase) + end + + custom_requires = Google::HashUtils.navigate(config, %w[requires]) + unless custom_requires.nil? + requires << custom_requires + end +-%> +<%= lines(emit_requires(requires)) -%> + +<% Google::LOGGER.info "Generating #{object.name}: #{object.out_name}" -%> +Puppet::Type.type(:<%= object.out_name -%>).provide(:google) do + mk_resource_methods + +<% + input_only = object.properties.select(&:input) + has_input = !input_only.empty? +-%> +<%= lines(get_code_multiline(config, 'attributes')) -%> + def self.instances + debug('instances') + raise [ + '"puppet resource" is not supported at the moment:', + 'TODO(nelsonjr): https://goto.google.com/graphite-bugs-view?id=167' + ].join(' ') + end + + def self.prefetch(resources) + debug('prefetch') + resources.each do |name, resource| +<% custom_prefetch = get_code_multiline config, 'prefetch' -%> +<% if custom_prefetch.nil? -%> + project = resource[:project] + debug("prefetch #{name}") if project.nil? + debug("prefetch #{name} @ #{project}") unless project.nil? +<% if object.self_link_query.nil? -%> +<%= + lines(format( + [ + ('fetch = fetch_resource(resource, self_link(resource))' \ + unless object.kind?), + ['fetch = fetch_resource(resource, self_link(resource),', + "'#{object.kind}')"].join(' '), + [ + 'fetch = fetch_resource(resource, self_link(resource),', + indent("'#{object.kind}')", 23) # 23 = align previous until ( + ] + ].compact, 6 + )) +-%> +<% else -%> +<% + self_link_kind = !object.self_link_query.kind.nil? ? \ + "'#{object.self_link_query.kind}'," : '' + + obj_kind = object.kind? ? "'#{object.kind}'," : '' +-%> +<%= + lines(format( + [ + ["fetch = fetch_wrapped_resource(resource, #{obj_kind}", + "#{self_link_kind}", + "'#{object.self_link_query.items}')"].join(' '), + [ + ["fetch = fetch_wrapped_resource(resource, #{obj_kind}", + "#{self_link_kind}"].join(' '), + indent([ + "'#{object.self_link_query.items}')" + ], 31) # 31 = align with ( previous line + ], + [ + "fetch = fetch_wrapped_resource(resource, #{obj_kind}", + indent([ + "#{self_link_kind}", + "'#{object.self_link_query.items}')" + ], 31) # 31 = align with ( previous line + ] + ], 6 + )) +-%> +<% end -%> +<% if has_input -%> + resource.provider = present(name, fetch, resource) unless fetch.nil? +<% else # has_input -%> + resource.provider = present(name, fetch) unless fetch.nil? +<% end # has_input -%> +<% unless object.exports.nil? -%> + Google::ObjectStore.instance.add(:<%= object.out_name -%>, resource) +<% end -%> +<% else # custom_prefetch.nil? -%> +<%= lines(indent(custom_prefetch, 6)) -%> +<% end # custom_prefetch.nil? -%> + end + end + +<% + assigns = + object.properties.reject(&:input).map do |prop| + name = prop.out_name + api_name = prop.name + type = prop.property_type + if api_name.include?('.') + fetch_tree = api_name.split('.').join(' ') + format([ + [ + "#{name}: #{type}.api_munge(", + indent("Google::HashUtils.navigate(fetch, %w[#{fetch_tree}])", 2), + ')' + ], + [ + "#{name}: \\", + "#{type}.api_munge(", + indent("Google::HashUtils.navigate(fetch, %w[#{fetch_tree}])", 2), + ')' + ] + ], 0, 7) + else + format([ + ["#{name}: #{type}.api_munge(fetch['#{prop.field_name}'])"], + [ + "#{name}:", + indent("#{type}.api_munge(fetch['#{prop.field_name}'])", 2) + ], + [ + "#{name}: #{type}.api_munge(", + indent("fetch['#{prop.field_name}']", 2), + ')', + ], + [ + "#{name}:", + indent(["#{type}.api_munge(", + indent("fetch['#{prop.field_name}']", 2), + ')'], 2) + ] + ], 0, 7) # 6 spaces = indent, 1 space = trailing comma (it's a list) + end + end + assigns.concat(input_only.map do |prop| + "#{prop.out_name}: resource[:#{prop.out_name}]" + end) + present_code = [ + if has_input + [ + 'result = new(', + indent( + ['{ title: name,', ' ', + 'ensure: :present }.merge(fetch_to_hash(fetch, resource))'].join, 2 + ), + ')' + ] + else + ['result = new({ title: name,', + 'ensure: :present }.merge(fetch_to_hash(fetch)))'].join(' ') + end, + ('result.instance_variable_set(:@fetched, fetch)' if \ + save_api_results?(config, object)), + 'result' + ].compact + f2h_code = [ + '{', + indent_list(assigns, 2), + '}.reject { |_, v| v.nil? }', + ] +-%> +<% custom_present = get_code_multiline config, 'present' -%> +<% if custom_present.nil? -%> +<%= + lines(indent(emit_method('self.present', + has_input ? %w[name fetch resource] : %w[name fetch], + present_code, file_relative), 2), 1) +-%> +<% else -%> +<%= lines(indent(custom_present, 2)) -%> +<% end -%> +<%= + lines(indent(emit_method('self.fetch_to_hash', + has_input ? %w[fetch resource] : %w[fetch], + f2h_code, file_relative), 2), 1) +-%> +<%= lines(get_code_multiline(config, 'constructor')) -%> +<% unless object.virtual -%> + def exists? + debug("exists? #{@property_hash[:ensure] == :present}") + @property_hash[:ensure] == :present + end + +<% + # TODO(nelsonjr): Investigate if we can have a timeout to wait for operations + # that we did not start to complete. For example if you start a firewall + # change via Developer Console and attempt to apply the manifest you get: + # + # Error: /Stage[main]/Main/Gcompute_firewall[test-firewall-allow-ssh]: Could + # not evaluate: Operation failed: The resource + # 'projects/google.com:graphite-playground/global/firewalls/....' + # is not ready +-%> + def create + debug('create') + @created = true +<% custom_create = get_code_multiline config, 'create' -%> +<% if custom_create.nil? -%> +<% + if object.create_verb.nil? || object.create_verb == :POST + body_new = 'collection(@resource)' + request_new = "Google::#{product_ns}::Network::Post.new" + elsif object.create_verb == :PUT + body_new = 'self_link(@resource)' + request_new = "Google::#{product_ns}::Network::Put.new" + end + + request_patch = get_code_multiline config, 'resource_create_patch' + custom_resource = true?(Google::HashUtils.navigate( + config, %w[provider_helpers custom_create_resource] + )) +-%> +<%= + lines(indent_list(["create_req = #{request_new}(#{body_new}"].concat( + indent([ + 'fetch_auth(@resource)', + "'application/json'", + "#{custom_resource ? 'resource_to_create' : 'resource_to_request'})" + ], request_new.length + 14).split("\n") # 14 = 'create_req = ' + '(' + ), 4)) +-%> +<% fetch_assign = save_api_results?(config, object) ? '@fetched = ' : '' -%> +<% unless object.async -%> +<% obj_kind = object.kind? ? ", '#{object.kind}'" : '' -%> + <%= fetch_assign -%>return_if_object create_req.send<%= obj_kind %> +<% else -%> + <%= fetch_assign -%>wait_for_operation create_req.send, @resource +<% end -%> +<% else -%> +<%= lines(indent(custom_create, 4)) -%> +<% end -%> + @property_hash[:ensure] = :present + end + + def destroy + debug('destroy') + @deleted = true +<% custom_delete = get_code_multiline config, 'delete' -%> +<% if custom_delete.nil? -%> +<% dele_new = "Google::#{product_ns}::Network::Delete.new" -%> +<%= + lines(indent_list(["delete_req = #{dele_new}(self_link(@resource)"].concat( + indent([ + 'fetch_auth(@resource))' + ], dele_new.length + 14).split("\n") # 14 = 'delete_req = ' + '(' + ), 4)) +-%> +<% kind_param = object.kind? ? ", '#{object.kind}'" : '' -%> +<% unless object.async -%> + return_if_object delete_req.send<%= kind_param %> +<% else -%> + wait_for_operation delete_req.send, @resource +<% end -%> +<% else -%> +<%= lines(indent(custom_delete, 4)) -%> +<% end -%> + @property_hash[:ensure] = :absent + end + +<% end -%> + def flush + debug('flush') +<% # TODO(nelsonjr): Remove @dirty or SQL does not do idempotent updates. -%> + # return on !@dirty is for aiding testing (puppet already guarantees that) + return if @created || @deleted || !@dirty +<% custom_flush = get_code_multiline config, 'flush' -%> +<% + if custom_flush.nil? + put_new = if upd_method.nil? + "Google::#{product_ns}::Network::Put.new" + else + "Google::#{product_ns}::Network::#{upd_method.capitalize}.new" + end + custom_resource = true?(Google::HashUtils.navigate( + config, %w[provider_helpers custom_update_resource] + )) +-%> +<%= + lines(indent_list(["update_req = #{put_new}(self_link(@resource)"].concat( + indent([ + 'fetch_auth(@resource)', + "'application/json'", + "#{custom_resource ? 'resource_to_update' : 'resource_to_request'})" + ], put_new.length + 14).split("\n") # 14 = 'update_req = ' + '(' + ), 4)) +-%> +<% if object.async -%> + <%= fetch_assign -%>wait_for_operation update_req.send, @resource +<% else # object.async -%> + <% obj_kind = object.kind? ? ", '#{object.kind}'" : '' -%> + <%= fetch_assign -%>return_if_object update_req.send<%= obj_kind %> +<% end # object.async -%> +<% else # custom_flush.nil? -%> +<%= lines(indent(custom_flush, 4)) -%> +<% end # custom_flush.nil? -%> + end + + def dirty(field, from, to) + @dirty = {} if @dirty.nil? + @dirty[field] = { + from: from, + to: to + } + end + +<% unless object.exports.nil? -%> +<% + exp_list = [ + '{', + indent_list(object.exported_properties.map do |p| + if p.is_a?(Api::Type::FetchedExternal) + "#{p.out_name}: @fetched['#{p.field_name}']" + else + "#{p.out_name}: resource[:#{p.out_name}]" + end + end, 2), + '}' + ] +-%> +<%= lines(indent(emit_method('exports', [], exp_list, file_relative), 2), 1) -%> +<% end -%> + private +<% + all_props = object.all_user_properties + has_boolean = !all_props.select{ |o| o.is_a?(Api::Type::Boolean) }.empty? +-%> +<% if has_boolean -%> + + # Hashes have :true or :false which to_json converts to strings + def sym_to_bool(value) + if value == :true + true + elsif value == :false + false + else + value + end + end +<% end -%> +<% + all_properties = object.all_user_properties + has_project_property = \ + !object.all_user_properties.select { |o| o.name == 'project' }.empty? + project_arg = has_project_property ? [] : ['project: resource[:project]'] + r2h_code = [ + '{', + indent_list(project_arg.concat([ + 'name: resource[:name]', + ("kind: '#{object.kind}'" if object.kind?) + ]).concat(all_properties.select { |p| p.name != 'name' }.map do |prop| + format([ + ["#{prop.out_name}: resource[:#{prop.out_name}]"], + [ + "#{prop.out_name}:", + indent("resource[:#{prop.out_name}]", 2) + ] + ], 0, 4) + end).compact, 2), + '}.reject { |_, v| v.nil? }' + ] +-%> + +<%= + lines(indent(emit_method('self.resource_to_hash', %w[resource], r2h_code, + file_relative), 2), 1) +-%> +<% unless false?(Google::HashUtils.navigate(config, + %w[provider_helpers visible + resource_to_request])) -%> +<% + prop_code = [] + prop_code << "kind: '#{object.kind}'" if object.kind? + prop_code.concat( + object.properties.select { |p| !p.output } + .map do |prop| + "#{prop.field_name}: @resource[:#{prop.out_name}]" + end + ) + prop_code.concat( + (object.parameters || []) + .select { |p| p.input } + .map do |prop| + "#{prop.field_name}: @resource[:#{prop.out_name}]" + end + ) + + r2r_code = [] + if prop_code.empty? + r2r_code << 'request = {}' + else + r2r_code << 'request = {' + r2r_code << indent_list(prop_code, 2) + r2r_code << '}.reject { |_, v| v.nil? }' + end + + if has_boolean + r2r_code << '' + r2r_code << '# Convert boolean symbols into JSON compatible value.' + r2r_code << ['request = request.inject({})', + '{ |h, (k, v)| h.merge(k => sym_to_bool(v)) }'].join(' ') + r2r_code << '' + end + + resource_to_request_patch = get_code_multiline config, + 'resource_to_request_patch' + unless resource_to_request_patch.nil? + r2r_code << '' unless has_boolean + r2r_code << resource_to_request_patch + r2r_code << '' + end # resource_to_request_patch.nil? + + if object.encoder? + r2r_code << '# Format request to conform with API endpoint' + r2r_code << "request = #{object.transport.encoder}(request)" + end + + r2r_code \ + << 'debug "request: #{request}" unless ENV[\'PUPPET_HTTP_DEBUG\'].nil?' + r2r_code << 'request.to_json' +-%> +<%= lines(indent(emit_method('resource_to_request', [], r2r_code, + file_relative), 2), 1) -%> +<% end # visible:resource_to_request -%> +<% + unless false?(Google::HashUtils.navigate(config, + %w[provider_helpers visible + unwrap_resource])) + unless object.self_link_query.nil? +-%> +<% + urf_code = [ + '{', + indent_list( + Hash[object.identity.map { |i| [i, "resource[:#{i.out_name}]"] }] + .map { |k, v| "#{k.out_name}: #{v}" }, 2 + ), + '}' + ] +-%> + def unwrap_resource_filter(resource) + self.class.unwrap_resource_filter(resource) + end + +<%= lines(indent(emit_method('self.unwrap_resource_filter', %w[resource], + urf_code, file_relative), 2), 1) -%> +<% end # unless object.self_link_query.nil? -%> +<% end # visible:unwrap_resource -%> + def fetch_auth(resource) + self.class.fetch_auth(resource) + end + + def self.fetch_auth(resource) + Puppet::Type.type(:gauth_credential).fetch(resource) + end + + def debug(message) + puts("DEBUG: #{message}") if ENV['PUPPET_HTTP_VERBOSE'] + super(message) + end + +<% custom_collection = get_code_multiline config, 'collection' -%> +<% if custom_collection.nil? -%> +<%= lines(indent(emit_link('collection', collection_url(object), true), 2)) %> +<% else # custom_collection.nil? -%> +<%= lines(indent(emit_link('collection', custom_collection, true), 2)) %> +<% end # custom_collection.nil? -%> +<% custom_self_link = get_code_multiline config, 'self_link' -%> +<% if custom_self_link.nil? -%> +<%= lines(indent(emit_link('self_link', self_link_url(object), true), 2), 1) -%> +<% else # custom_self_link.nil? -%> +<%= lines(indent(emit_link('self_link', custom_self_link, true), 2), 1) -%> +<% end # custom_self_link.nil? -%> +<% custom_return_if_object = get_code_multiline config, 'return_if_object' -%> +<% if custom_return_if_object.nil? -%> +<%= lines(indent(compile('templates/return_if_object.erb'), 2)) %> +<% else # custom_return_if_object.nil? -%> +<%= lines(indent(custom_return_if_object, 2), 1) -%> +<% end # custom_return_if_object.nil? -%> +<%= lines(indent(compile('templates/expand_variables.erb'), 2)) %> +<%= + if object.async + lines(indent(compile('templates/async.erb'), inside_indent), 1) + end +-%> +<%= lines(indent(compile('templates/provider_helpers.erb'), 2), 1) -%> +<%= lines(indent(compile('templates/transport.erb'), 2)) -%> +end diff --git a/templates/puppet/provider_spec.erb b/templates/puppet/provider_spec.erb new file mode 100644 index 000000000000..d760958cc1ed --- /dev/null +++ b/templates/puppet/provider_spec.erb @@ -0,0 +1,456 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'spec_helper' + +<% +if object.virtual + test_matrix = Provider::TestMatrix.new(template, object, self, + exists: { + changes: [ # read-only object mistmatch + [:no_name, :fail], + [:has_name, :fail] + ], + no_change: [ + [:no_name, :pass], + [:has_name, :pass] + ], + }, + missing: [ + [:no_name, :fail], + [:has_name, :fail] + ] + ) +else + test_matrix = Provider::TestMatrix.new(template, object, self, + present: { + exists: { + changes: { # flush + no_name: [:pass, :fail], + has_name: [:pass, :fail] + }, + no_change: { # no action + no_name: [:pass, :fail], + has_name: [:pass, :fail] + } + }, + missing: { # create + # changes == ignore + no_name: [:pass, :fail], + has_name: [:pass, :fail] + } + }, + absent: { + exists: { # delete + # changes == ignore + no_name: [:pass, :fail], + has_name: [:pass, :fail] + }, + missing: { # no action + # changes == ignore + no_name: [:pass, :fail], + has_name: [:pass, :fail] + } + } + ) +end + +prop_data = Provider::TestData::Expectations.new(self, @data_gen) +manifester = Provider::PuppetTestManifestFormatter.new self +-%> +<%= + lines(format( + [ + ["describe Puppet::Type.type(:#{object.out_name}).provider(:google) do"], + [ + "describe Puppet::Type.type(:#{object.out_name})", + indent('.provider(:google) do', 21) + ] + ])) +-%> + before(:all) do + cred = Google::FakeAuthorization.new + Puppet::Type.type(:gauth_credential) + .define_singleton_method(:fetch) { |_resource| cred } + end + + it '#instances' do + expect { described_class.instances }.to raise_error(StandardError, + /not supported/) + end + +<% if object.virtual -%> +<% # Object does NOT provides 'ensure' parameter -%> +<%= test_matrix.push(:ignore, :exists) -%> +<%= test_matrix.push(:ignore, :exists, :no_change) -%> +<%= test_matrix.push(:ignore, :exists, :no_change, [:no_name, :pass]) -%> + # TODO(nelsonjr): Implement new test format. +<%= test_matrix.pop(:ignore, :exists, :no_change, [:no_name, :pass]) -%> + +<%= test_matrix.push(:ignore, :exists, :no_change, [:has_name, :pass]) -%> + # TODO(nelsonjr): Implement new test format. +<%= test_matrix.pop(:ignore, :exists, :no_change, [:has_name, :pass]) -%> +<%= test_matrix.pop(:ignore, :exists, :no_change) -%> + +<%= test_matrix.push(:ignore, :exists, :changes) -%> +<%= test_matrix.push(:ignore, :exists, :changes, [:no_name, :fail]) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:ignore, :exists, :changes, [:no_name, :fail]) -%> + +<%= test_matrix.push(:ignore, :exists, :changes, [:has_name, :fail]) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:ignore, :exists, :changes, [:has_name, :fail]) -%> +<%= test_matrix.pop(:ignore, :exists, :changes) -%> +<%= test_matrix.pop(:ignore, :exists) -%> + +<%= test_matrix.push(:ignore, :missing) -%> +<%= test_matrix.push(:ignore, :missing, :ignore, [:no_name, :fail]) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:ignore, :missing, :ignore, [:no_name, :fail]) -%> + +<%= test_matrix.push(:ignore, :missing, :ignore, [:has_name, :fail]) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:ignore, :missing, :ignore, [:has_name, :fail]) -%> +<%= test_matrix.pop(:ignore, :missing) -%> +<% else -%> +<% # Object that provides 'ensure' parameter -%> +<%= test_matrix.push(:present) -%> +<%= test_matrix.push(:present, :exists) -%> +<%= test_matrix.push(:present, :exists, :no_change) -%> +<%= test_matrix.push(:present, :exists, :no_change, :no_name) -%> +<%= test_matrix.push(:present, :exists, :no_change, :no_name, :pass) -%> +<%= + # Ensure no changes happened + name = false + compile('templates/puppet/test~present~no_changes.erb') +-%> +<%= test_matrix.pop(:present, :exists, :no_change, :no_name, :pass) -%> + +<%= test_matrix.push(:present, :exists, :no_change, :no_name, :fail) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :exists, :no_change, :no_name, :fail) -%> +<%= test_matrix.pop(:present, :exists, :no_change, :no_name) -%> + +<%= test_matrix.push(:present, :exists, :no_change, :has_name) -%> +<%= test_matrix.push(:present, :exists, :no_change, :has_name, :pass) -%> +<%= + # Ensure no changes happened + name = true + compile('templates/puppet/test~present~no_changes.erb') +-%> +<%= test_matrix.pop(:present, :exists, :no_change, :has_name, :pass) -%> + +<%= test_matrix.push(:present, :exists, :no_change, :has_name, :fail) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :exists, :no_change, :has_name, :fail) -%> +<%= test_matrix.pop(:present, :exists, :no_change, :has_name) -%> +<%= test_matrix.pop(:present, :exists, :no_change) -%> + +<%= test_matrix.push(:present, :exists, :changes) -%> +<%= test_matrix.push(:present, :exists, :changes, :no_name) -%> +<%= test_matrix.push(:present, :exists, :changes, :no_name, :pass) -%> + # TODO(nelsonjr): Implement new test format. +<%= test_matrix.pop(:present, :exists, :changes, :no_name, :pass) -%> + +<%= test_matrix.push(:present, :exists, :changes, :no_name, :fail) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :exists, :changes, :no_name, :fail) -%> +<%= test_matrix.pop(:present, :exists, :changes, :no_name) -%> + +<%= test_matrix.push(:present, :exists, :changes, :has_name) -%> +<%= test_matrix.push(:present, :exists, :changes, :has_name, :pass) -%> + # TODO(nelsonjr): Implement new test format. +<%= test_matrix.pop(:present, :exists, :changes, :has_name, :pass) -%> + +<%= test_matrix.push(:present, :exists, :changes, :has_name, :fail) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :exists, :changes, :has_name, :fail) -%> +<%= test_matrix.pop(:present, :exists, :changes, :has_name) -%> +<%= test_matrix.pop(:present, :exists, :changes) -%> +<%= test_matrix.pop(:present, :exists) -%> + +<%= test_matrix.push(:present, :missing) -%> +<%= test_matrix.push(:present, :missing, :ignore, :no_name) -%> +<%= test_matrix.push(:present, :missing, :ignore, :no_name, :pass) -%> +<%= + # Creates the resource + name = false + compile('templates/puppet/test~present~create.erb') +-%> +<%= test_matrix.pop(:present, :missing, :ignore, :no_name, :pass) -%> + +<%= test_matrix.push(:present, :missing, :ignore, :no_name, :fail) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :missing, :ignore, :no_name, :fail) -%> +<%= test_matrix.pop(:present, :missing, :ignore, :no_name) -%> + +<%= test_matrix.push(:present, :missing, :ignore, :has_name) -%> +<%= test_matrix.push(:present, :missing, :ignore, :has_name, :pass) -%> +<%= + # Creates the resource + name = true + compile('templates/puppet/test~present~create.erb') +-%> +<%= test_matrix.pop(:present, :missing, :ignore, :has_name, :pass) -%> + +<%= test_matrix.push(:present, :missing, :ignore, :has_name, :fail) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:present, :missing, :ignore, :has_name, :fail) -%> +<%= test_matrix.pop(:present, :missing, :ignore, :has_name) -%> +<%= test_matrix.pop(:present, :missing) -%> +<%= test_matrix.pop(:present) -%> + +<%= test_matrix.push(:absent) -%> +<%= test_matrix.push(:absent, :missing) -%> +<%= test_matrix.push(:absent, :missing, :ignore, :no_name) -%> +<%= test_matrix.push(:absent, :missing, :ignore, :no_name, :pass) -%> +<%= + # No action + name = false + compile('templates/puppet/test~absent~no_action.erb') +-%> +<%= test_matrix.pop(:absent, :missing, :ignore, :no_name, :pass) -%> + +<%= test_matrix.push(:absent, :missing, :ignore, :no_name, :fail) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:absent, :missing, :ignore, :no_name, :fail) -%> +<%= test_matrix.pop(:absent, :missing, :ignore, :no_name) -%> + +<%= test_matrix.push(:absent, :missing, :ignore, :has_name) -%> +<%= test_matrix.push(:absent, :missing, :ignore, :has_name, :pass) -%> +<%= + # No action + name = true + compile('templates/puppet/test~absent~no_action.erb') +-%> +<%= test_matrix.pop(:absent, :missing, :ignore, :has_name, :pass) -%> + +<%= test_matrix.push(:absent, :missing, :ignore, :has_name, :fail) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:absent, :missing, :ignore, :has_name, :fail) -%> +<%= test_matrix.pop(:absent, :missing, :ignore, :has_name) -%> +<%= test_matrix.pop(:absent, :missing) -%> + +<%= test_matrix.push(:absent, :exists) -%> +<%= test_matrix.push(:absent, :exists, :ignore, :no_name) -%> +<%= test_matrix.push(:absent, :exists, :ignore, :no_name, :pass) -%> +<%= + # Delete resource + name = false + compile('templates/puppet/test~absent~delete.erb') +-%> +<%= test_matrix.pop(:absent, :exists, :ignore, :no_name, :pass) -%> + +<%= test_matrix.push(:absent, :exists, :ignore, :no_name, :fail) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:absent, :exists, :ignore, :no_name, :fail) -%> +<%= test_matrix.pop(:absent, :exists, :ignore, :no_name) -%> + +<%= test_matrix.push(:absent, :exists, :ignore, :has_name) -%> +<%= test_matrix.push(:absent, :exists, :ignore, :has_name, :pass) -%> +<%= + # Delete resource + name = true + compile('templates/puppet/test~absent~delete.erb') +-%> +<%= test_matrix.pop(:absent, :exists, :ignore, :has_name, :pass) -%> + +<%= test_matrix.push(:absent, :exists, :ignore, :has_name, :fail) -%> + # TODO(nelsonjr): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } +<%= test_matrix.pop(:absent, :exists, :ignore, :has_name, :fail) -%> +<%= test_matrix.pop(:absent, :exists, :ignore, :has_name) -%> +<%= test_matrix.pop(:absent, :exists) -%> +<%= test_matrix.pop(:absent) -%> +<% end -%> + + context '#flush' do + subject do + Puppet::Type.type(:<%= object.out_name -%>).new( +<% unless object.virtual -%> + ensure: :present, +<% end -%> + name: 'my-name' + ).provider + end + context 'no-op' do + it { subject.flush } + end + context 'modified object' do + before do + subject.dirty :some_property, 'current', 'newvalue' + end + context 'no-op if created' do + before { subject.instance_variable_set(:@created, true) } + it { expect { subject.flush }.not_to raise_error } + end + context 'no-op if deleted' do + before { subject.instance_variable_set(:@deleted, true) } + it { expect { subject.flush }.not_to raise_error } + end +<%= lines_before(lines(indent(get_code_multiline(tests, + %w[flush cases]), 6))) -%> + end + end +<% unless object.exports.nil? -%> +<% + # Nested Object properties do not work properly with exports tests. + # TODO(alexstephen): Custom types will fix this. + fetched = object.exported_properties.map do |p| + if p.is_a? Api::Type::FetchedExternal + object.all_user_properties.select { |j| j.name == p.name }[0] + end + end + + has_nested = !fetched.select { |p| p.is_a? Api::Type::NestedObject }.empty? +-%> +<% unless has_nested -%> + + context '#exports' do + context 'exports all properties' do + let(:resource1) { create_type 1 } + before do +<% # If the object has mandatory resource references, e.g. a network require a + # region to exist, then we need to populate cache with a resource so the + # object under test can find and retrieve relevant information, e.g. the + # dependent resource selfLink. + object.all_user_properties.select(&:required) + .select { |p| p.is_a?(Api::Type::ResourceRef) }.each do |ref| -%> +<% ref_underscore_name = Google::StringUtils.underscore(ref.resource) -%> +<%= lines(indent("prefetch_#{ref_underscore_name}", 8)) -%> +<% end # object.all_user_properties....ResourceRef -%> + expect_network_get_success 1 + described_class.prefetch(title0: resource1) + end + + subject { resource1.exports } + + let(:expected_results) do + { +<% + export_values = object.exported_properties.map do |p| + "#{p.out_name}: '#{@data_gen.value(p.class, p, 0)}'" + end +-%> +<%= indent_list(export_values, 10) %> + } + end + it { is_expected.to eq(expected_results) } + end + end +<% end # unless has_nested -%> +<% end -%> + + private + +<%= lines(indent(compile('templates/network_mocks.erb'), 2), 1) -%> +<% object.all_resourcerefs.each do |ref| -%> +<% unless object.exports.nil? || !ref.required -%> +<% + prop_ref = ref.resource_ref + ref_underscore_name = Google::StringUtils.underscore(ref.resource) +-%> + # Creates and prefetch type so exports can be resolved without network access. + def prefetch_<%= ref_underscore_name %> + expect_network_get_success_<%= ref_underscore_name %> 1 + + resource = Puppet::Type.type(:<%= prop_ref.out_name -%>).new( + project: 'test project#0 data', + name: 'test name#0 data' + ) + + Puppet::Type.type(:<%= prop_ref.out_name -%>).provider(:google) + .prefetch(resource: resource) + end + +<% end # object.exports.nil? -%> +<% end # object.all_resourcerefs -%> +<%= + lines(indent(compile('templates/puppet/resourceref_expandvars.erb'), 2), 1) +-%> + def create_type(id) + Puppet::Type.type(:<%= object.out_name -%>).new( +<% unless object.virtual -%> + ensure: :present, +<% end -%> +<% + prop_code = [ + 'title: "title#{id - 1}"', + 'credential: "cred#{id - 1}"' + ].concat(@constants.value_assign(object)) +-%> +<%= lines(indent_list(prop_code, 6)) -%> + ) + end + + def expand_variables(template, data, extra_data = {}) + Puppet::Type.type(:<%= object.out_name -%>).provider(:google) + .expand_variables(template, data, extra_data) + end + +<% if object.encoder? -%> + def <%= object.transport.encoder -%>(resource) + Puppet::Type.type(:<%= object.out_name -%>).provider(:google) + .<%= object.transport.encoder -%>(resource) + end + +<% end # object.encoder? -%> +<%= lines(indent(compile_if(tests, %w[expectation_helpers]), 2), 1) -%> +<%= lines(indent(emit_link('collection', collection_url(object), false), 2)) %> +<%= lines(indent(emit_link('self_link', self_link_url(object), false), 2)) -%> + +<% if @constants.value_assign(object).empty? -%> + def uri_data(_id) + {} + end +<% else # value.keys.empty? -%> + # Creates variable test data to comply with self_link URI parameters + def uri_data(id) + { +<%= lines(indent_list(@constants.value_assign(object), 6)) -%> + } + end +<% end # value.keys.empty? -%> +end diff --git a/templates/puppet/puppetlint_spec.rb.erb b/templates/puppet/puppetlint_spec.rb.erb new file mode 100644 index 000000000000..797490fb5df8 --- /dev/null +++ b/templates/puppet/puppetlint_spec.rb.erb @@ -0,0 +1,39 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +require 'spec_helper' + +context 'check examples with puppet-lint' do + let(:examples) { "#{File.dirname(__FILE__)}/../examples" } + + let(:command) { ['puppet-lint', '--fail-on-warnings', examples] } + + subject { Bundle.run(command) } + + it { is_expected.to be_zero } +end + +context 'ensure puppet-lint recognizes bad manifests' do + let(:poor_example) { "#{File.dirname(__FILE__)}/data/poor_example.pp" } + + let(:command) { ['puppet-lint', '--fail-on-warnings', poor_example] } + + subject { Bundle.run(command) } + + it { is_expected.not_to be_zero } +end diff --git a/templates/puppet/resourceref_expandvars.erb b/templates/puppet/resourceref_expandvars.erb new file mode 100644 index 000000000000..69c40f4c54d1 --- /dev/null +++ b/templates/puppet/resourceref_expandvars.erb @@ -0,0 +1,12 @@ +<%# Requires: none -%> +<% object.all_resourcerefs.each do |ref| -%> +<% + prop_ref = ref.resource_ref + ref_underscore_name = Google::StringUtils.underscore(ref.resource) +-%> +def expand_variables_<%= ref_underscore_name -%>(template, data, ext_dat = {}) + Puppet::Type.type(:<%= prop_ref.out_name -%>).provider(:google) + .expand_variables(template, data, ext_dat) +end + +<% end -%> diff --git a/templates/puppet/run-tests-steps.erb b/templates/puppet/run-tests-steps.erb new file mode 100644 index 000000000000..05b91eb7cbe9 --- /dev/null +++ b/templates/puppet/run-tests-steps.erb @@ -0,0 +1,20 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +``` +gem install bundler +bundle install +bundle exec rspec +bundle exec rubocop +``` diff --git a/templates/puppet/spec_helper.rb.erb b/templates/puppet/spec_helper.rb.erb new file mode 100644 index 000000000000..d688fa309203 --- /dev/null +++ b/templates/puppet/spec_helper.rb.erb @@ -0,0 +1,119 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +#---------------------------------------------------------- +# Setup timezone. +# +# Our default timezone is UTC, to avoid local time compromise +# test code seed generation. + +ENV['TZ'] = 'UTC' + +#---------------------------------------------------------- +# Setup code coverage + +require 'simplecov' +SimpleCov.start + +#---------------------------------------------------------- +# Add test path to the search libs + +$LOAD_PATH.unshift(File.expand_path('.')) +<% if spec_stubs? -%> +$LOAD_PATH.unshift(File.expand_path('./spec/stubs')) +<% end # spec_stubs? -%> + +#---------------------------------------------------------- +# Block all network traffic + +require 'network_blocker' + +#---------------------------------------------------------- +# Auto require files + +files = [] +files << 'spec/bundle.rb' +files << 'spec/copyright.rb' +files << 'spec/fake_auth.rb' +files << 'spec/test_constants.rb' +files << File.join('lib', '**', '*.rb') + +# Require all files so we can track them via code coverage +Dir[*files].reject { |p| File.directory? p } + .each do |f| + puts "Auto requiring #{f}" \ + if ENV['RSPEC_DEBUG'] + require f + end + +#---------------------------------------------------------- +# Setup PuppetSpec to allow executing the Puppet manifests from within tests + +require 'puppet' + +module PuppetSpec + puppet_dir = File.dirname(Puppet.method(:settings).source_location[0]) + require File.join(puppet_dir, '../spec/lib/puppet_spec/compiler') + require File.join(puppet_dir, '../spec/lib/puppet_spec/files') +end + +require 'rspec-puppet' + +RSpec.configure do |c| + c.include PuppetSpec::Compiler + c.include PuppetSpec::Files + + c.filter_run_excluding broken: true + + Puppet::Test::TestHelper.initialize + + c.before :all do + Puppet::Test::TestHelper.before_all_tests + end + + c.after :all do + Puppet::Test::TestHelper.after_all_tests + end + + c.before :each do + base = PuppetSpec::Files.tmpdir('tmp_settings') + Puppet[:vardir] = File.join(base, 'var') + Puppet[:confdir] = File.join(base, 'etc') + Puppet[:codedir] = File.join(base, 'code') + Puppet[:logdir] = '$vardir/log' + Puppet[:rundir] = '$vardir/run' + Puppet[:hiera_config] = File.join(base, 'hiera') + + FileUtils.mkdir_p Puppet[:statedir] + + Puppet::Test::TestHelper.before_each_test + end + + c.after :each do + Puppet::Test::TestHelper.after_each_test + Dir.stub(:entries) + PuppetSpec::Files.cleanup + end +end + +require 'mocha/test_unit' + +#---------------------------------------------------------- +# Helper modules + +require 'yaml' diff --git a/templates/puppet/test~absent~delete.erb b/templates/puppet/test~absent~delete.erb new file mode 100644 index 000000000000..6cab172aea82 --- /dev/null +++ b/templates/puppet/test~absent~delete.erb @@ -0,0 +1,64 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% inside_indent = (test_matrix.level + 2) * 2 -%> +<% + config_path = if name + %w[absent exists success title_and_name] + else + %w[absent exists success title_eq_name] + end + + # Build out a graph of objects. + # This graph, including the current object, will be stored in a + # TestObjects instance. + + collector = Dependencies::DependencyGraph.new(@data_gen) + collector.add(object, 0, (name ? :name : :title), {ensure: 'absent'}) + + # Generate the Puppet manifest using the graph of objects above. + # This manifest will be ordered by dependencies. + # The ensure: absent verifies that only dependent resources needed for + # deletion will be created. + resources = manifester.generate_all_objects(collector, object.name, + name ? :name : :title, + ensure: 'absent') + + # Creates the expect_network_* statements required for this test. + # This includes the expect statements for all referenced resources. + expectations = prop_data.delete_before_data(tests, object, + { + path: config_path + %w[before], + exists: true, + has_name: name + }, collector) + + resource_block = resources.flatten(1).uniq.map { |r| lines(r) }.join("\n") + res_name = "#{object.out_name.capitalize}[title0]" + +-%> + before(:each) do +<%= lines(indent(expectations, inside_indent)) -%> + end + + subject do + apply_with_error_check( + <<-MANIFEST +<%= lines(indent(resource_block, inside_indent + 2)) -%> + MANIFEST + ).catalog.resource('<%= res_name -%>') + .provider.ensure + end + + it { is_expected.to eq :absent } diff --git a/templates/puppet/test~absent~no_action.erb b/templates/puppet/test~absent~no_action.erb new file mode 100644 index 000000000000..4a6a266078c0 --- /dev/null +++ b/templates/puppet/test~absent~no_action.erb @@ -0,0 +1,62 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% inside_indent = (test_matrix.level + 2) * 2 -%> +<% + config_path = if name + %w[absent not_exist success title_and_name] + else + %w[absent not_exist success title_eq_name] + end + + # Build out a graph of objects. + # This graph, including the current object, will be stored in a + # TestObjects instance. + collector = Dependencies::DependencyGraph.new(@data_gen) + collector.add(object, 0, (name ? :name : :title), {ensure: 'absent'}) + + + # Generate the Puppet manifest using the graph of objects above. + # This manifest will be ordered by dependencies. + resources = manifester.generate_all_objects(collector, object.name, + name ? :name : :title, + ensure: 'absent') + + # Creates the expect_network_* statements required for this test. + # This includes the expect statements for all referenced resources. + expectations = prop_data.delete_before_data(tests, object, + { + path: config_path + %w[before], + exists: false, + has_name: name + }, collector) + + + resource_block = resources.flatten(1).uniq.map { |r| lines(r) }.join("\n") + res_name = "#{object.out_name.capitalize}[title0]" +-%> + before(:each) do +<%= lines(indent(expectations, inside_indent)) -%> + end + + subject do + apply_with_error_check( + <<-MANIFEST +<%= lines(indent(resource_block, inside_indent + 2)) -%> + MANIFEST + ).catalog.resource('<%= res_name -%>') + .provider.ensure + end + + it { is_expected.to eq :absent } diff --git a/templates/puppet/test~present~create.erb b/templates/puppet/test~present~create.erb new file mode 100644 index 000000000000..d400a8044244 --- /dev/null +++ b/templates/puppet/test~present~create.erb @@ -0,0 +1,93 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% inside_indent = (test_matrix.level + 2) * 2 -%> +<% + config_path = if name + %w[present not_exist success title_and_name] + else + %w[present not_exist success title_eq_name] + end + + # Build out a graph of objects. + collector = Dependencies::DependencyGraph.new(@data_gen) + collector.add(object, 0, (name ? :name : :title), ensure: 'present') + + # Generate the Puppet manifest using the graph of objects above. + # This manifest will be ordered by dependencies. + resources = manifester.generate_all_objects(collector, object.name, + name ? :name : :title, + ensure: 'present') + + + # Creates the expect_network_* statements required for this test. + # This includes the expect statements for all referenced resources. + expect_data = @create_data.create_expect_data(config_path + %w[result], + name, tests, object) + expectations = prop_data.create_before_data(tests, object, + { + path: config_path + %w[before], + has_name: name, + expected_data: expect_data + }, collector) + + # TODO(nelsonjr): Fix test when object references itself as a ResourceRef, + # e.g. a bucket ACL points back to a bucket as a resource ref. When generating + # the references the code it oblivious to that fact and attempt to have + # various objects created to satisfy the dependency. That leads to collision + # of object seed==0, as well as not having objects being tested created to + # satisty dependency. + references_self_type = false + collector.each do |obj| + references_self_type = true if obj.parent && \ + obj.parent.__resource == obj.object + end + + resource_block = resources.flatten(1).uniq.map { |r| lines(r) }.join("\n") + res_name = "#{object.out_name.capitalize}[title0]" +-%> + before(:each) do +<%= lines(indent(expectations, inside_indent)) -%> + end + + subject do + apply_with_error_check( + <<-MANIFEST +<%= lines(indent(resource_block, inside_indent + 2)) -%> + MANIFEST +<%= + res_name = "#{object.out_name.capitalize}[title0]" + lines(format([ + [").catalog.resource('#{res_name}').provider.ensure"], + [ + ").catalog.resource('#{res_name}').provider", + indent('.ensure', 2) + ], + [ + ").catalog.resource('#{res_name}')", + indent('.provider.ensure', 2) + ], + ], inside_indent)) +-%> + end + +<% # TODO(nelsonjr): Temporarily disable the tests where an object has a + # reference to its own type -%> +<% if references_self_type -%> + it 'was expected to be present', broken: true do + pending('Implement tests where object references its own type.') + end +<% else # references_self_type -%> + it { is_expected.to eq :present } +<% end # references_self_type -%> diff --git a/templates/puppet/test~present~no_changes.erb b/templates/puppet/test~present~no_changes.erb new file mode 100644 index 000000000000..739de93fc146 --- /dev/null +++ b/templates/puppet/test~present~no_changes.erb @@ -0,0 +1,122 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% inside_indent = (test_matrix.level + 2) * 2 -%> +<% + config_path = if name + %w[present exist success title_and_name before] + else + %w[present exist success title_eq_name before] + end + + cust_before = get_code_multiline(tests, config_path) + + # Build out a graph of objects. + collector = Dependencies::DependencyGraph.new(@data_gen) + (0..2).each do |index| + collector.add(object, index, (name ? :name : :title), ensure: 'present') + end + + # Generate the Puppet manifest using the graph of objects above. + # This manifest will be ordered by dependencies. + resources = manifester.generate_all_objects(collector, object.name, + name ? :name : :title, + ensure: 'present') + + + # Creates the expect_network_* statements required for this test. + # This includes the expect statements for all referenced resources. + expectations = [ + # Generate network expectations for 3 objects + (1..3).map do |idx| + prop_data.create_expectation('expect_network_get_success', name, object, + inside_indent, [], idx) + end, + prop_data.create_resource_ref_get_success(object, collector, inside_indent) + ].flatten.compact.uniq + + resource_block = resources.flatten(1).uniq.map { |r| lines(r) }.join("\n") + res_name = "#{object.out_name.capitalize}[title0]" + + # TODO(nelsonjr): Fix test when object references itself as a ResourceRef, + # e.g. a bucket ACL points back to a bucket as a resource ref. When generating + # the references the code it oblivious to that fact and attempt to have + # various objects created to satisfy the dependency. That leads to collision + # of object seed==0, as well as not having objects being tested created to + # satisty dependency. + references_self_type = false + collector.each do |obj| + references_self_type = true if obj.parent && \ + obj.parent.__resource == obj.object + end +-%> +<% if !cust_before.nil? -%> + before do +<%= lines(indent(cust_before, inside_indent)) -%> + end +<% else -%> + before do + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) +<%= lines(indent(expectations, inside_indent)) -%> + end +<% end -%> + + let(:catalog) do + apply_with_error_check( + <<-MANIFEST +<%= lines(indent(resource_block, inside_indent + 2)) -%> + MANIFEST + ).catalog + end +<% (0..2).each do |index| -%> +<% res_name = "#{object.out_name.capitalize}[title#{index}]" -%> + + context '<%= res_name -%>' do + subject do +<%= + lines(format([ + ["catalog.resource('#{res_name}').provider"], + [ + "catalog.resource('#{res_name}')", + " .provider" + ] + ], 16)) +-%> + end + +<% if references_self_type -%> + it 'was expected to be present', broken: true do + pending('Implement tests where object references its own type.') + end +<% else -%> +<% + object.properties.each do |prop| + if !name && prop.name == 'name' +-%> + it { is_expected.to have_attributes(name: 'title<%= index -%>') } +<% + next + end +-%> +<%= lines(indent(@property.property(prop, index, + @data_gen.comparator(prop), + @data_gen.value(prop.class, prop, index), + inside_indent), + inside_indent)) -%> +<% end # if !name-%> +<% end # if references_self_type -%> + end +<% end # index -%> diff --git a/templates/puppet/type.erb b/templates/puppet/type.erb new file mode 100644 index 000000000000..f89eb699c075 --- /dev/null +++ b/templates/puppet/type.erb @@ -0,0 +1,154 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile('templates/license.erb') -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% + requires = generate_requires(object.all_user_properties) + requires << 'puppet' + unless object.exports.nil? + requires << 'google/object_store' + end +-%> +<%= lines(emit_requires(requires)) -%> + +<% Google::LOGGER.info "Generating #{object.name}: #{object.out_name}" -%> +Puppet::Type.newtype(:<%= object.out_name -%>) do +<%= format_description(object, 2, '@doc =') %> + + autorequire(:gauth_credential) do + credential = self[:credential] + raise "#{ref}: required property 'credential' is missing" if credential.nil? + [credential] + end + +<% + unless object.parameters.nil? + object.parameters.each do |param| + if param.class <= Api::Type::ResourceRef + Google::LOGGER.info \ + "Generating autorequire #{object.name}.#{param.name}: #{param.type}" +-%> + autorequire(:<%= param.out_type -%>) do + reference = self[:<%= param.out_name -%>] +<% if param.required -%> +<%= + lines(format( + [ + ["raise \"\#{ref} required property '#{param.out_name}' is missing\"", + 'if reference.nil?'].join(' '), + [ + "raise \"\#{ref} required property '#{param.out_name}' is missing\" \\", + indent('if reference.nil?', 2) + ] + ], 4 + )) +-%> + reference.autorequires +<% else # param.required -%> + reference.autorequires unless reference.nil? +<% end # param.required -%> + end + +<% + end + end + end +-%> +<% unless object.virtual -%> + ensurable + +<% end -%> + newparam :credential do + desc <<-DESC + A gauth_credential name to be used to authenticate with Google Cloud + Platform. + DESC + end +<% if object.all_user_properties.select { |o| o.name == 'project' }.empty? -%> + + newparam(:project) do + desc 'A Google Cloud Platform project to manage.' + end +<% end -%> +<% + # TODO(nelsonjr): Support multiple namevar. If there are 2+ defined we turn + # multi name var off. + identities = if object.__identity.nil? || object.__identity.size != 1 + [] + else + object.__identity + end + if identities.empty? +-%> + + newparam(:name, namevar: true) do + # TODO(nelsona): Make this description to match the key of the object. + desc 'The name of the <%= object.name -%>.' + end +<% end -%> +<% unless object.parameters.nil? -%> +<% object.parameters.each do |p| -%> + +<% Google::LOGGER.info "Generating param #{object.name}.#{p.name}:#{p.type}" -%> +<%= + namevar = identities.include?(p.name) ? 'namevar: true' : nil + + format([ + ["newparam(:#{p.out_name}", namevar, + "parent: #{p.property_type}) do"].compact.join(', '), + (indent_list([ + "newparam(:#{p.out_name}, #{namevar}", + # + 12 = newparam(: + , + indent("parent: #{p.property_type}) do", p.out_name.length + 12) + ], 0) unless namevar.nil?), + indent_list([ + "newparam(:#{p.out_name}", + (indent(namevar, 9) unless namevar.nil?), + indent("parent: #{p.property_type}) do", 9) + ].compact, 0), + ].compact, 2) %> +<%= format_description(p, 4, 'desc') %> +<%= property_body(p) -%> + end +<% end -%> +<% end -%> +<% object.properties.each do |p| + Google::LOGGER.info "Generating #{object.name}.#{p.name}: #{p.type}" +-%> + +<%= + new_property_len = 'newproperty('.length + format([ + ["newproperty(:#{p.out_name}, parent: #{p.property_type}) do"], + [ + "newproperty(:#{p.out_name},", + indent("parent: #{p.property_type}) do", new_property_len) + ], + ], 2) +%> +<%= format_description(p, 4, 'desc', p.output ? "(output only)" : "") %> +<%= property_body(p) -%> + end +<% end -%> +<% unless object.exports.nil? -%> + + # Returns all properties that a provider can export to other resources + def exports + provider.exports + end +<% end -%> +end diff --git a/templates/regional_async.yaml.erb b/templates/regional_async.yaml.erb new file mode 100644 index 000000000000..a586abf98b93 --- /dev/null +++ b/templates/regional_async.yaml.erb @@ -0,0 +1,18 @@ +async: !ruby/object:Api::Async + operation: !ruby/object:Api::Async::Operation + kind: 'compute#operation' + path: 'name' + base_url: 'projects/{{project}}/regions/{{region}}/operations/{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::Async::Result + path: 'targetLink' + status: !ruby/object:Api::Async::Status + path: 'status' + complete: 'DONE' + allowed: + - 'PENDING' + - 'RUNNING' + - 'DONE' + error: !ruby/object:Api::Async::Error + path: 'error/errors' + message: 'message' diff --git a/templates/resourceref_mocks.erb b/templates/resourceref_mocks.erb new file mode 100644 index 000000000000..544e5a3b7bb6 --- /dev/null +++ b/templates/resourceref_mocks.erb @@ -0,0 +1,77 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%# Emits network mocks specifically designed for each resourceref -%> +<%# Requires: none -%> +<% object.all_resourcerefs.each do |ref| -%> +<% + prop_ref = ref.resource_ref + ref_underscore_name = Google::StringUtils.underscore(ref.resource) + ref_index ||= 0 + ref_name = "resource(#{ref_underscore_name},#{ref_index})" +-%> +def expect_network_get_success_<%= ref_underscore_name -%>(id, data = {}) + id_data = data.fetch(:name, '').include?('title') ? 'title' : 'name' + body = load_network_result_<%= ref_underscore_name -%>("success#{id}~" \ + "#{id_data}.yaml") + .to_json + uri = uri_data_<%= ref_underscore_name -%>(id).merge(data) + + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + + debug_network "!! GET #{uri}" + expect(Google::<%= product_ns -%>::Network::Get).to receive(:new) + .with(self_link_<%= ref_underscore_name -%>(uri), + instance_of(Google::FakeAuthorization)) do |args| + debug_network ">> GET #{args}" + request + end +end + +def load_network_result_<%= ref_underscore_name -%>(file) + results = File.join(File.dirname(__FILE__), 'data', 'network', + '<%= prop_ref.out_name -%>', file) + raise "Network result data file #{results}" unless File.exist?(results) + data = YAML.safe_load(File.read(results)) + raise "Invalid network results #{results}" unless data.class <= Hash + data +end + +<% if @constants.value_assign(prop_ref).empty? -%> +def uri_data_<%= ref_underscore_name -%>(_id) + {} +end + +<% else # value.keys.empty? -%> +<%# We can call any variable because all specs are loaded at once -%> +<%# Also, all value holders are global -%> +# Creates variable test data to comply with self_link URI parameters +# Only used for <%= prop_ref.out_name -%> objects +def uri_data_<%= ref_underscore_name -%>(id) + { +<%= lines(indent_list(@constants.value_assign(prop_ref), 4)) -%> + } +end + +<% end # value.keys.empty? -%> +<% + self_link_ref = emit_link("self_link_#{ref_underscore_name}", + self_link_url(prop_ref), + false) + self_link_ref = self_link_ref.gsub("expand_variables", + "expand_variables_#{ref_underscore_name}") +-%> +<%= lines(self_link_ref, 1) -%> +<% end -%> diff --git a/templates/return_if_object.erb b/templates/return_if_object.erb new file mode 100644 index 000000000000..9fbd5dd43689 --- /dev/null +++ b/templates/return_if_object.erb @@ -0,0 +1,45 @@ +<% if object.kind? -%> +# rubocop:disable Metrics/CyclomaticComplexity +def self.return_if_object(response, kind) +<% else # object.kind? -%> +def self.return_if_object(response) +<% end # object.kind? -%> + raise "Bad response: #{response.body}" \ + if response.is_a?(Net::HTTPBadRequest) + raise "Bad response: #{response}" \ + unless response.is_a?(Net::HTTPResponse) + return if response.is_a?(Net::HTTPNotFound) + return if response.is_a?(Net::HTTPNoContent) +<% if object.transport.nil? -%> + result = JSON.parse(response.body) +<% else # object.transport.nil? -%> +<% if object.transport.decoder.nil? -%> + result = JSON.parse(response.body) +<% else # object.transport.decoder.nil? -%> +<% if object.kind? -%> + result = <%= object.transport.decoder -%>(response, kind) +<% else # object.kind? -%> + result = <%= object.transport.decoder -%>(response) +<% end # object.kind? -%> +<% end # object.transport.decoder.nil? -%> +<% end # object.transport.nil? -%> + raise_if_errors result, %w[error errors], 'message' + raise "Bad response: #{response}" unless response.is_a?(Net::HTTPOK) +<% if object.kind? -%> + raise "Incorrect result: #{result['kind']} (expected '#{kind}')" \ + unless result['kind'] == kind +<% end # object.kind? -%> + result +end +<% if object.kind? -%> +# rubocop:enable Metrics/CyclomaticComplexity + +def return_if_object(response, kind) + self.class.return_if_object(response, kind) +end +<% else # object.kind? -%> + +def return_if_object(response) + self.class.return_if_object(response) +end +<% end # object.kind? -%> diff --git a/templates/spec_lib_stub.rb.erb b/templates/spec_lib_stub.rb.erb new file mode 100644 index 000000000000..05501d1a208e --- /dev/null +++ b/templates/spec_lib_stub.rb.erb @@ -0,0 +1,19 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a stub file to resolve external dependencies for the file with the +# same relative path from spec/stubs. +# +# Note that all external dependencies have to be mocked when running tests, so +# anything the code requires from this module should be handled by the tests +# that attempt to use it. diff --git a/templates/test_constants.rb.erb b/templates/test_constants.rb.erb new file mode 100644 index 000000000000..fe8ac98aa95e --- /dev/null +++ b/templates/test_constants.rb.erb @@ -0,0 +1,66 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<%= compile 'templates/license.erb' -%> + +<%= compile 'templates/autogen_notice.erb' -%> + +<% +def data_to_constant(name, data) + if data.any? { |d| d.include?(' ') } + [ + "#{name} = [", + indent_list(data, 2), + '].freeze' + ] + else + [ + "#{name} = %w[", + indent_list(data, 2).tr("'", '').tr(',', ''), + '].freeze' + ] + end +end +-%> +<% + var_map = @constants.value_arrays(@api).map do |name, info| + source = info[:source] + if source.size == 1 + [ + "# Constants for: #{source.first.map { |s| s.to_s }.join('.')}", + data_to_constant(name, info[:data]) + ] + else # source.size == 1 + [ + '# Constants for the following objects:', + source.map do |ss| + t = ss.map { |s| s.to_s }.join('.') + "\# - #{t}" + end, + data_to_constant(name, info[:data]) + ] + end # source.size == 1 + end # @constants.value_arrays(@api).each +-%> +module GoogleTests +<%= lines(indent(emit_rubocop(binding, :module, + %w[GoogleTests Constants].join('::'), :disabled), + 2)) -%> + module Constants +<%= lines(indent(var_map.map { |v| lines(v) }.join("\n"), 4)) -%> + end +<%= lines(indent(emit_rubocop(binding, :module, + %w[GoogleTests Constants].join('::'), :enabled), + 2)) -%> +end diff --git a/templates/transport.erb b/templates/transport.erb new file mode 100644 index 000000000000..cdc2676a2f9a --- /dev/null +++ b/templates/transport.erb @@ -0,0 +1,85 @@ +<% if false # the license inside this if block assertains to this file -%> +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +<% end -%> +<% if object.kind? -%> +def self.fetch_resource(resource, self_link, kind) + get_request = ::Google::<%= product_ns -%>::Network::Get.new( + self_link, fetch_auth(resource) + ) + return_if_object get_request.send, kind +end +<% else # object.kind? -%> +def self.fetch_resource(resource, self_link) + get_request = ::Google::<%= product_ns -%>::Network::Get.new( + self_link, fetch_auth(resource) + ) + return_if_object get_request.send +end +<% end # object.kind? -%> + +<% unless object.self_link_query.nil? -%> +def fetch_wrapped_resource(resource, kind, wrap_kind, wrap_path) +<%= + lines(format( + [ + 'self.class.fetch_wrapped_resource(resource, kind, wrap_kind, wrap_path)', + [ + 'self.class.fetch_wrapped_resource(resource, kind, wrap_kind,', + indent('wrap_path)', 34) # 34 = align with ( previous line + ] + ], 2, inside_indent + )) +-%> +end + +def self.fetch_wrapped_resource(resource, kind, wrap_kind, wrap_path) + result = fetch_resource(resource, self_link(resource), wrap_kind) + return if result.nil? || !result.key?(wrap_path) + result = unwrap_resource(result[wrap_path], resource) + return if result.nil? + raise "Incorrect result: #{result['kind']} (expected #{kind})" \ + unless result['kind'] == kind + result +end + +<% unless false?(Google::HashUtils.navigate(config, + %w[provider_helpers visible + unwrap_resource])) -%> +def unwrap_resource(result, resource) + self.class.unwrap_resource(result, resource) +end + +def self.unwrap_resource(result, resource) + query_predicate = unwrap_resource_filter(resource) + matches = result.select do |entry| + query_predicate.all? do |k, v| + entry[k.id2name] == v + end + end + raise "More than 1 result found: #{matches}" if matches.size > 1 + return if matches.empty? + matches.first +end + +<% end # visible:unwrap_resource -%> +<% end # object.self_link_query.nil? -%> +def self.raise_if_errors(response, err_path, msg_field) + errors = ::Google::HashUtils.navigate(response, err_path) + raise_error(errors, msg_field) unless errors.nil? +end + +def self.raise_error(errors, msg_field) + raise IOError, ['Operation failed:', + errors.map { |e| e[msg_field] }.join(', ')].join(' ') +end diff --git a/templates/zonal_async.yaml.erb b/templates/zonal_async.yaml.erb new file mode 100644 index 000000000000..157ebf9340d4 --- /dev/null +++ b/templates/zonal_async.yaml.erb @@ -0,0 +1,18 @@ +async: !ruby/object:Api::Async + operation: !ruby/object:Api::Async::Operation + kind: 'compute#operation' + path: 'name' + base_url: 'projects/{{project}}/zones/{{zone}}/operations/{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::Async::Result + path: 'targetLink' + status: !ruby/object:Api::Async::Status + path: 'status' + complete: 'DONE' + allowed: + - 'PENDING' + - 'RUNNING' + - 'DONE' + error: !ruby/object:Api::Async::Error + path: 'error/errors' + message: 'message' diff --git a/tests/end2end/Gemfile b/tests/end2end/Gemfile new file mode 100644 index 000000000000..fd1211f6d77b --- /dev/null +++ b/tests/end2end/Gemfile @@ -0,0 +1,10 @@ +source 'https://rubygems.org' + +gem 'chef', '>= 13.0.0' + +gem 'google-api-client' +gem 'googleauth' + +gem 'puppet', ENV['PUPPET_GEM_VERSION'] || '>= 4.2.0' + +gem 'rubocop' diff --git a/tests/end2end/Gemfile.lock b/tests/end2end/Gemfile.lock new file mode 100644 index 000000000000..6aed288259c8 --- /dev/null +++ b/tests/end2end/Gemfile.lock @@ -0,0 +1,221 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) + ast (2.3.0) + builder (3.2.3) + chef (13.0.118) + addressable + bundler (>= 1.10) + chef-config (= 13.0.118) + chef-zero (>= 13.0) + diff-lcs (~> 1.2, >= 1.2.4) + erubis (~> 2.7) + ffi-yajl (~> 2.2) + highline (~> 1.6, >= 1.6.9) + iniparse (~> 1.4) + iso8601 (~> 0.9.1) + mixlib-archive (~> 0.4) + mixlib-authentication (~> 1.4) + mixlib-cli (~> 1.7) + mixlib-log (~> 1.3) + mixlib-shellout (~> 2.0) + net-sftp (~> 2.1, >= 2.1.2) + net-ssh (>= 2.9, < 5.0) + net-ssh-multi (~> 1.2, >= 1.2.1) + ohai (~> 13.0) + plist (~> 3.2) + proxifier (~> 1.0) + rspec-core (~> 3.5) + rspec-expectations (~> 3.5) + rspec-mocks (~> 3.5) + rspec_junit_formatter (~> 0.2.0) + serverspec (~> 2.7) + specinfra (~> 2.10) + syslog-logger (~> 1.6) + uuidtools (~> 2.1.5) + chef-config (13.0.118) + addressable + fuzzyurl + mixlib-config (~> 2.0) + mixlib-shellout (~> 2.0) + chef-zero (13.0.0) + ffi-yajl (~> 2.2) + hashie (>= 2.0, < 4.0) + mixlib-log (~> 1.3) + rack (~> 2.0) + uuidtools (~> 2.1) + declarative (0.0.9) + declarative-option (0.1.0) + diff-lcs (1.3) + erubis (2.7.0) + facter (2.4.6) + faraday (0.11.0) + multipart-post (>= 1.2, < 3) + fast_gettext (1.1.0) + ffi (1.9.18) + ffi-yajl (2.3.0) + libyajl2 (~> 1.2) + fuzzyurl (0.9.0) + gettext (3.2.2) + locale (>= 2.0.5) + text (>= 1.3.0) + gettext-setup (0.24) + fast_gettext (~> 1.1.0) + gettext (>= 3.0.2) + locale + google-api-client (0.10.1) + addressable (~> 2.3) + googleauth (~> 0.5) + httpclient (~> 2.7) + hurley (~> 0.1) + memoist (~> 0.11) + mime-types (>= 1.6) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + googleauth (0.5.1) + faraday (~> 0.9) + jwt (~> 1.4) + logging (~> 2.0) + memoist (~> 0.12) + multi_json (~> 1.11) + os (~> 0.9) + signet (~> 0.7) + hashie (3.5.5) + hiera (3.3.1) + highline (1.7.8) + httpclient (2.8.3) + hurley (0.2) + iniparse (1.4.2) + ipaddress (0.8.3) + iso8601 (0.9.1) + json_pure (1.8.6) + jwt (1.5.6) + libyajl2 (1.2.0) + little-plugger (1.1.4) + locale (2.1.2) + logging (2.2.0) + little-plugger (~> 1.1) + multi_json (~> 1.10) + memoist (0.15.0) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mixlib-archive (0.4.1) + mixlib-log + mixlib-authentication (1.4.1) + mixlib-log + mixlib-cli (1.7.0) + mixlib-config (2.2.4) + mixlib-log (1.7.1) + mixlib-shellout (2.2.7) + multi_json (1.12.1) + multipart-post (2.0.0) + net-scp (1.2.1) + net-ssh (>= 2.6.5) + net-sftp (2.1.2) + net-ssh (>= 2.6.5) + net-ssh (4.1.0) + net-ssh-gateway (2.0.0) + net-ssh (>= 4.0.0) + net-ssh-multi (1.2.1) + net-ssh (>= 2.6.5) + net-ssh-gateway (>= 1.2.0) + net-telnet (0.1.1) + ohai (13.0.1) + chef-config (>= 12.5.0.alpha.1, < 14) + ffi (~> 1.9) + ffi-yajl (~> 2.2) + ipaddress + mixlib-cli + mixlib-config (~> 2.0) + mixlib-log (>= 1.7.1, < 2.0) + mixlib-shellout (~> 2.0) + plist (~> 3.1) + systemu (~> 2.6.4) + wmi-lite (~> 1.0) + os (0.9.6) + parser (2.4.0.0) + ast (~> 2.2) + plist (3.3.0) + powerpack (0.1.1) + proxifier (1.0.3) + public_suffix (2.0.5) + puppet (4.10.0) + facter (> 2.0, < 4) + gettext-setup (>= 0.10, < 1) + hiera (>= 2.0, < 4) + json_pure (~> 1.8) + locale (~> 2.1) + rack (2.0.1) + rainbow (2.2.2) + rake + rake (12.0.0) + representable (3.0.3) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.0.1) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-its (1.2.0) + rspec-core (>= 3.0.0) + rspec-expectations (>= 3.0.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + rspec_junit_formatter (0.2.3) + builder (< 4) + rspec-core (>= 2, < 4, != 2.12.0) + rubocop (0.48.1) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + ruby-progressbar (1.8.1) + serverspec (2.38.0) + multi_json + rspec (~> 3.0) + rspec-its + specinfra (~> 2.53) + sfl (2.3) + signet (0.7.3) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (~> 1.5) + multi_json (~> 1.10) + specinfra (2.67.9) + net-scp + net-ssh (>= 2.7, < 5.0) + net-telnet + sfl + syslog-logger (1.6.8) + systemu (2.6.5) + text (1.3.1) + uber (0.1.0) + unicode-display_width (1.2.1) + uuidtools (2.1.5) + wmi-lite (1.0.0) + +PLATFORMS + ruby + +DEPENDENCIES + chef (>= 13.0.0) + google-api-client + googleauth + puppet (>= 4.2.0) + rubocop + +BUNDLED WITH + 1.14.6 diff --git a/tests/end2end/chef_test_templates.rb b/tests/end2end/chef_test_templates.rb new file mode 100644 index 000000000000..065060d8f7f3 --- /dev/null +++ b/tests/end2end/chef_test_templates.rb @@ -0,0 +1,244 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/string_utils' +require 'test_template' + +module Chef + # A template for a test that follows the standard test workflow: + # + # @pre + # - cleanup + # - create + # - create {again} : idempotency + # - delete + # - delete {again} : idempotency + # @post + # + # If 'post' is defined it is attached to the end of the test, in case some + # tests require non-standard cleanup. + # rubocop:disable Metrics/ClassLength + class StandardTest < TestTemplate + private + + DEFAULT_RESOURCE_COUNT = 2 # 1=resource + 1=auth + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def build_plan + init + mod_name = "google-#{Google::StringUtils.underscore(@module)}" + file = Google::StringUtils.underscore(@resource_name || @name) + + # Possible statements for creating an object. + create_change = [ + 'Chef Client finished,', + "#{@affected['create']}/#{@resources['create']}", + 'resources updated' + ].join(' ') + create_no_change = [ + "Chef Client finished, 0/#{@resources['create']}", + 'resources updated' + ].join(' ') + + # Possible statement for deleting an object. + delete_change = [ + 'Chef Client finished,', + "#{@affected['delete']}/#{@resources['delete']}", + 'resources updated' + ].join(' ') + delete_no_change = [ + "Chef Client finished, 0/#{@resources['delete']}", + 'resources updated' + ].join(' ') + + @vars = { + 'name' => @name, + 'phases' => (@pre || []).concat( + [ + { + 'name' => 'cleanup', + 'apply' => [ + { + 'run' => "#{mod_name}::#{@recipes['delete']}", + 'outputs' => [ + [ + "Deleting #{@module}_#{file}[.*]", + "#{@module}_#{file}[.*] action delete (up to date)" + ], + (0..@affected['delete']).map do |index| + ['Chef Client finished,', + "#{index}/#{@resources['delete']} resources", + 'updated'].join(' ') + end + ] + } + ] + }, + { + 'name' => 'create', + 'apply' => [ + { + 'run' => "#{mod_name}::#{@recipes['create']}", + 'outputs' => [ + ["Creating #{@module}_#{file}[.*]"], + [create_change] + ] + } + ] + }, + { + 'name' => 'create{again}', + 'apply' => [ + { + 'run' => "#{mod_name}::#{@recipes['create']}", + 'outputs' => [ + ["#{@module}_#{file}[.*] action create (up to date)"], + [create_no_change] + ] + } + ] + } + ] + ) + } + + @vars['phases'] = @vars['phases'].concat(@change || []) + @vars['phases'] = @vars['phases'].concat( + [ + { + 'name' => 'delete', + 'apply' => [ + { + 'run' => "#{mod_name}::#{@recipes['delete']}", + 'outputs' => [ + ["Deleting #{@module}_#{file}[.*]"], + [delete_change] + ] + } + ] + }, + { + 'name' => 'delete{again}', + 'apply' => [ + { + 'run' => "#{mod_name}::#{@recipes['delete']}", + 'outputs' => [ + ["#{@module}_#{file}[.*] action delete (up to date)"], + [delete_no_change] + ] + } + ] + } + ] + ).concat(@post || []) + + apply_test_environment @vars + + @vars + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity + + # Setup all values with proper defaults. + # This ensures thats all values with be hashes with custom values or + # defaults. + # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/PerceivedComplexity + def init + file = Google::StringUtils.underscore(@resource_name || @name) + + @recipes = setup_hash(@recipes) + @recipes['create'] = "tests~#{file}" unless @recipes['create'] + @recipes['delete'] = "tests~delete_#{file}" unless @recipes['delete'] + + @resources = setup_hash(@resource_count) + @resources['create'] = DEFAULT_RESOURCE_COUNT unless @resources['create'] + @resources['delete'] = DEFAULT_RESOURCE_COUNT unless @resources['delete'] + + @affected = setup_hash(@affected_count) + unless @affected['create'] + @affected['create'] = + DEFAULT_RESOURCE_COUNT - 1 + end + + return if @affected['delete'] + @affected['delete'] = DEFAULT_RESOURCE_COUNT - 1 + end + # rubocop:enable Metrics/CyclomaticComplexity + # rubocop:enable Metrics/PerceivedComplexity + + # This will take in either a partially/fully setup Hash for testing + # or a default value. + # Will return the partially / fully Hash or a fully setup Hash with default + # value. + def setup_hash(item) + return item if item.is_a? ::Hash + { + 'create' => item, + 'delete' => item + } + end + end + + # A template for a test that follows the virtual test workflow: + # + # @pre + # - create + # @post + # + # If 'post' is defined it is attached to the end of the test, in case some + # tests require non-standard cleanup. + class VirtualTest < TestTemplate + private + + # rubocop:disable Metrics/MethodLength + def build_plan + mod_name = "google-#{Google::StringUtils.underscore(@module)}" + file = Google::StringUtils.underscore(@resource_name || @name) + + resources = @resource_count || 2 # 1=resource + 1=auth + affected = @affected_count || 0 # virtual resources are never updated. + changed_resource = \ + "Chef Client finished, #{affected}/#{resources} resources updated" + create_name = @create || "tests~#{file}" + @vars = { + 'name' => @name, + 'phases' => (@pre || []).concat( + [ + { + 'name' => 'create', + 'apply' => [ + { + 'run' => "#{mod_name}::#{create_name}", + 'outputs' => [ + ["#{@module}_#{file}[.*] action create (up to date)"], + [changed_resource] + ] + } + ] + } + ] + ) + } + + apply_test_environment @vars + + @vars + end + # rubocop:enable Metrics/MethodLength + end + # rubocop:enable Metrics/ClassLength +end diff --git a/tests/end2end/chef_tester.rb b/tests/end2end/chef_tester.rb new file mode 100644 index 000000000000..a0d6fc704dfc --- /dev/null +++ b/tests/end2end/chef_tester.rb @@ -0,0 +1,44 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'tester_base' + +module Chef + # Executes all examples against a real Google Cloud Platform project. The + # account requires to have Owner (or Editor) to all resources being tested. + class Tester < TesterBase + def provider + 'chef' + end + + def header + <<-HEADER + _______ _ _ _______ _______ + | |_____| |______ |______ + |_____ | | |______ | + + HEADER + end + + private + + def command(data) + %w[bundle exec chef-client -z --runlist] \ + << "recipe[#{data['run']}]" + end + + def variables(env) + super env + end + end +end diff --git a/tests/end2end/constants.rb b/tests/end2end/constants.rb new file mode 100644 index 000000000000..60e5d4431837 --- /dev/null +++ b/tests/end2end/constants.rb @@ -0,0 +1,20 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module End2End + # Provides constants that can be used for creating end-to-end test examples. + module Constants + TEST_FILE_REGEX = /.*tests.*/ + TEST_FOLDER = '.tests/end2end/data'.freeze + end +end diff --git a/tests/end2end/logger.rb b/tests/end2end/logger.rb new file mode 100644 index 000000000000..852d781e97e0 --- /dev/null +++ b/tests/end2end/logger.rb @@ -0,0 +1,46 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'singleton' + +# Simple logger w/ atomic timestamp +class Logger + include Singleton + + def initialize + @semaphore = Mutex.new + @logged = 0 + end + + def log(provider, product, message) + message.gsub("\n\n", "\n \n") + .split("\n") + .each do |l| + if product.nil? + printf "%-30s %s: %s\n", provider, "[#{timestamp}]", l + else + printf "%-30s %s: %s\n", "#{provider}/#{product}", + "[#{timestamp}]", l + end + end + end + + private + + def timestamp + @semaphore.synchronize do + @logged += 1 + format '%05d', @logged + end + end +end diff --git a/tests/end2end/puppet_test_templates.rb b/tests/end2end/puppet_test_templates.rb new file mode 100644 index 000000000000..2f5bcda8cf05 --- /dev/null +++ b/tests/end2end/puppet_test_templates.rb @@ -0,0 +1,127 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/string_utils' +require 'test_template' + +module Puppet + # A template for a test that follows the standard test workflow: + # + # @pre + # - cleanup + # - create + # - create {again} : idempotency + # @change + # - delete + # - delete {again} : idempotency + # @post + # + # If 'pre' is defined it is attached to the beginning of the test, in case + # some tests require non-standard startup. + # + # If 'post' is defined it is attached to the end of the test, in case some + # tests require non-standard cleanup. + # + # If 'change' is defined it is attached after the create phases. Sometimes, + # tests involve changing a resource before deleting it. + class StandardTest < TestTemplate + private + + # rubocop:disable Metrics/MethodLength + def build_plan + file = Google::StringUtils.underscore(@name) + delete_name = @delete || "delete_#{file}.pp" + create_name = @create || "#{file}.pp" + @vars = { + 'name' => @name, + 'phases' => (@pre || []).concat( + [ + { + 'name' => 'cleanup', + 'apply' => [{ 'run' => delete_name, 'exits' => [0, 2] }] + }, + { + 'name' => 'cleanup{again}', + 'apply' => [{ 'run' => delete_name, 'exits' => 0 }] + }, + { + 'name' => 'create', + 'apply' => [{ 'run' => create_name, 'exits' => 2 }] + }, + { + 'name' => 'create{again}', + 'apply' => [{ 'run' => create_name, 'exits' => 0 }] + } + ] + ) + } + + @vars['phases'] = @vars['phases'].concat(@change || []) + @vars['phases'] = @vars['phases'].concat( + [ + { + 'name' => 'delete', + 'apply' => [{ 'run' => delete_name, 'exits' => 2 }] + }, + { + 'name' => 'delete{again}', + 'apply' => [{ 'run' => delete_name, 'exits' => 0 }] + } + ] + ).concat(@post || []) + + apply_test_environment @vars + + @vars + end + end + + # A template for a test that follows the virtual test workflow: + # + # @pre + # - run + # - run {again} : idempotency + # @post + # + # If 'pre' is defined it is attached to the beginning of the test, in case + # some tests require non-standard startup. + # + # If 'post' is defined it is attached to the end of the test, in case some + # tests require non-standard cleanup. + class VirtualTest < TestTemplate + private + + def build_plan + file = Google::StringUtils.underscore(@name) + @vars = { + 'name' => @name, + 'phases' => (@pre || []).concat( + [ + { + 'name' => 'run', + 'apply' => [{ 'run' => "#{file}.pp", 'exits' => 0 }] + }, + { + 'name' => 'run{again}', + 'apply' => [{ 'run' => "#{file}.pp", 'exits' => 0 }] + } + ] + ).concat(@post || []) + } + + apply_test_environment @vars + + @vars + end + end +end diff --git a/tests/end2end/puppet_tester.rb b/tests/end2end/puppet_tester.rb new file mode 100755 index 000000000000..4f4aecfa3186 --- /dev/null +++ b/tests/end2end/puppet_tester.rb @@ -0,0 +1,46 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'tester_base' +require 'constants' + +module Puppet + # Executes all examples against a real Google Cloud Platform project. The + # account requires to have Owner (or Editor) to all resources being tested. + class Tester < TesterBase + def provider + 'puppet' + end + + def header + <<-HEADER + _____ _ _ _____ _____ _______ _______ + |_____] | | |_____] |_____] |______ | + | |_____| | | |______ | + + HEADER + end + + private + + def command(data) + %w[bundle exec puppet apply --detailed-exitcodes] \ + << File.join('..', '..', 'build', 'puppet', @product.downcase, + End2End::Constants::TEST_FOLDER, data['run']) + end + + def variables(env) + super Hash[env.map { |k, v| ["FACTER_#{k}", v] }] + end + end +end diff --git a/tests/end2end/run b/tests/end2end/run new file mode 120000 index 000000000000..209e0ecbb4bc --- /dev/null +++ b/tests/end2end/run @@ -0,0 +1 @@ +tester.rb \ No newline at end of file diff --git a/tests/end2end/test_template.rb b/tests/end2end/test_template.rb new file mode 100644 index 000000000000..b9c393076d35 --- /dev/null +++ b/tests/end2end/test_template.rb @@ -0,0 +1,52 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'google/string_utils' + +# Basic services for test templates. +class TestTemplate + attr_reader :env # if specified the environment is spread to all tests + attr_reader :pre + attr_reader :post + + def validate + raise "'name' parameter is required" if @name.nil? || @name.empty? + log "#{@name} standard test" + + init if respond_to? :init + build_plan + end + + def [](variable) + return @verifiers if variable == 'verifiers' + @vars[variable] + end + + def key?(variable) + @vars.key?(variable) + end + + private + + def apply_test_environment(vars) + return if @env.nil? + + vars['phases'].map { |p| p['apply'] } + .flatten + .each { |a| a['env'] = @env.clone.merge(a['env'] || {}) } + end + + def log(message) + Logger.instance.log 'end2end', 'parser', message + end +end diff --git a/tests/end2end/tester.rb b/tests/end2end/tester.rb new file mode 100755 index 000000000000..9e70a1c95a73 --- /dev/null +++ b/tests/end2end/tester.rb @@ -0,0 +1,210 @@ +#!/usr/bin/env ruby +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Executes all examples against a real Google Cloud Platform project. The +# account requires to have Owner (or Editor) to all resources being tested. +# +# Usage: tests/end2end/run +# can be of the form: +# - : e.g. puppet:dns only this module +# - : e.g. puppet: all puppet modules +# - : e.g. :dns all DNS providers +# to test a single object: +# - :: e.g. :dns:ManagedZone +# - :: e.g. puppet:dns:ManagedZone +# +# Environment variables: +# - PARALLEL=true|false controls how tests are run + +$LOAD_PATH.unshift File.dirname(__FILE__) +# Add root so we have google/ available +$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..') + +require 'benchmark' +require 'chef_test_templates' +require 'chef_tester' +require 'logger' +require 'puppet_test_templates' +require 'puppet_tester' +require 'singleton' +require 'yaml' + +# A simple class to track execution times. +class Timers + include Singleton + + def initialize + @timers = {} + end + + def measure(context, &block) + @timers[context] = Benchmark.measure(&block) + end + + def print + @timers.each do |k, v| + Logger.instance.log('timer', k.join('/'), + format('%-23s : %s', k.join('/'), + Time.at(v.total).utc.strftime('%H:%M:%S.%L'))) + end + end +end + +module Colors + B_RED = "\e[37;41m".freeze + B_GREEN = "\e[37;42m".freeze + B_YELLOW = "\e[37;43m".freeze + NC = "\e[0m".freeze +end + +def log(provider, product, message) + Logger.instance.log provider, product, message +end + +def bundle_install + Open3.popen2e(%w[bundle install]) do |_, std_out_and_err, thread| + output = Array[*std_out_and_err] + output.each { |line| log 'bundle', 'install', line } + + exit_code = thread.value.exitstatus + raise 'Bundle install failed' unless exit_code.zero? + end +end + +def true?(obj) + obj.to_s == 'true' +end + +def todo?(tests) + tests.any? do |t| + t.tests.any? do |u| + u['phases'].any? { |p| p['apply'].any? { |a| a.key?('todo') } } + end + end +end + +# Placeholder for shared configuration constants. +module Config + # The RUN_ID is available as a variable for the test environments as + # {{run_id}}. + RUN_ID = ENV['RUN_ID'] || (rand * 1_000_000).to_i + log 'end2end', nil, "Run ID: #{RUN_ID}" +end + +# To have the output organized at the end (as it runs in parallel it will be +# mixed between runs you can execute like this: +# +# tests/end2end/run | tee output.log; cat output.log | sort +PARALLEL = true?(ENV['PARALLEL'] || true) +log 'end2end', nil, PARALLEL ? 'Parallel mode' : 'Serial mode' + +Dir.chdir File.dirname(__FILE__) + +# Load YAML files from submodules and plan.yaml in root +plans = Dir[File.join('..', '..', 'products', '**', '*-e2e.yaml')] +plans << 'plan.yaml' if File.exist? 'plan.yaml' + +tests = plans.map do |file| + YAML.safe_load(File.read(file), + [Puppet::Tester, Puppet::StandardTest, Puppet::VirtualTest, + Chef::Tester, Chef::StandardTest, Chef::VirtualTest]) +end.flatten + +tests.each { |t| t.test_matrix = ARGV } + .each { |t| t.validate if t.respond_to?(:validate) } + +# Ensure we are not missing any products +missing = Dir[File.join('..', '..', 'build', '*', '*')] + .reject { |f| f.start_with?('../../build/presubmit/') } + .reject { |f| f.end_with?('/auth') } + .reject { |f| f.end_with?('_bundle') } + .select do |f| + tests.select do |t| + f == File.join('..', '..', 'build', t.provider, t.product) + end.empty? + end + +bad = Colors::B_RED +nc = Colors::NC +puts "#{bad} The following modules are missing: #{missing} #{nc}" unless \ + missing.empty? + +Timers.instance.measure(%w[bundle install]) do + bundle_install +end + +unless ARGV.empty? + tests = tests.reject do |t| + ARGV.select do |a| + parts = a.split(':') + (parts[0].empty? || parts[0] == t.provider) \ + && (parts.length < 2 || parts[1].empty? || parts[1] == t.product) + end.empty? + end +end + +results = nil + +if PARALLEL + # Run everything in parallel. + test_tasks = tests.map { |t| Thread.new { t.test } } + results = test_tasks.map(&:value) +else + results = tests.map(&:test) +end + +if results.nil? || results.empty? + log nil, nil, 'Nothing was selected to be tested' + success = false +else + success = results.reject { |v| v }.empty? +end + +Timers.instance.print + +if success + good = Colors::B_GREEN + nc = Colors::NC + log \ + nil, nil, + [ + "#{good} _______ _ _ _______ _______ _______ _______ _______ #{nc}", + "#{good} |______ | | | | |______ |______ |______ #{nc}", + "#{good} ______| |_____| |_____ |_____ |______ ______| ______| #{nc}", + "#{good} #{nc}" + ].join("\n") + if todo?(tests) + warn = Colors::B_YELLOW + log \ + nil, nil, + "#{warn} There are unaddressed TODO items. Just saying... #{nc}" + end +else + bad = Colors::B_RED + nc = Colors::NC + log \ + nil, nil, + [ + "#{bad} _______ _______ _____ _______ ______ #{nc}", + "#{bad} |______ |_____| | | |______ | \\ #{nc}", + "#{bad} | | | __|__ |_____ |______ |_____/ #{nc}", + "#{bad} #{nc}" + ].join("\n") + if todo?(tests) + warn = Colors::B_YELLOW + log \ + nil, nil, + "#{warn} There are unaddressed TODO items. Just saying... #{nc}" + end +end diff --git a/tests/end2end/tester_base.rb b/tests/end2end/tester_base.rb new file mode 100755 index 000000000000..5c4ce18f2479 --- /dev/null +++ b/tests/end2end/tester_base.rb @@ -0,0 +1,287 @@ +# Copyright 2017 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require 'logger' +require 'open3' + +# Executes all examples against a real Google Cloud Platform project. The +# account requires to have Owner (or Editor) to all resources being tested. +class TesterBase + attr_reader :product + attr_reader :tests + attr_writer :test_matrix + + ALL_VERIFIER_PHASE_NAME = 'ALL'.freeze + ALL_VERIFIER_PHASE_RETURNS = { + 'cleanup' => 1, + 'create' => 0, + 'delete' => 1 + }.freeze + + def provider + raise 'provider must be implemented' + end + + # rubocop:disable Lint/DuplicateMethods + def product + @product.downcase + end + # rubocop:enable Lint/DuplicateMethods + + def header + raise 'header must be implemented' + end + + def test + Timers.instance.measure([provider, product]) do + log_test_start + + raise "No tests on product '#{@product}'" if @tests.nil? || @tests.empty? + + return @tests.reject { |object| skip_if_not_specified(object) } + .map { |object| test_object(object) } + .all? { |v| v } + end + end + + def validate + @tests.each do |t| + if t.respond_to?(:validate) + t.validate + else + Logger.instance.log('end2end', 'parser', "#{t['name']} manual test") + end + end + end + + private + + def log_test_start + log header + log '=' * 80 + log '' + log "Testing #{@product} on #{provider.capitalize}" + log '' + end + + def skip_if_not_specified(object) + return false if @test_matrix.empty? + + # If we have the provider or product specified as wildcard we're good + return false unless @test_matrix.select do |m| + m == "#{provider}:" || m == ":#{product}:" \ + || m == "#{provider}:#{product}:" + end.empty? + + # If not we need a match for :product:object + return false unless @test_matrix.select do |m| + m.end_with?(":#{product}:#{object['name']}") + end.empty? + + log "Skipping object #{object['name']}" + true + end + + def test_object(object) + log "Testing object #{object['name']}" + object['phases'].map do |p| + return false unless phase(p) + verify_phase(object, p) + end + true + end + + def phase(phase) + run_and_log_error do + log "<==== PHASE: #{phase['name']} ====>" + + raise "No tests on phase '#{phase['name']}'" \ + if phase['apply'].nil? || phase['apply'].empty? + + phase['apply'].each do |data| + run phase['name'], data['run'], data, variables(data['env'] || {}) + end + + log "Tests on phase '#{phase['name']}' successful" + end + end + + # A verifier is a command that is run to confirm that the configuration + # manager actually did what it claimed + # + # Format: + # verifiers: + # - phase: + # command: + # exits: + # + # All phases verifiers: If you have a command that is the same on all phases, + # except what it returns based on the phase you can use the special 'ALL' on + # the phase. If you do that the command will be applied to: + # - cleanup: exit=1 + # - create: exit=0 + # - delete: exit=1 + def verify_phase(object, phase) + run_and_log_error do + verifier = get_verifier(object, phase) + return true if verifier.nil? + + command = verifier['command'] + expected_exit = expected_exit(phase, verifier) + + execute( + 'verifier', "verifier(#{phase['name']})", { 'exits' => expected_exit }, + {}, [command.split("\n") + .map(&:strip) + .map { |l| l.gsub('{{run_id}}', Config::RUN_ID.to_s) } + .join(' ')] + ) + end + end + + def get_verifier(object, phase) + return nil if object['verifiers'].nil? + + verifiers = object['verifiers'].select do |v| + [ALL_VERIFIER_PHASE_NAME, phase['name']].include?(v['phase']) + end + + raise 'Cannot specify "ALL" and phase verifiers at the same time' \ + if verifiers.size > 1 + + return nil if verifiers.empty? || skip_all_verifier(verifiers[0], phase) + + verifiers[0] + end + + def skip_all_verifier(verifier, phase) + verifier['phase'] == ALL_VERIFIER_PHASE_NAME \ + && !ALL_VERIFIER_PHASE_RETURNS.key?(phase['name']) + end + + def expected_exit(phase, verifier) + default_exit = verifier['exits'] || 0 + return default_exit unless ALL_VERIFIER_PHASE_RETURNS.key?(phase['name']) + return default_exit unless verifier['phase'] == ALL_VERIFIER_PHASE_NAME + ALL_VERIFIER_PHASE_RETURNS[phase['name']] + end + + def run_and_log_error + begin + yield + rescue StandardError => e + color_log Colors::B_RED, e.to_s + return false + end + + true + end + + def command(_data) + raise 'command has to be implemented' + end + + def variables(env) + env.each do |k, v| + if v.is_a?(String) && v.include?('{{run_id}}') + env[k] = v.gsub('{{run_id}}', Config::RUN_ID.to_s) + end + end + + env + end + + def run(phase, name, data, variables) + execute phase, name, data, variables, command(data) + end + + def execute(phase, name, data, variables, command) + log_run name, variables, command + + Open3.popen2e(variables, *command) do |_, std_out_and_err, thread| + output = Array[*std_out_and_err] + output.each { |line| log "#{phase}(#{name}): #{line}" } + + exit_code = thread.value.exitstatus + + verify_exit_codes (data['exits'] || [0]), exit_code + verify_outputs data['outputs'], output + + color_log(Colors::B_YELLOW, data['todo']) unless data['todo'].nil? + end + end + + def log_run(name, variables, command) + log '-' * 80 + log "Running: #{name}" + log "(variables: #{variables.map { |k, v| "#{k} = #{v}" }.join(', ')})" \ + unless variables.empty? + log command.join(' ') + end + + def verify_exit_codes(expected_exits, exit_code) + return if expected_exits.is_a?(Integer) && expected_exits == exit_code + return if expected_exits.is_a?(Array) && expected_exits.include?(exit_code) + raise ['Unexpected return code:', + "expected=#{expected_exits}, actual=#{exit_code}"].join(' ') + end + + # We need to have at least 1 valid output that matched expected outputs + # The expected outputs is an array of arrays. The outer array is tested with + # AND while the inner array is tested with OR. + # + # So if: + # + # - - "A" + # - "a" + # - - "B" + # - "b" + # + # It requires the output to have both an "A" or "B", upper or lowercase. + def verify_outputs(expected_outputs, output) + return if expected_outputs.nil? || expected_outputs.empty? + expected_outputs.each do |eo_and| + found = false + output.each do |o| + eo_and.each do |eo_or| + found = output_matches?(eo_or, o) + break if found + end + + break if found + end + raise "None of expected outputs not found: #{eo_and}" unless found + end + end + + def output_matches?(eo_or, o) + return true if o.include?(eo_or) + + # If the value is a regular expression, try to match it + return true if Regexp.new([ + '.*', + eo_or.gsub('[', '\\[').gsub(']', '\\]').gsub('(', '\\(').gsub(')', '\\)'), + '.*' + ].join).match(o) + + false + end + + def color_log(color, message) + log [color, ' ', message, ' ', Colors::NC].join + end + + def log(message) + Logger.instance.log(provider, product, message) + end +end