Skip to content

Commit c16178e

Browse files
authored
fix(terraform): Fix the generate_import_id function to better handle inline objects (#692)
1 parent b5815c8 commit c16178e

3 files changed

Lines changed: 909 additions & 0 deletions

File tree

assets/terraform/internal/provider/tools.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import (
55
"encoding/json"
66
"fmt"
77
"strings"
8+
9+
"github.com/hashicorp/terraform-plugin-framework/attr"
10+
rsschema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
813
)
914

1015
type Locationer interface {
@@ -107,3 +112,169 @@ func CreateXpathForAttributeWithAncestors(ancestors []Ancestor, attribute string
107112
xpath = append(xpath, "/"+attribute)
108113
return strings.Join(xpath, ""), nil
109114
}
115+
116+
// TypesObjectToMap converts a Terraform types.Object to a map[string]interface{} for JSON marshaling.
117+
// This allows marshaling partial objects without requiring all struct fields.
118+
// If schemaAttr is provided, validates the object against the schema to catch unknown fields.
119+
func TypesObjectToMap(obj types.Object, schemaAttr ...rsschema.Attribute) (interface{}, error) {
120+
if obj.IsNull() {
121+
return nil, nil
122+
}
123+
124+
attrs := obj.Attributes()
125+
result := make(map[string]interface{})
126+
127+
// Validate against schema if provided
128+
if len(schemaAttr) > 0 && schemaAttr[0] != nil {
129+
schema, ok := schemaAttr[0].(rsschema.SingleNestedAttribute)
130+
if ok {
131+
// Validate no unknown fields
132+
for key := range attrs {
133+
if _, exists := schema.Attributes[key]; !exists {
134+
return nil, fmt.Errorf("unknown field %q in location object", key)
135+
}
136+
}
137+
// Validate not empty
138+
if len(attrs) == 0 {
139+
return nil, fmt.Errorf("location object cannot be empty")
140+
}
141+
}
142+
}
143+
144+
for key, val := range attrs {
145+
switch v := val.(type) {
146+
case types.Object:
147+
// For nested objects, don't validate (no schema available)
148+
nested, err := TypesObjectToMap(v)
149+
if err != nil {
150+
return nil, err
151+
}
152+
result[key] = nested
153+
case types.String:
154+
if !v.IsNull() {
155+
result[key] = v.ValueString()
156+
}
157+
case types.Bool:
158+
if !v.IsNull() {
159+
result[key] = v.ValueBool()
160+
}
161+
case types.Int64:
162+
if !v.IsNull() {
163+
result[key] = v.ValueInt64()
164+
}
165+
case types.Float64:
166+
if !v.IsNull() {
167+
result[key] = v.ValueFloat64()
168+
}
169+
case types.List:
170+
if !v.IsNull() {
171+
var list []interface{}
172+
for _, elem := range v.Elements() {
173+
switch e := elem.(type) {
174+
case types.Object:
175+
nested, err := TypesObjectToMap(e)
176+
if err != nil {
177+
return nil, err
178+
}
179+
list = append(list, nested)
180+
case types.String:
181+
if !e.IsNull() {
182+
list = append(list, e.ValueString())
183+
}
184+
default:
185+
list = append(list, elem)
186+
}
187+
}
188+
result[key] = list
189+
}
190+
case types.Map:
191+
if !v.IsNull() {
192+
mapResult := make(map[string]interface{})
193+
for k, mapVal := range v.Elements() {
194+
switch mv := mapVal.(type) {
195+
case types.String:
196+
if !mv.IsNull() {
197+
mapResult[k] = mv.ValueString()
198+
}
199+
default:
200+
mapResult[k] = mapVal
201+
}
202+
}
203+
result[key] = mapResult
204+
}
205+
}
206+
}
207+
208+
return result, nil
209+
}
210+
211+
// MapToTypesObject converts a map[string]interface{} to a Terraform types.Object using the provided schema.
212+
// This automatically handles missing fields by creating typed null values.
213+
// Validates that the map doesn't contain unknown fields or is empty.
214+
func MapToTypesObject(data map[string]interface{}, schemaAttr rsschema.Attribute) (types.Object, error) {
215+
schema, ok := schemaAttr.(rsschema.SingleNestedAttribute)
216+
if !ok {
217+
return types.ObjectNull(nil), fmt.Errorf("schema attribute is not a SingleNestedAttribute")
218+
}
219+
220+
attrTypes := make(map[string]attr.Type)
221+
attrValues := make(map[string]attr.Value)
222+
223+
for name, schemaField := range schema.Attributes {
224+
attrTypes[name] = schemaField.GetType()
225+
226+
if val, exists := data[name]; exists && val != nil {
227+
// Convert JSON value to appropriate terraform type
228+
switch schemaField.GetType().(type) {
229+
case basetypes.ObjectType:
230+
// Recursively handle nested objects
231+
nestedMap, ok := val.(map[string]interface{})
232+
if !ok {
233+
return types.ObjectNull(attrTypes), fmt.Errorf("expected map for nested object %s", name)
234+
}
235+
nestedObj, err := MapToTypesObject(nestedMap, schemaField)
236+
if err != nil {
237+
return types.ObjectNull(attrTypes), err
238+
}
239+
attrValues[name] = nestedObj
240+
case basetypes.StringType:
241+
strVal, ok := val.(string)
242+
if !ok {
243+
return types.ObjectNull(attrTypes), fmt.Errorf("expected string for field %s", name)
244+
}
245+
attrValues[name] = types.StringValue(strVal)
246+
default:
247+
// For other types, try to set them as-is
248+
attrValues[name] = types.StringValue(fmt.Sprintf("%v", val))
249+
}
250+
} else {
251+
// Create typed null for missing fields
252+
switch schemaField.GetType().(type) {
253+
case basetypes.ObjectType:
254+
objType := schemaField.GetType().(basetypes.ObjectType)
255+
attrValues[name] = types.ObjectNull(objType.AttrTypes)
256+
default:
257+
// For non-object types, create a generic null
258+
attrValues[name] = types.StringNull()
259+
}
260+
}
261+
}
262+
263+
// Validate no unknown fields in input
264+
for key := range data {
265+
if _, exists := schema.Attributes[key]; !exists {
266+
return types.ObjectNull(attrTypes), fmt.Errorf("unknown field %q in location object", key)
267+
}
268+
}
269+
270+
// Validate not empty
271+
if len(data) == 0 {
272+
return types.ObjectNull(attrTypes), fmt.Errorf("location object cannot be empty")
273+
}
274+
275+
obj, diags := types.ObjectValue(attrTypes, attrValues)
276+
if diags.HasError() {
277+
return types.ObjectNull(attrTypes), fmt.Errorf("failed to create object: %v", diags.Errors())
278+
}
279+
return obj, nil
280+
}

0 commit comments

Comments
 (0)