diff --git a/api/type.rb b/api/type.rb index 5f55c25e5468..f093d9a910ee 100644 --- a/api/type.rb +++ b/api/type.rb @@ -166,9 +166,18 @@ def initialize(name) NAME = Api::Type::String.new('name') end + # Properties that are fetched externally + class FetchedExternal < Type + attr_writer :resource + + def api_name + name + end + end + # Represents a fingerprint. A fingerprint is an output-only # field used for optimistic locking during updates. - class Fingerprint < String + class Fingerprint < FetchedExternal def validate super @output = true if @output.nil? @@ -304,15 +313,6 @@ def validate end end - # Properties that are fetched externally - class FetchedExternal < Type - attr_writer :resource - - def api_name - name - end - end - # Represents a 'selfLink' property, which returns the URI of the resource. class SelfLink < FetchedExternal EXPORT_KEY = 'selfLink'.freeze diff --git a/products/compute/chef.yaml b/products/compute/chef.yaml index 78909b14c9f3..8ace7dc01245 100644 --- a/products/compute/chef.yaml +++ b/products/compute/chef.yaml @@ -49,6 +49,9 @@ overrides: !ruby/object:Provider::ResourceOverrides message = 'Instance cannot be edited' Chef::Log.fatal message raise message + provider_helpers: + - 'products/compute/helpers/ruby/provider_instance.rb' + - 'products/compute/helpers/ruby/instance_metadata.rb' Network: !ruby/object:Provider::Chef::ResourceOverride handlers: !ruby/object:Provider::Chef::Handlers # TODO(alexstephen): Better editing from auto to custom diff --git a/provider/core.rb b/provider/core.rb index bb5c268a293e..e7c4d57795b8 100644 --- a/provider/core.rb +++ b/provider/core.rb @@ -568,6 +568,22 @@ def relative_path(target, base) Pathname.new(target).relative_path_from(Pathname.new(base)) end + # Filter the properties to keep only the ones requiring custom update + # method and group them by update url & verb. + def properties_by_custom_update(properties) + update_props = properties.reject do |p| + p.update_url.nil? || p.update_verb.nil? + end + update_props.group_by do |p| + { update_url: p.update_url, update_verb: p.update_verb } + end + end + + def update_url(resource, url_part) + return build_url(resource.self_link_url) if url_part.nil? + [resource.__product.base_url, url_part].flatten.join + end + # TODO(nelsonjr): Review all object interfaces and move to private methods # that should not be exposed outside the object hierarchy. private diff --git a/provider/terraform.rb b/provider/terraform.rb index f15382ae7afa..d58f7e334537 100644 --- a/provider/terraform.rb +++ b/provider/terraform.rb @@ -71,11 +71,6 @@ def build_url(url_parts, _extra = false) url_parts.flatten.join end - def update_url(resource, url_part) - return build_url(resource.self_link_url) if url_part.nil? - [resource.__product.base_url, url_part].flatten.join - end - # Transforms a format string with field markers to a regex string with # capture groups. # @@ -108,17 +103,6 @@ def nested_properties(property) end end - # Filter the properties to keep only the ones requiring custom update - # method and group them by update url & verb. - def properties_by_custom_update(properties) - update_props = properties.reject do |p| - p.update_url.nil? || p.update_verb.nil? - end - update_props.group_by do |p| - { update_url: p.update_url, update_verb: p.update_verb } - end - end - private # This function uses the resource.erb template to create one file diff --git a/spec/provider_chef_spec.rb b/spec/provider_chef_spec.rb index e5e8a8ee4e7c..11091e468075 100644 --- a/spec/provider_chef_spec.rb +++ b/spec/provider_chef_spec.rb @@ -234,6 +234,7 @@ def allow_open_real_templates allow_open 'templates/resourceref_mocks.erb' allow_open 'templates/return_if_object.erb' allow_open 'templates/transport.erb' + allow_open 'templates/puppetchef/update_props.erb' allow_open_real_tests diff --git a/spec/provider_puppet_spec.rb b/spec/provider_puppet_spec.rb index bb69ef6d01f4..65f249e768e0 100644 --- a/spec/provider_puppet_spec.rb +++ b/spec/provider_puppet_spec.rb @@ -694,6 +694,7 @@ def allow_open_real_templates allow_open 'templates/network_spec.yaml.erb' allow_open 'templates/return_if_object.erb' allow_open 'templates/transport.erb' + allow_open 'templates/puppetchef/update_props.erb' allow_open_real_tests allow_open_real_resourceref diff --git a/templates/ansible/resource.erb b/templates/ansible/resource.erb index 2c1e368446f5..53931dff6a20 100644 --- a/templates/ansible/resource.erb +++ b/templates/ansible/resource.erb @@ -16,6 +16,8 @@ __metaclass__ = type metadata_version = quote_string(@config.manifest.get('metadata_version', object)) supported_by = quote_string(@config.manifest.get('supported_by', object)) + supported_by = quote_string(@config.manifest.get('supported_by', config)) + update_props = properties_by_custom_update(object.all_user_properties) -%> ANSIBLE_METADATA = {'metadata_version': <%= metadata_version -%>, 'status': <%= @config.manifest.get('status', object) -%>, @@ -153,6 +155,10 @@ def main(): ('fetch' if object.access_api_results) ]) -%> +<% unless update_props.empty? -%> + update_fields(module, resource_to_request(module), + response_to_hash(module, fetch)) +<% end -%> <%= lines(indent("fetch = #{method}", 16)) -%> changed = True else: @@ -240,7 +246,7 @@ def main(): end -%> <% if object.update.nil? -%> -<% if !false?(object.editable) -%> +<% if object.editable || !update_props.empty? -%> auth = GcpSession(module, <%= quote_string(prod_name) -%>) <% update_verb = object.update_verb.to_s.downcase @@ -263,6 +269,25 @@ def main(): <% end # object.update.nil? -%> +<% unless update_props.empty? -%> +def update_fields(module, request, response): + difference = GcpRequest(request).difference(GcpRequest(response)) + auth = GcpSession(module, <%= quote_string(prod_name) -%>) +<% update_props.each do |key, props| -%> + if <%= props.map { |prop| "difference.get(#{quote_string(prop.name.underscore)})" }.join(' || ') -%>: + auth.<%= key[:update_verb].downcase -%>( + ''.join([ + "<%= object.__product.base_url -%>", + "<%= key[:update_url].gsub('{{', '{').gsub('}}', '}') -%>" + ]).format(**module.params), + { +<%= lines(request_properties(props, 16)) -%> + } + ) +<% end # update_props.each -%> + + +<% end # unless update_props.empty? -%> <%= lines(method_decl('delete', ['module', 'link', ('kind' if object.kind?), ('fetch' if object.access_api_results)])) diff --git a/templates/chef/resource.erb b/templates/chef/resource.erb index 978ed0a340e1..e06d758b5cb5 100644 --- a/templates/chef/resource.erb +++ b/templates/chef/resource.erb @@ -436,7 +436,14 @@ module Google puts # making a newline until we find a better way TODO: find! compute_changes.each { |log| puts " - #{log.strip}\n" } <% if object&.handlers&.update.nil? -%> -<% +<% update_props = properties_by_custom_update(object.all_user_properties) -%> +<% update_props.each do |key, props| -%> +<% prop_statement = props.map { |prop| "(@current_resource.#{prop.name.underscore} != @new_resource.#{prop.name.underscore})" }.join(' || ') -%> + if <%= prop_statement %> + <%= props.first.name.downcase -%>_update(@current_resource) + end +<% end # update_props.each -%> +<% put_new = ["::Google::#{product_ns}::Network", "#{object.update_verb.to_s.capitalize}.new" ].join('::') @@ -505,6 +512,7 @@ module Google <%= lines(indent(emit_method('self.resource_to_hash', %w[resource], r2h_code, file_relative), 8)) -%> +<%= lines(indent(compile('templates/puppetchef/update_props.erb'), 2)) -%> # Copied from Chef > Provider > #converge_if_changed def compute_changes properties = @new_resource.class.state_properties.map(&:name) diff --git a/templates/puppet/resource.erb b/templates/puppet/resource.erb index 795925d7830b..812e5db8b6e6 100644 --- a/templates/puppet/resource.erb +++ b/templates/puppet/resource.erb @@ -317,6 +317,11 @@ Puppet::Type.type(:<%= object.out_name -%>).provide(:google) do <% # 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 +<% update_props = properties_by_custom_update(object.all_user_properties) -%> +<% update_props.each do |key, props| -%> +<% prop_statement = props.map { |prop| "@dirty[:#{prop.name.underscore}]" }.join(' || ') -%> + <%= props.first.name.downcase -%>_update(@resource) if <%= prop_statement %> +<% end # update_props.each -%> <% if object&.handlers&.flush.nil? -%> <% put_new = ["Google::#{product_ns}::Network", @@ -352,6 +357,7 @@ Puppet::Type.type(:<%= object.out_name -%>).provide(:google) do } end +<%= lines(indent(compile('templates/puppetchef/update_props.erb'), 2)) -%> <% unless object.exports.nil? -%> <% exp_list = [ diff --git a/templates/puppetchef/update_props.erb b/templates/puppetchef/update_props.erb new file mode 100644 index 000000000000..14a781627211 --- /dev/null +++ b/templates/puppetchef/update_props.erb @@ -0,0 +1,23 @@ +<% update_props = properties_by_custom_update(object.all_user_properties) -%> +<% update_props.each do |key, props| -%> +def <%= props[0].name.downcase -%>_update(data) + Google::<%= product_ns -%>::Network::<%= key[:update_verb].capitalize -%>.new( +<%= indent(build_url([object.__product.base_url, key[:update_url]]), 4) -%>, + fetch_auth(@resource), + 'application/json', + { +<% + update_prop_statements = props.map do |prop| + if prop.is_a? Api::Type::FetchedExternal + "#{prop.api_name}: @fetched['#{prop.api_name}']" + else + "#{prop.api_name}: @resource[:#{prop.out_name}]" + end + end +-%> +<%= lines(indent_list(update_prop_statements, 6)) -%> + }.to_json + ).send +end + +<% end # update_props.each -%>