diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3567c50..f0dd6aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -163,6 +163,8 @@ chef-codegen: * libraries/google/compute/property/region_deprecated.rb * libraries/google/compute/property/region_name.rb * libraries/google/compute/property/region_selflink.rb + * libraries/google/compute/property/router_advertised_ip_ranges.rb + * libraries/google/compute/property/router_bgp.rb * libraries/google/compute/property/snapshot_snapshot_encryption_key.rb * libraries/google/compute/property/snapshot_source_disk_encryption_key.rb * libraries/google/compute/property/sslcertificate_selflink.rb @@ -328,6 +330,7 @@ chef-codegen: * resources/network.rb * resources/region.rb * resources/route.rb + * resources/router.rb * resources/snapshot.rb * resources/ssl_certificate.rb * resources/subnetwork.rb @@ -475,6 +478,12 @@ chef-codegen: * spec/data/network/gcompute_route/success2~title.yaml * spec/data/network/gcompute_route/success3~name.yaml * spec/data/network/gcompute_route/success3~title.yaml + * spec/data/network/gcompute_router/success1~name.yaml + * spec/data/network/gcompute_router/success1~title.yaml + * spec/data/network/gcompute_router/success2~name.yaml + * spec/data/network/gcompute_router/success2~title.yaml + * spec/data/network/gcompute_router/success3~name.yaml + * spec/data/network/gcompute_router/success3~title.yaml * spec/data/network/gcompute_snapshot/success1~name.yaml * spec/data/network/gcompute_snapshot/success1~title.yaml * spec/data/network/gcompute_snapshot/success2~name.yaml @@ -563,6 +572,7 @@ chef-codegen: * spec/network_spec.rb * spec/region_spec.rb * spec/route_spec.rb + * spec/router_spec.rb * spec/snapshot_spec.rb * spec/spec_helper.rb * spec/ssl_certificate_spec.rb diff --git a/README.md b/README.md index 706b55f..39d0288 100644 --- a/README.md +++ b/README.md @@ -312,6 +312,8 @@ For complete details of the authentication cookbook, visit the sending virtual machine's routing table will be dropped. A Route resource must have exactly one specification of either nextHopGateway, nextHopInstance, nextHopIp, or nextHopVpnTunnel. +* [`gcompute_router`](#gcompute_router) - + Represents a Router resource. * [`gcompute_snapshot`](#gcompute_snapshot) - Represents a Persistent Disk Snapshot resource. Use snapshots to back up data from your persistent disks. Snapshots are @@ -4321,6 +4323,144 @@ Set the `r_label` 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" +### gcompute_router +Represents a Router resource. + + +#### Example + +```ruby +# Router requires a network and a region, so define them in your recipe: +# - gcompute_network 'my-network' do ... end +# - gcompute_region 'some-region' do ... end +gcompute_router 'my-router' do + action :create + bgp( + asn 64514 + advertise_mode 'CUSTOM' + advertised_groups ['ALL_SUBNETS'] + advertised_ip_ranges [ + { + range '1.2.3.4' + } + { + range '6.7.0.0/16' + } + ] + ) + network 'my-network' + region 'some-region' + project ENV['PROJECT'] # ex: 'my-test-project' + credential 'mycred' +end + +``` + +#### Reference + +```ruby +gcompute_router 'id-for-resource' do + bgp { + advertise_mode 'DEFAULT' or 'CUSTOM', + advertised_groups [ + string, + ... + ], + advertised_ip_ranges [ + { + description string, + range string, + }, + ... + ], + asn integer, + } + creation_timestamp time + description string + id integer + name string + network reference to gcompute_network + region reference to gcompute_region + project string + credential reference to gauth_credential +end +``` + +#### Actions + +* `create` - + Converges the `gcompute_router` resource into the final + state described within the block. If the resource does not exist, Chef will + attempt to create it. +* `delete` - + Ensures the `gcompute_router` resource is not present. + If the resource already exists Chef will attempt to delete it. + +#### Properties + +* `id` - + Output only. The unique identifier for the resource. + +* `creation_timestamp` - + Output only. Creation timestamp in RFC3339 text format. + +* `name` - + Required. Name of 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. + +* `description` - + An optional description of this resource. + +* `network` - + Required. A reference to the network to which this router belongs. + +* `bgp` - + BGP information specific to this router. + +* `bgp/asn` + Required. Local BGP Autonomous System Number (ASN). Must be an RFC6996 + private ASN, either 16-bit or 32-bit. The value will be fixed for + this router resource. All VPN tunnels that link to this router + will have the same local ASN. + +* `bgp/advertise_mode` + User-specified flag to indicate which mode to use for advertisement. + Valid values of this enum field are: DEFAULT, CUSTOM + +* `bgp/advertised_groups` + User-specified list of prefix groups to advertise in custom mode. + This field can only be populated if advertiseMode is CUSTOM and + is advertised to all peers of the router. These groups will be + advertised in addition to any specified prefixes. Leave this field + blank to advertise no custom groups. + This enum field has the one valid value: ALL_SUBNETS + +* `bgp/advertised_ip_ranges` + User-specified list of individual IP ranges to advertise in + custom mode. This field can only be populated if advertiseMode + is CUSTOM and is advertised to all peers of the router. These IP + ranges will be advertised in addition to any specified groups. + Leave this field blank to advertise no custom IP ranges. + +* `bgp/advertised_ip_ranges[]/range` + The IP range to advertise. The value must be a + CIDR-formatted string. + +* `bgp/advertised_ip_ranges[]/description` + User-specified description for the IP range. + +* `region` - + Required. Region where the router resides. + +#### Label +Set the `r_label` 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" + ### gcompute_snapshot Represents a Persistent Disk Snapshot resource. diff --git a/libraries/google/compute/property/router_advertised_ip_ranges.rb b/libraries/google/compute/property/router_advertised_ip_ranges.rb new file mode 100644 index 0000000..a448759 --- /dev/null +++ b/libraries/google/compute/property/router_advertised_ip_ranges.rb @@ -0,0 +1,157 @@ +# Copyright 2018 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 Magic Modules 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. +# +# ---------------------------------------------------------------------------- + +require 'google/compute/property/array' +module Google + module Compute + module Data + # A class to manage data for advertised_ip_ranges for router. + class RouteAdverIpRange + include Comparable + + attr_reader :range + attr_reader :description + + def to_json(_arg = nil) + { + 'range' => range, + 'description' => description + }.reject { |_k, v| v.nil? }.to_json + end + + def to_s + { + range: range.to_s, + description: description.to_s + }.map { |k, v| "#{k}: #{v}" }.join(', ') + end + + def ==(other) + return false unless other.is_a? RouteAdverIpRange + 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? RouteAdverIpRange + 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 + + def compare_fields(other) + [ + { self: range, other: other.range }, + { self: description, other: other.description } + ] + end + end + + # Manages a RouteAdverIpRange nested object + # Data is coming from the GCP API + class RouteAdverIpRangeApi < RouteAdverIpRange + def initialize(args) + @range = Google::Compute::Property::String.api_parse(args['range']) + @description = + Google::Compute::Property::String.api_parse(args['description']) + end + end + + # Manages a RouteAdverIpRange nested object + # Data is coming from the Chef catalog + class RouteAdverIpRangeCatalog < RouteAdverIpRange + def initialize(args) + @range = Google::Compute::Property::String.catalog_parse(args[:range]) + @description = + Google::Compute::Property::String.catalog_parse(args[:description]) + end + end + end + + module Property + # A class to manage input to advertised_ip_ranges for router. + class RouteAdverIpRange + def self.coerce + lambda do |x| + ::Google::Compute::Property::RouteAdverIpRange.catalog_parse(x) + end + end + + # Used for parsing Chef catalog + def self.catalog_parse(value) + return if value.nil? + return value if value.is_a? Data::RouteAdverIpRange + Data::RouteAdverIpRangeCatalog.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::RouteAdverIpRange + Data::RouteAdverIpRangeApi.new(value) + end + end + + # A Chef property that holds an integer + class RouteAdverIpRangeArray < Google::Compute::Property::Array + def self.coerce + lambda do |x| + ::Google::Compute::Property::RouteAdverIpRangeArray.catalog_parse(x) + end + end + + # Used for parsing Chef catalog + def self.catalog_parse(value) + return if value.nil? + return RouteAdverIpRange.catalog_parse(value) \ + unless value.is_a?(::Array) + value.map { |v| RouteAdverIpRange.catalog_parse(v) } + end + + # Used for parsing GCP API responses + def self.api_parse(value) + return if value.nil? + return RouteAdverIpRange.api_parse(value) \ + unless value.is_a?(::Array) + value.map { |v| RouteAdverIpRange.api_parse(v) } + end + end + end + end +end diff --git a/libraries/google/compute/property/router_bgp.rb b/libraries/google/compute/property/router_bgp.rb new file mode 100644 index 0000000..31659f0 --- /dev/null +++ b/libraries/google/compute/property/router_bgp.rb @@ -0,0 +1,154 @@ +# Copyright 2018 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 Magic Modules 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. +# +# ---------------------------------------------------------------------------- + +module Google + module Compute + module Data + # A class to manage data for bgp for router. + class RouterBgp + include Comparable + + attr_reader :asn + attr_reader :advertise_mode + attr_reader :advertised_groups + attr_reader :advertised_ip_ranges + + def to_json(_arg = nil) + { + 'asn' => asn, + 'advertiseMode' => advertise_mode, + 'advertisedGroups' => advertised_groups, + 'advertisedIpRanges' => advertised_ip_ranges + }.reject { |_k, v| v.nil? }.to_json + end + + def to_s + { + asn: asn.to_s, + advertise_mode: advertise_mode.to_s, + advertised_groups: advertised_groups.to_s, + advertised_ip_ranges: ['[', + advertised_ip_ranges.map(&:to_json).join(', '), + ']'].join(' ') + }.map { |k, v| "#{k}: #{v}" }.join(', ') + end + + def ==(other) + return false unless other.is_a? RouterBgp + 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? RouterBgp + 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 + + def compare_fields(other) + [ + { self: asn, other: other.asn }, + { self: advertise_mode, other: other.advertise_mode }, + { self: advertised_groups, other: other.advertised_groups }, + { self: advertised_ip_ranges, other: other.advertised_ip_ranges } + ] + end + end + + # Manages a RouterBgp nested object + # Data is coming from the GCP API + class RouterBgpApi < RouterBgp + def initialize(args) + @asn = Google::Compute::Property::Integer.api_parse(args['asn']) + @advertise_mode = + Google::Compute::Property::Enum.api_parse(args['advertiseMode']) + @advertised_groups = Google::Compute::Property::StringArray.api_parse( + args['advertisedGroups'] + ) + @advertised_ip_ranges = + Google::Compute::Property::RouteAdverIpRangeArray.api_parse( + args['advertisedIpRanges'] + ) + end + end + + # Manages a RouterBgp nested object + # Data is coming from the Chef catalog + class RouterBgpCatalog < RouterBgp + def initialize(args) + @asn = Google::Compute::Property::Integer.catalog_parse(args[:asn]) + @advertise_mode = + Google::Compute::Property::Enum.catalog_parse(args[:advertise_mode]) + @advertised_groups = + Google::Compute::Property::StringArray.catalog_parse( + args[:advertised_groups] + ) + @advertised_ip_ranges = + Google::Compute::Property::RouteAdverIpRangeArray.catalog_parse( + args[:advertised_ip_ranges] + ) + end + end + end + + module Property + # A class to manage input to bgp for router. + class RouterBgp + def self.coerce + ->(x) { ::Google::Compute::Property::RouterBgp.catalog_parse(x) } + end + + # Used for parsing Chef catalog + def self.catalog_parse(value) + return if value.nil? + return value if value.is_a? Data::RouterBgp + Data::RouterBgpCatalog.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::RouterBgp + Data::RouterBgpApi.new(value) + end + end + end + end +end diff --git a/resources/router.rb b/resources/router.rb new file mode 100644 index 0000000..ece4b10 --- /dev/null +++ b/resources/router.rb @@ -0,0 +1,405 @@ +# Copyright 2018 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 Magic Modules 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. +# +# ---------------------------------------------------------------------------- + +# Add our google/ lib +$LOAD_PATH.unshift ::File.expand_path('../libraries', ::File.dirname(__FILE__)) + +require 'chef/resource' +require 'google/compute/network/delete' +require 'google/compute/network/get' +require 'google/compute/network/post' +require 'google/compute/network/put' +require 'google/compute/property/enum' +require 'google/compute/property/integer' +require 'google/compute/property/network_selflink' +require 'google/compute/property/region_name' +require 'google/compute/property/router_advertised_ip_ranges' +require 'google/compute/property/router_bgp' +require 'google/compute/property/string' +require 'google/compute/property/string_array' +require 'google/compute/property/time' +require 'google/hash_utils' + +module Google + module GCOMPUTE + # A provider to manage Google Compute Engine resources. + class Router < Chef::Resource + resource_name :gcompute_router + + property :id, + Integer, + coerce: ::Google::Compute::Property::Integer.coerce, + desired_state: true + property :creation_timestamp, + Time, + coerce: ::Google::Compute::Property::Time.coerce, + desired_state: true + property :r_label, + String, + coerce: ::Google::Compute::Property::String.coerce, + name_property: true, desired_state: true + property :description, + String, + coerce: ::Google::Compute::Property::String.coerce, + desired_state: true + property :network, + [String, ::Google::Compute::Data::NetwoSelfLinkRef], + coerce: ::Google::Compute::Property::NetwoSelfLinkRef.coerce, + desired_state: true + property :bgp, + [Hash, ::Google::Compute::Data::RouterBgp], + coerce: ::Google::Compute::Property::RouterBgp.coerce, + desired_state: true + property :region, + [String, ::Google::Compute::Data::RegionNameRef], + coerce: ::Google::Compute::Property::RegionNameRef.coerce, + desired_state: true + + property :credential, String, desired_state: false, required: true + property :project, String, desired_state: false, required: true + + # 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 + + action :create do + fetch = fetch_resource(@new_resource, self_link(@new_resource), + 'compute#router') + if fetch.nil? + converge_by "Creating gcompute_router[#{new_resource.name}]" do + # 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" } + create_req = ::Google::Compute::Network::Post.new( + collection(@new_resource), fetch_auth(@new_resource), + 'application/json', resource_to_request + ) + @new_resource.__fetched = + wait_for_operation create_req.send, @new_resource + end + else + @current_resource = @new_resource.clone + @current_resource.id = + ::Google::Compute::Property::Integer.api_parse(fetch['id']) + @current_resource.creation_timestamp = + ::Google::Compute::Property::Time.api_parse( + fetch['creationTimestamp'] + ) + @current_resource.description = + ::Google::Compute::Property::String.api_parse( + fetch['description'] + ) + @current_resource.bgp = + ::Google::Compute::Property::RouterBgp.api_parse(fetch['bgp']) + @new_resource.__fetched = fetch + + update + end + end + + action :delete do + fetch = fetch_resource(@new_resource, self_link(@new_resource), + 'compute#router') + unless fetch.nil? + converge_by "Deleting gcompute_router[#{new_resource.name}]" do + delete_req = ::Google::Compute::Network::Delete.new( + self_link(@new_resource), fetch_auth(@new_resource) + ) + wait_for_operation delete_req.send, @new_resource + end + end + end + + # TODO(nelsonjr): Add actions :manage and :modify + + def exports + { + self_link: __fetched['selfLink'] + } + end + + private + + action_class do + def resource_to_request + request = { + kind: 'compute#router', + name: new_resource.r_label, + description: new_resource.description, + network: new_resource.network, + bgp: new_resource.bgp, + region: new_resource.region + }.reject { |_, v| v.nil? } + request.to_json + end + + 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" } + update_req = + ::Google::Compute::Network::Put.new(self_link(@new_resource), + fetch_auth(@new_resource), + 'application/json', + resource_to_request) + wait_for_operation update_req.send, @new_resource + end + end + + def self.fetch_export(resource, type, id, property) + return if id.nil? + resource.resources("#{type}[#{id}]").exports[property] + end + + def self.resource_to_hash(resource) + { + project: resource.project, + name: resource.r_label, + kind: 'compute#router', + id: resource.id, + creation_timestamp: resource.creation_timestamp, + description: resource.description, + network: resource.network, + bgp: resource.bgp, + region: resource.region + }.reject { |_, v| v.nil? } + end + + # 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 + + def fetch_auth(resource) + self.class.fetch_auth(resource) + end + + def self.fetch_auth(resource) + resource.resources("gauth_credential[#{resource.credential}]") + .authorization + end + + def fetch_resource(resource, self_link, kind) + self.class.fetch_resource(resource, self_link, kind) + end + + def debug(message) + Chef::Log.debug(message) + end + + def self.collection(data) + URI.join( + 'https://www.googleapis.com/compute/v1/', + expand_variables( + 'projects/{{project}}/regions/{{region}}/routers', + data + ) + ) + end + + def collection(data) + self.class.collection(data) + end + + def self.self_link(data) + URI.join( + 'https://www.googleapis.com/compute/v1/', + expand_variables( + 'projects/{{project}}/regions/{{region}}/routers/{{name}}', + data + ) + ) + end + + def self_link(data) + self.class.self_link(data) + end + + # rubocop:disable Metrics/CyclomaticComplexity + def self.return_if_object(response, 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) + 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']} (expected '#{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 + + 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 + + def expand_variables(template, var_data, extra_data = {}) + self.class.expand_variables(template, var_data, extra_data) + end + + def fetch_resource(resource, self_link, kind) + self.class.fetch_resource(resource, self_link, kind) + end + + def async_op_url(data, extra_data = {}) + URI.join( + 'https://www.googleapis.com/compute/v1/', + expand_variables( + 'projects/{{project}}/regions/{{region}}/operations/{{op_id}}', + data, extra_data + ) + ) + end + + def wait_for_operation(response, resource) + op_result = return_if_object(response, 'compute#operation') + return if op_result.nil? + status = ::Google::HashUtils.navigate(op_result, %w[status]) + wait_done = wait_for_completion(status, op_result, resource) + fetch_resource( + resource, + URI.parse(::Google::HashUtils.navigate(wait_done, + %w[targetLink])), + 'compute#router' + ) + end + + def wait_for_completion(status, op_result, resource) + op_id = ::Google::HashUtils.navigate(op_result, %w[name]) + op_uri = async_op_url(resource, op_id: op_id) + while status != 'DONE' + debug("Waiting for completion of operation #{op_id}") + raise_if_errors op_result, %w[error errors], 'message' + sleep 1.0 + raise "Invalid result '#{status}' on gcompute_router." \ + unless %w[PENDING RUNNING DONE].include?(status) + op_result = fetch_resource(resource, op_uri, 'compute#operation') + status = ::Google::HashUtils.navigate(op_result, %w[status]) + end + op_result + end + + def raise_if_errors(response, err_path, msg_field) + self.class.raise_if_errors(response, err_path, msg_field) + end + + def self.fetch_resource(resource, self_link, kind) + get_request = ::Google::Compute::Network::Get.new( + self_link, fetch_auth(resource) + ) + return_if_object get_request.send, kind + end + + 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 + end + end + end +end diff --git a/spec/data/network/gcompute_router/success1~name.yaml b/spec/data/network/gcompute_router/success1~name.yaml new file mode 100644 index 0000000..527c193 --- /dev/null +++ b/spec/data/network/gcompute_router/success1~name.yaml @@ -0,0 +1,54 @@ +# Copyright 2018 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 Magic Modules 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#router +name: test name#0 data +id: 2149500871 +bgp: + asn: 273119946 + advertiseMode: DEFAULT + advertisedGroups: + - uu + - vv + - ww + - xx + advertisedIpRanges: + - range: test range#0 data + description: test description#0 data + - range: test range#1 data + description: test description#1 data + - range: test range#2 data + description: test description#2 data + - range: test range#3 data + description: test description#3 data +creationTimestamp: '2045-05-23T12:08:10+00:00' +description: test description#0 data +network: selflink(resource(network,0)) +project: "'test project#0 data'" +region: test name#0 data +selfLink: selflink(resource(router,0)) diff --git a/spec/data/network/gcompute_router/success1~title.yaml b/spec/data/network/gcompute_router/success1~title.yaml new file mode 100644 index 0000000..dd4d58a --- /dev/null +++ b/spec/data/network/gcompute_router/success1~title.yaml @@ -0,0 +1,54 @@ +# Copyright 2018 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 Magic Modules 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#router +name: title0 +id: 2149500871 +bgp: + asn: 273119946 + advertiseMode: DEFAULT + advertisedGroups: + - uu + - vv + - ww + - xx + advertisedIpRanges: + - range: test range#0 data + description: test description#0 data + - range: test range#1 data + description: test description#1 data + - range: test range#2 data + description: test description#2 data + - range: test range#3 data + description: test description#3 data +creationTimestamp: '2045-05-23T12:08:10+00:00' +description: test description#0 data +network: selflink(resource(network,0)) +project: "'test project#0 data'" +region: test name#0 data +selfLink: selflink(resource(router,0)) diff --git a/spec/data/network/gcompute_router/success2~name.yaml b/spec/data/network/gcompute_router/success2~name.yaml new file mode 100644 index 0000000..d21eecb --- /dev/null +++ b/spec/data/network/gcompute_router/success2~name.yaml @@ -0,0 +1,49 @@ +# Copyright 2018 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 Magic Modules 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#router +name: test name#1 data +id: 4299001743 +bgp: + asn: 546239892 + advertiseMode: DEFAULT + advertisedGroups: + - rr + - ss + - tt + advertisedIpRanges: + - range: test range#1 data + description: test description#1 data + - range: test range#2 data + description: test description#2 data +creationTimestamp: '2120-10-14T00:16:21+00:00' +description: test description#1 data +network: selflink(resource(network,1)) +project: "'test project#1 data'" +region: test name#1 data +selfLink: selflink(resource(router,1)) diff --git a/spec/data/network/gcompute_router/success2~title.yaml b/spec/data/network/gcompute_router/success2~title.yaml new file mode 100644 index 0000000..ac00a86 --- /dev/null +++ b/spec/data/network/gcompute_router/success2~title.yaml @@ -0,0 +1,49 @@ +# Copyright 2018 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 Magic Modules 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#router +name: title1 +id: 4299001743 +bgp: + asn: 546239892 + advertiseMode: DEFAULT + advertisedGroups: + - rr + - ss + - tt + advertisedIpRanges: + - range: test range#1 data + description: test description#1 data + - range: test range#2 data + description: test description#2 data +creationTimestamp: '2120-10-14T00:16:21+00:00' +description: test description#1 data +network: selflink(resource(network,1)) +project: "'test project#1 data'" +region: test name#1 data +selfLink: selflink(resource(router,1)) diff --git a/spec/data/network/gcompute_router/success3~name.yaml b/spec/data/network/gcompute_router/success3~name.yaml new file mode 100644 index 0000000..77dee80 --- /dev/null +++ b/spec/data/network/gcompute_router/success3~name.yaml @@ -0,0 +1,54 @@ +# Copyright 2018 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 Magic Modules 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#router +name: test name#2 data +id: 6448502614 +bgp: + asn: 819359839 + advertiseMode: DEFAULT + advertisedGroups: + - oo + - pp + advertisedIpRanges: + - range: test range#2 data + description: test description#2 data + - range: test range#3 data + description: test description#3 data + - range: test range#4 data + description: test description#4 data + - range: test range#5 data + description: test description#5 data + - range: test range#6 data + description: test description#6 data +creationTimestamp: '2196-03-05T12:24:32+00:00' +description: test description#2 data +network: selflink(resource(network,2)) +project: "'test project#2 data'" +region: test name#2 data +selfLink: selflink(resource(router,2)) diff --git a/spec/data/network/gcompute_router/success3~title.yaml b/spec/data/network/gcompute_router/success3~title.yaml new file mode 100644 index 0000000..6961598 --- /dev/null +++ b/spec/data/network/gcompute_router/success3~title.yaml @@ -0,0 +1,54 @@ +# Copyright 2018 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 Magic Modules 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#router +name: title2 +id: 6448502614 +bgp: + asn: 819359839 + advertiseMode: DEFAULT + advertisedGroups: + - oo + - pp + advertisedIpRanges: + - range: test range#2 data + description: test description#2 data + - range: test range#3 data + description: test description#3 data + - range: test range#4 data + description: test description#4 data + - range: test range#5 data + description: test description#5 data + - range: test range#6 data + description: test description#6 data +creationTimestamp: '2196-03-05T12:24:32+00:00' +description: test description#2 data +network: selflink(resource(network,2)) +project: "'test project#2 data'" +region: test name#2 data +selfLink: selflink(resource(router,2)) diff --git a/spec/router_spec.rb b/spec/router_spec.rb new file mode 100644 index 0000000..f63e4be --- /dev/null +++ b/spec/router_spec.rb @@ -0,0 +1,1643 @@ +# Copyright 2018 Google Inc. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 Magic Modules 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. +# +# ---------------------------------------------------------------------------- + +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 +context 'gcompute_router' do + context 'ensure == present' do + context 'resource exists' do + # Ensure present: resource exists, no change + context 'no changes == no action' do + # Ensure present: resource exists, no change, no name + context 'title == name' do + # Ensure present: resource exists, no change, no name, pass + context 'title == name (pass)' do + before do + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_success 1, + name: 'title0', + region: 'test name#0 data' + expect_network_get_success 2, + name: 'title1', + region: 'test name#1 data' + expect_network_get_success 3, + name: 'title2', + 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_region 1 + expect_network_get_success_region 2 + expect_network_get_success_region 3 + 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. + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + + ChefSpec::SoloRunner.new( + step_into: %w[gcompute_router gcompute_region gcompute_network], + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) + end + + let(:chef_run) do + apply_recipe( + <<-MANIFEST + gcompute_network 'resource(network,0)' do + action :create + n_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_network 'resource(network,1)' do + action :create + n_label 'test name#1 data' + project 'test project#1 data' + credential 'mycred' + end + + gcompute_network 'resource(network,2)' do + action :create + n_label 'test name#2 data' + project 'test project#2 data' + credential 'mycred' + end + + gcompute_region 'resource(region,0)' do + action :create + r_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_region 'resource(region,1)' do + action :create + r_label 'test name#1 data' + project 'test project#1 data' + credential 'mycred' + end + + gcompute_region 'resource(region,2)' do + action :create + r_label 'test name#2 data' + project 'test project#2 data' + credential 'mycred' + end + + gcompute_router 'title0' do + action :create + bgp({ + advertise_mode: 'DEFAULT', + advertised_groups: ['uu', 'vv', 'ww', 'xx'], + advertised_ip_ranges: [ + { + description: 'test description#0 data', + range: 'test range#0 data' + }, + { + description: 'test description#1 data', + range: 'test range#1 data' + }, + { + description: 'test description#2 data', + range: 'test range#2 data' + }, + { + description: 'test description#3 data', + range: 'test range#3 data' + } + ], + asn: 273119946 + }) + description 'test description#0 data' + network 'resource(network,0)' + region 'resource(region,0)' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_router 'title1' do + action :create + bgp({ + advertise_mode: 'DEFAULT', + advertised_groups: ['rr', 'ss', 'tt'], + advertised_ip_ranges: [ + { + description: 'test description#1 data', + range: 'test range#1 data' + }, + { + description: 'test description#2 data', + range: 'test range#2 data' + } + ], + asn: 546239892 + }) + description 'test description#1 data' + network 'resource(network,1)' + region 'resource(region,1)' + project 'test project#1 data' + credential 'mycred' + end + + gcompute_router 'title2' do + action :create + bgp({ + advertise_mode: 'DEFAULT', + advertised_groups: ['oo', 'pp'], + advertised_ip_ranges: [ + { + description: 'test description#2 data', + range: 'test range#2 data' + }, + { + description: 'test description#3 data', + range: 'test range#3 data' + }, + { + description: 'test description#4 data', + range: 'test range#4 data' + }, + { + description: 'test description#5 data', + range: 'test range#5 data' + }, + { + description: 'test description#6 data', + range: 'test range#6 data' + } + ], + asn: 819359839 + }) + description 'test description#2 data' + network 'resource(network,2)' + region 'resource(region,2)' + project 'test project#2 data' + credential 'mycred' + end + 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 + + context 'gcompute_router[title0]' do + subject do + chef_run.find_resource(:gcompute_router, 'title0') + end + + it { is_expected.to have_attributes(r_label: 'title0') } + + it do + is_expected + .to have_attributes(description: 'test description#0 data') + end + + # TODO(alexstephen): Implement resourceref test. + # it 'network' do + # # Add test code here + # end + + # TODO(nelsonjr): Implement complex nested property object test. + # it 'bgp' do + # # Add test code here + # end + + # TODO(alexstephen): Implement resourceref test. + # it 'region' do + # # Add test code here + # end + end + + context 'gcompute_router[title1]' do + subject do + chef_run.find_resource(:gcompute_router, 'title1') + end + + it { is_expected.to have_attributes(r_label: 'title1') } + + it do + is_expected + .to have_attributes(description: 'test description#1 data') + end + + # TODO(alexstephen): Implement resourceref test. + # it 'network' do + # # Add test code here + # end + + # TODO(nelsonjr): Implement complex nested property object test. + # it 'bgp' do + # # Add test code here + # end + + # TODO(alexstephen): Implement resourceref test. + # it 'region' do + # # Add test code here + # end + end + + context 'gcompute_router[title2]' do + subject do + chef_run.find_resource(:gcompute_router, 'title2') + end + + it { is_expected.to have_attributes(r_label: 'title2') } + + it do + is_expected + .to have_attributes(description: 'test description#2 data') + end + + # TODO(alexstephen): Implement resourceref test. + # it 'network' do + # # Add test code here + # end + + # TODO(nelsonjr): Implement complex nested property object test. + # it 'bgp' do + # # Add test code here + # end + + # TODO(alexstephen): Implement resourceref test. + # it 'region' do + # # Add test code here + # end + end + end + + # Ensure present: resource exists, no change, no name, fail + context 'title == name (fail)' do + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } + end + end + + # Ensure present: resource exists, no change, has name + context 'title != name' do + # Ensure present: resource exists, no change, has name, pass + context 'title != name (pass)' do + before do + allow(Time).to receive(:now).and_return( + Time.new(2017, 1, 2, 3, 4, 5) + ) + expect_network_get_success 1, region: 'test name#0 data' + expect_network_get_success 2, region: 'test name#1 data' + expect_network_get_success 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_region 1 + expect_network_get_success_region 2 + expect_network_get_success_region 3 + 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. + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + + ChefSpec::SoloRunner.new( + step_into: %w[gcompute_router gcompute_region gcompute_network], + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) + end + + let(:chef_run) do + apply_recipe( + <<-MANIFEST + gcompute_network 'resource(network,0)' do + action :create + n_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_network 'resource(network,1)' do + action :create + n_label 'test name#1 data' + project 'test project#1 data' + credential 'mycred' + end + + gcompute_network 'resource(network,2)' do + action :create + n_label 'test name#2 data' + project 'test project#2 data' + credential 'mycred' + end + + gcompute_region 'resource(region,0)' do + action :create + r_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_region 'resource(region,1)' do + action :create + r_label 'test name#1 data' + project 'test project#1 data' + credential 'mycred' + end + + gcompute_region 'resource(region,2)' do + action :create + r_label 'test name#2 data' + project 'test project#2 data' + credential 'mycred' + end + + gcompute_router 'title0' do + action :create + bgp({ + advertise_mode: 'DEFAULT', + advertised_groups: ['uu', 'vv', 'ww', 'xx'], + advertised_ip_ranges: [ + { + description: 'test description#0 data', + range: 'test range#0 data' + }, + { + description: 'test description#1 data', + range: 'test range#1 data' + }, + { + description: 'test description#2 data', + range: 'test range#2 data' + }, + { + description: 'test description#3 data', + range: 'test range#3 data' + } + ], + asn: 273119946 + }) + description 'test description#0 data' + network 'resource(network,0)' + r_label 'test name#0 data' + region 'resource(region,0)' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_router 'title1' do + action :create + bgp({ + advertise_mode: 'DEFAULT', + advertised_groups: ['rr', 'ss', 'tt'], + advertised_ip_ranges: [ + { + description: 'test description#1 data', + range: 'test range#1 data' + }, + { + description: 'test description#2 data', + range: 'test range#2 data' + } + ], + asn: 546239892 + }) + description 'test description#1 data' + network 'resource(network,1)' + r_label 'test name#1 data' + region 'resource(region,1)' + project 'test project#1 data' + credential 'mycred' + end + + gcompute_router 'title2' do + action :create + bgp({ + advertise_mode: 'DEFAULT', + advertised_groups: ['oo', 'pp'], + advertised_ip_ranges: [ + { + description: 'test description#2 data', + range: 'test range#2 data' + }, + { + description: 'test description#3 data', + range: 'test range#3 data' + }, + { + description: 'test description#4 data', + range: 'test range#4 data' + }, + { + description: 'test description#5 data', + range: 'test range#5 data' + }, + { + description: 'test description#6 data', + range: 'test range#6 data' + } + ], + asn: 819359839 + }) + description 'test description#2 data' + network 'resource(network,2)' + r_label 'test name#2 data' + region 'resource(region,2)' + project 'test project#2 data' + credential 'mycred' + end + 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 + + context 'gcompute_router[title0]' do + subject do + chef_run.find_resource(:gcompute_router, 'title0') + end + + it { is_expected.to have_attributes(r_label: 'test name#0 data') } + + it do + is_expected + .to have_attributes(description: 'test description#0 data') + end + + # TODO(alexstephen): Implement resourceref test. + # it 'network' do + # # Add test code here + # end + + # TODO(nelsonjr): Implement complex nested property object test. + # it 'bgp' do + # # Add test code here + # end + + # TODO(alexstephen): Implement resourceref test. + # it 'region' do + # # Add test code here + # end + end + + context 'gcompute_router[title1]' do + subject do + chef_run.find_resource(:gcompute_router, 'title1') + end + + it { is_expected.to have_attributes(r_label: 'test name#1 data') } + + it do + is_expected + .to have_attributes(description: 'test description#1 data') + end + + # TODO(alexstephen): Implement resourceref test. + # it 'network' do + # # Add test code here + # end + + # TODO(nelsonjr): Implement complex nested property object test. + # it 'bgp' do + # # Add test code here + # end + + # TODO(alexstephen): Implement resourceref test. + # it 'region' do + # # Add test code here + # end + end + + context 'gcompute_router[title2]' do + subject do + chef_run.find_resource(:gcompute_router, 'title2') + end + + it { is_expected.to have_attributes(r_label: 'test name#2 data') } + + it do + is_expected + .to have_attributes(description: 'test description#2 data') + end + + # TODO(alexstephen): Implement resourceref test. + # it 'network' do + # # Add test code here + # end + + # TODO(nelsonjr): Implement complex nested property object test. + # it 'bgp' do + # # Add test code here + # end + + # TODO(alexstephen): Implement resourceref test. + # it 'region' do + # # Add test code here + # end + end + end + + # Ensure present: resource exists, no change, has name, fail + context 'title != name (fail)' do + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } + end + end + end + + # Ensure present: resource exists, changes + context 'changes == action' do + # Ensure present: resource exists, changes, no name + context 'title == name' do + # Ensure present: resource exists, changes, no name, pass + context 'title == name (pass)' do + # TODO(alexstephen): Implement new test format. + end + + # Ensure present: resource exists, changes, no name, fail + context 'title == name (fail)' do + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } + end + end + + # Ensure present: resource exists, changes, has name + context 'title != name' do + # Ensure present: resource exists, changes, has name, pass + context 'title != name (pass)' do + # TODO(alexstephen): Implement new test format + end + + # Ensure present: resource exists, changes, has name, fail + context 'title != name (fail)' do + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } + end + end + end + end + + context 'resource missing' do + # Ensure present: resource missing, ignore, no name + context 'title == name' do + # Ensure present: resource missing, ignore, no name, pass + context 'title == name (pass)' do + before do + expect_network_get_failed 1, + name: 'title0', + region: 'test name#0 data' + expect_network_create \ + 1, + { + 'kind' => 'compute#router', + 'name' => 'title0', + 'description' => 'test description#0 data', + 'network' => 'selflink(resource(network,0))', + 'bgp' => { + 'asn' => 273_119_946, + 'advertiseMode' => 'DEFAULT', + 'advertisedGroups' => %w[uu vv ww xx], + 'advertisedIpRanges' => [ + { + 'range' => 'test range#0 data', + 'description' => 'test description#0 data' + }, + { + 'range' => 'test range#1 data', + 'description' => 'test description#1 data' + }, + { + 'range' => 'test range#2 data', + 'description' => 'test description#2 data' + }, + { + 'range' => 'test range#3 data', + 'description' => 'test description#3 data' + } + ] + }, + 'region' => 'test name#0 data' + }, + name: 'title0', + region: 'test name#0 data' + expect_network_get_async 1, + name: 'title0', + region: 'test name#0 data' + expect_network_get_success_network 1 + expect_network_get_success_region 1 + 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. + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + + ChefSpec::SoloRunner.new( + step_into: %w[gcompute_router gcompute_region gcompute_network], + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) + end + + let(:chef_run) do + apply_recipe( + <<-MANIFEST + gcompute_network 'resource(network,0)' do + action :create + n_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_region 'resource(region,0)' do + action :create + r_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_router 'title0' do + action :create + bgp({ + advertise_mode: 'DEFAULT', + advertised_groups: ['uu', 'vv', 'ww', 'xx'], + advertised_ip_ranges: [ + { + description: 'test description#0 data', + range: 'test range#0 data' + }, + { + description: 'test description#1 data', + range: 'test range#1 data' + }, + { + description: 'test description#2 data', + range: 'test range#2 data' + }, + { + description: 'test description#3 data', + range: 'test range#3 data' + } + ], + asn: 273119946 + }) + description 'test description#0 data' + network 'resource(network,0)' + region 'resource(region,0)' + project 'test project#0 data' + credential 'mycred' + end + 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 + chef_run.find_resource(:gcompute_router, 'title0') + end + + it 'should run test correctly' do + expect(chef_run).to create(:gcompute_router, + 'title0') + end + it { is_expected.to have_attributes(r_label: 'title0') } + + it do + is_expected + .to have_attributes(description: 'test description#0 data') + end + + # TODO(alexstephen): Implement resourceref test. + # it 'network' do + # # Add test code here + # end + + # TODO(nelsonjr): Implement complex nested property object test. + # it 'bgp' do + # # Add test code here + # end + + # TODO(alexstephen): Implement resourceref test. + # it 'region' do + # # Add test code here + # end + end + + # Ensure present: resource missing, ignore, no name, fail + context 'title == name (fail)' do + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } + end + end + + # Ensure present: resource missing, ignore, has name + context 'title != name' do + # Ensure present: resource missing, ignore, has name, pass + context 'title != name (pass)' do + before do + expect_network_get_failed 1, region: 'test name#0 data' + expect_network_create \ + 1, + { + 'kind' => 'compute#router', + 'name' => 'test name#0 data', + 'description' => 'test description#0 data', + 'network' => 'selflink(resource(network,0))', + 'bgp' => { + 'asn' => 273_119_946, + 'advertiseMode' => 'DEFAULT', + 'advertisedGroups' => %w[uu vv ww xx], + 'advertisedIpRanges' => [ + { + 'range' => 'test range#0 data', + 'description' => 'test description#0 data' + }, + { + 'range' => 'test range#1 data', + 'description' => 'test description#1 data' + }, + { + 'range' => 'test range#2 data', + 'description' => 'test description#2 data' + }, + { + 'range' => 'test range#3 data', + 'description' => 'test description#3 data' + } + ] + }, + 'region' => 'test name#0 data' + }, + region: 'test name#0 data' + expect_network_get_async 1, region: 'test name#0 data' + expect_network_get_success_network 1 + expect_network_get_success_region 1 + 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. + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + + ChefSpec::SoloRunner.new( + step_into: %w[gcompute_router gcompute_region gcompute_network], + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) + end + + let(:chef_run) do + apply_recipe( + <<-MANIFEST + gcompute_network 'resource(network,0)' do + action :create + n_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_region 'resource(region,0)' do + action :create + r_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_router 'title0' do + action :create + bgp({ + advertise_mode: 'DEFAULT', + advertised_groups: ['uu', 'vv', 'ww', 'xx'], + advertised_ip_ranges: [ + { + description: 'test description#0 data', + range: 'test range#0 data' + }, + { + description: 'test description#1 data', + range: 'test range#1 data' + }, + { + description: 'test description#2 data', + range: 'test range#2 data' + }, + { + description: 'test description#3 data', + range: 'test range#3 data' + } + ], + asn: 273119946 + }) + description 'test description#0 data' + network 'resource(network,0)' + r_label 'test name#0 data' + region 'resource(region,0)' + project 'test project#0 data' + credential 'mycred' + end + 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 + chef_run.find_resource(:gcompute_router, 'title0') + end + + it 'should run test correctly' do + expect(chef_run).to create(:gcompute_router, + 'title0') + end + it { is_expected.to have_attributes(r_label: 'test name#0 data') } + + it do + is_expected + .to have_attributes(description: 'test description#0 data') + end + + # TODO(alexstephen): Implement resourceref test. + # it 'network' do + # # Add test code here + # end + + # TODO(nelsonjr): Implement complex nested property object test. + # it 'bgp' do + # # Add test code here + # end + + # TODO(alexstephen): Implement resourceref test. + # it 'region' do + # # Add test code here + # end + end + + # Ensure present: resource missing, ignore, has name, fail + context 'title != name (fail)' do + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } + end + end + end + end + + context 'ensure == absent' do + context 'resource missing' do + # Ensure absent: resource missing, ignore, no name + context 'title == name' do + # Ensure absent: resource missing, ignore, no name, pass + context 'title == name (pass)' do + before do + expect_network_get_failed 1, + name: 'title0', + region: 'test name#0 data' + expect_network_get_success_network 1 + expect_network_get_success_region 1 + 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. + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + + ChefSpec::SoloRunner.new( + step_into: %w[gcompute_router gcompute_region gcompute_network], + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) + end + + let(:chef_run) do + apply_recipe( + <<-MANIFEST + gcompute_network 'resource(network,0)' do + action :create + n_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_region 'resource(region,0)' do + action :create + r_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_router 'title0' do + action :delete + network 'resource(network,0)' + region 'resource(region,0)' + project 'test project#0 data' + credential 'mycred' + end + 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 + chef_run.find_resource(:gcompute_router, 'title0') + end + end + + # Ensure absent: resource missing, ignore, no name, fail + context 'title == name (fail)' do + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } + end + end + + # Ensure absent: resource missing, ignore, has name + context 'title != name' do + # Ensure absent: resource missing, ignore, has name, pass + context 'title != name (pass)' do + before do + expect_network_get_failed 1, region: 'test name#0 data' + expect_network_get_success_network 1 + expect_network_get_success_region 1 + 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. + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + + ChefSpec::SoloRunner.new( + step_into: %w[gcompute_router gcompute_region gcompute_network], + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) + end + + let(:chef_run) do + apply_recipe( + <<-MANIFEST + gcompute_network 'resource(network,0)' do + action :create + n_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_region 'resource(region,0)' do + action :create + r_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_router 'title0' do + action :delete + network 'resource(network,0)' + r_label 'test name#0 data' + region 'resource(region,0)' + project 'test project#0 data' + credential 'mycred' + end + 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 + chef_run.find_resource(:gcompute_router, 'title0') + end + end + + # Ensure absent: resource missing, ignore, has name, fail + context 'title != name (fail)' do + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } + end + end + end + + context 'resource exists' do + # Ensure absent: resource exists, ignore, no name + context 'title == name' do + # Ensure absent: resource exists, ignore, no name, pass + context 'title == name (pass)' do + before do + expect_network_get_success 1, + name: 'title0', + region: 'test name#0 data' + expect_network_delete 1, 'title0', region: 'test name#0 data' + expect_network_get_async 1, + name: 'title0', + region: 'test name#0 data' + expect_network_get_success_network 1 + expect_network_get_success_region 1 + 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. + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + + ChefSpec::SoloRunner.new( + step_into: %w[gcompute_router gcompute_region gcompute_network], + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) + end + + let(:chef_run) do + apply_recipe( + <<-MANIFEST + gcompute_network 'resource(network,0)' do + action :create + n_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_region 'resource(region,0)' do + action :create + r_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_router 'title0' do + action :delete + network 'resource(network,0)' + region 'resource(region,0)' + project 'test project#0 data' + credential 'mycred' + end + 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 + chef_run.find_resource(:gcompute_router, 'title0') + end + + it 'should run test correctly' do + expect(chef_run).to delete(:gcompute_router, + 'title0') + end + end + + # Ensure absent: resource exists, ignore, no name, fail + context 'title == name (fail)' do + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } + end + end + # Ensure absent: resource exists, ignore, has name + context 'title != name' do + # Ensure absent: resource exists, ignore, has name, pass + context 'title != name (pass)' do + before do + expect_network_get_success 1, region: 'test name#0 data' + expect_network_delete 1, nil, region: 'test name#0 data' + expect_network_get_async 1, region: 'test name#0 data' + expect_network_get_success_network 1 + expect_network_get_success_region 1 + 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. + cookbook_paths << File.join(File.dirname(__FILE__), 'cookbooks') + + ChefSpec::SoloRunner.new( + step_into: %w[gcompute_router gcompute_region gcompute_network], + cookbook_path: cookbook_paths, + platform: 'ubuntu', + version: '16.04' + ) + end + + let(:chef_run) do + apply_recipe( + <<-MANIFEST + gcompute_network 'resource(network,0)' do + action :create + n_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_region 'resource(region,0)' do + action :create + r_label 'test name#0 data' + project 'test project#0 data' + credential 'mycred' + end + + gcompute_router 'title0' do + action :delete + network 'resource(network,0)' + r_label 'test name#0 data' + region 'resource(region,0)' + project 'test project#0 data' + credential 'mycred' + end + 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 + chef_run.find_resource(:gcompute_router, 'title0') + end + + it 'should run test correctly' do + expect(chef_run).to delete(:gcompute_router, + 'title0') + end + end + + # Ensure absent: resource exists, ignore, has name, fail + context 'title != name (fail)' do + # TODO(alexstephen): Implement new test format. + subject { -> { raise '[placeholder] This should fail.' } } + it { is_expected.to raise_error(RuntimeError, /placeholder/) } + end + end + end + end + + def expand_variables(template, data, extra_data = {}) + Google::GCOMPUTE::Router + .action_class.expand_variables(template, data, extra_data) + end + + 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::Compute::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 + + def expect_network_get_async(id, data = {}) + body = { kind: 'compute#router' }.to_json + + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + + debug_network "!! #{self_link(uri_data(id).merge(data))}" + expect(Google::Compute::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 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::Compute::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 + + def expect_network_create(id, expected_body, data = {}) + merged_uri = uri_data(id).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(collection(merged_uri), instance_of(Google::FakeAuthorization), + 'application/json', expected_body.to_json) do |args| + debug_network ">> POST #{args} = body(#{body})" + request + end + end + + def expect_network_delete(id, name = nil, data = {}) + delete_data = uri_data(id).merge(data) + delete_data[:name] = name unless name.nil? + body = { kind: 'compute#operation', + status: 'DONE', + targetLink: self_link(delete_data) }.to_json + + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + + debug_network "!! DELETE #{self_link(delete_data)}" + expect(Google::Compute::Network::Delete).to receive(:new) + .with(self_link(delete_data), + instance_of(Google::FakeAuthorization)) do |args| + debug_network ">> DELETE #{args}" + request + end + end + + def load_network_result(file) + results = File.join(File.dirname(__FILE__), 'data', 'network', + 'gcompute_router', 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 + + def expect_network_get_success_network(id, data = {}) + id_data = data.fetch(:name, '').include?('title') ? 'title' : 'name' + body = load_network_result_network("success#{id}~" \ + "#{id_data}.yaml") + .to_json + uri = uri_data_network(id).merge(data) + + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + + debug_network "!! GET #{uri}" + expect(Google::Compute::Network::Get).to receive(:new) + .with(self_link_network(uri), + instance_of(Google::FakeAuthorization)) do |args| + debug_network ">> GET #{args}" + request + end + end + + def load_network_result_network(file) + results = File.join(File.dirname(__FILE__), 'data', 'network', + 'gcompute_network', 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 + + # Creates variable test data to comply with self_link URI parameters + # Only used for gcompute_network objects + def uri_data_network(id) + { + project: GoogleTests::Constants::N_PROJECT_DATA[(id - 1) \ + % GoogleTests::Constants::N_PROJECT_DATA.size], + name: GoogleTests::Constants::N_NAME_DATA[(id - 1) \ + % GoogleTests::Constants::N_NAME_DATA.size] + } + end + + def self_link_network(data) + URI.join( + 'https://www.googleapis.com/compute/v1/', + expand_variables_network( + 'projects/{{project}}/global/networks/{{name}}', + data + ) + ) + end + + def expect_network_get_success_region(id, data = {}) + id_data = data.fetch(:name, '').include?('title') ? 'title' : 'name' + body = load_network_result_region("success#{id}~" \ + "#{id_data}.yaml") + .to_json + uri = uri_data_region(id).merge(data) + + request = double('request') + allow(request).to receive(:send).and_return(http_success(body)) + + debug_network "!! GET #{uri}" + expect(Google::Compute::Network::Get).to receive(:new) + .with(self_link_region(uri), + instance_of(Google::FakeAuthorization)) do |args| + debug_network ">> GET #{args}" + request + end + end + + def load_network_result_region(file) + results = File.join(File.dirname(__FILE__), 'data', 'network', + 'gcompute_region', 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 + + # Creates variable test data to comply with self_link URI parameters + # Only used for gcompute_region objects + def uri_data_region(id) + { + project: GoogleTests::Constants::R_PROJECT_DATA[(id - 1) \ + % GoogleTests::Constants::R_PROJECT_DATA.size], + name: GoogleTests::Constants::R_NAME_DATA[(id - 1) \ + % GoogleTests::Constants::R_NAME_DATA.size] + } + end + + def self_link_region(data) + URI.join( + 'https://www.googleapis.com/compute/v1/', + expand_variables_region( + 'projects/{{project}}/regions/{{name}}', + data + ) + ) + end + + 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 + + def expand_variables_network(template, data, ext_dat = {}) + Google::GCOMPUTE::Network + .action_class.expand_variables(template, data, ext_dat) + end + + def expand_variables_region(template, data, ext_dat = {}) + Google::GCOMPUTE::Region + .action_class.expand_variables(template, data, ext_dat) + end + + def collection(data) + URI.join( + 'https://www.googleapis.com/compute/v1/', + expand_variables( + 'projects/{{project}}/regions/{{region}}/routers', + data + ) + ) + end + + def self_link(data) + URI.join( + 'https://www.googleapis.com/compute/v1/', + expand_variables( + 'projects/{{project}}/regions/{{region}}/routers/{{name}}', + data + ) + ) + end + + # Creates variable test data to comply with self_link URI parameters + def uri_data(id) + { + project: GoogleTests::Constants::R_PROJECT_DATA[(id - 1) \ + % GoogleTests::Constants::R_PROJECT_DATA.size], + region: GoogleTests::Constants::R_REGION_DATA[(id - 1) \ + % GoogleTests::Constants::R_REGION_DATA.size], + name: GoogleTests::Constants::R_NAME_DATA[(id - 1) \ + % GoogleTests::Constants::R_NAME_DATA.size] + } + end + + 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 + recipe_path = "google-gcompute::#{recipe_name}" + begin + yield recipe_path + ensure + File.delete(recipe_loc) + end + end +end diff --git a/spec/test_constants.rb b/spec/test_constants.rb index 4091e57..3b157d8 100644 --- a/spec/test_constants.rb +++ b/spec/test_constants.rb @@ -435,6 +435,7 @@ module Constants # Constants for the following objects: # - Region.project # - Route.project + # - Router.project R_PROJECT_DATA = [ 'test project#0 data', 'test project#1 data', @@ -446,6 +447,7 @@ module Constants # Constants for the following objects: # - Region.name # - Route.name + # - Router.name R_NAME_DATA = [ 'test name#0 data', 'test name#1 data', @@ -454,6 +456,15 @@ module Constants 'test name#4 data' ].freeze + # Constants for: Router.region + R_REGION_DATA = [ + 'test name#0 data', + 'test name#1 data', + 'test name#2 data', + 'test name#3 data', + 'test name#4 data' + ].freeze + # Constants for the following objects: # - Snapshot.project # - Subnetwork.project