From 18945d50885a9f7b52ab13bcad6227fa683cba36 Mon Sep 17 00:00:00 2001 From: Emily Ye Date: Fri, 15 Mar 2019 16:29:06 -0700 Subject: [PATCH] add nested decoder --- api/resource.rb | 22 +++++++++ templates/terraform/nested_decoder.go.erb | 55 +++++++++++++++++++++++ templates/terraform/resource.erb | 31 +++++++++---- 3 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 templates/terraform/nested_decoder.go.erb diff --git a/api/resource.rb b/api/resource.rb index 4177730432dc..9fc716b0c2e2 100644 --- a/api/resource.rb +++ b/api/resource.rb @@ -53,6 +53,11 @@ module Properties # list. Otherwise, it's safe to leave empty. # If empty, we assume that `name` is the identifier. attr_reader :identity + # This is useful in case you need to change the query made for + # GET requests only. In particular, this is often used + # to extract an object from a parent object or a collection. + attr_reader :nested_decoder + attr_reader :exclude attr_reader :async attr_reader :readonly @@ -100,6 +105,18 @@ def validate end end + class NestedDecoder < Api::Object + attr_reader :keys + attr_reader :has_id_only_list + + def validate + super + + check :keys, type: Array, item_type: String, required: true + check :has_id_only_list, type: :boolean, default: false + end + end + # Represents a response from the API that returns a list of objects. class ResponseList < Api::Object attr_reader :kind @@ -190,6 +207,11 @@ def validate check :transport, type: Transport check :references, type: ReferenceLinks + check :nested_decoder, type: Api::Resource::NestedDecoder + if @nested_decoder&.has_id_only_list && @identity&.length != 1 + raise 'Resource with :has_id_only_list in :nested_decoder must have exactly one :identity property"' + end + check :collection_url_response, default: Api::Resource::ResponseList.new, type: Api::Resource::ResponseList diff --git a/templates/terraform/nested_decoder.go.erb b/templates/terraform/nested_decoder.go.erb new file mode 100644 index 000000000000..1418ab04015f --- /dev/null +++ b/templates/terraform/nested_decoder.go.erb @@ -0,0 +1,55 @@ + config := meta.(*Config) + + var v interface{} + var ok bool +<% object.nested_decoder.keys[0...-1].each do |k| -%> + v, ok = res["<%=k-%>"] + if !ok || v == nil { + return nil, nil + } + res = v.(map[string]interface{}) +<% end -%> + v, ok = res["<%=object.nested_decoder.keys[-1]-%>"] + if !ok || v == nil { + return nil, nil + } + // Final nested resource is either a list of resources we need to filter + // or just the resource itself, which we return. + switch v.(type) { + case []interface{}: + break + case map[string]interface{}: + return v.(map[string]interface{}), nil + default: + return nil, fmt.Errorf("invalid value for <%= object.nested_decoder.keys.join(".") -%>: %v", v) + } + + items := v.([]interface{}) + for _, vRaw := range items { + <% if object.nested_decoder.has_id_only_list -%> + // If only an id is given in parent resource, + // construct a resource map for that id KV pair. + item := map[string]interface{}{"<%=object.identity.first.api_name%>": vRaw} + <% else %> + item := vRaw.(map[string]interface{}) + <% end %> + + <% object.identity.each do |prop| -%> + <% if settable_properties.include?(prop) -%> + id, err := expand<%= resource_name -%><%= titlelize_property(prop) -%>(d.Get("<%= prop.name.underscore -%>"), d, config) + if err != nil { + return nil, err + } + <% else -%> + id := d.Get("<%= prop.name.underscore -%>") + <% end # settable_properties.include?(prop)-%> + + itemId := flatten<%= resource_name -%><%= titlelize_property(prop) -%>(item["<%= prop.api_name %>"], d) + log.Printf("[DEBUG] Checking equality of %#v, %#v", itemId, id) + if !reflect.DeepEqual(itemId, id) { + continue + } + <% end # object.identity.each -%> + return item, nil + } + return nil, nil diff --git a/templates/terraform/resource.erb b/templates/terraform/resource.erb index 9788d6fc05c2..4610ef35a7b5 100644 --- a/templates/terraform/resource.erb +++ b/templates/terraform/resource.erb @@ -217,15 +217,19 @@ func resource<%= resource_name -%>Read(d *schema.ResourceData, meta interface{}) return handleNotFoundError(err, d, fmt.Sprintf("<%= resource_name -%> %q", d.Id())) } -<% unless object.self_link_query.nil? -%> -<%# This part of the template extracts the one resource we're interested in - from the list that gets returned. self_link_query is a field which - describes a list result from a read. -%> -<%= compile_template('templates/terraform/self_link_query.erb', - object: object, - settable_properties: object.settable_properties, - resource_name: resource_name) -%> -<% end -%> +<% if object.nested_decoder -%> + res, err = resource<%= resource_name -%>Extractor(d, meta, res) + if err != nil { + return err + } + + if res == nil { + // Object isn't there any more - remove it from the state. + log.Printf("[DEBUG] Removing <%= resource_name -%> because it couldn't be matched.") + d.SetId("") + return nil + } +<% end -%> <% if object.custom_code.decoder -%> res, err = resource<%= resource_name -%>Decoder(d, meta, res) @@ -606,6 +610,15 @@ func resource<%= resource_name -%>UpdateEncoder(d *schema.ResourceData, meta int } <% end -%> +<% if object.nested_decoder -%> +func resource<%= resource_name -%>Extractor(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + <%= compile_template('templates/terraform/nested_decoder.go.erb', + object: object, + settable_properties: object.settable_properties, + resource_name: resource_name) -%> +} +<% end -%> + <% if object.custom_code.decoder -%> func resource<%= resource_name -%>Decoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { <%= lines(compile(object.custom_code.decoder)) -%>