I am writing a function in a custom provider. It accepts an arbitrary map as a DynamicParameter, does some logic, and returns another arbitrary map. I know that the map will be known by the time of function’s execution. I do not know the structure of the map beforehand, and it is deeply nested.
I am currently stuck trying to reflect a Terraform value into a Go value. My goal is to have this work:
var arg SomeType
_ = req.Arguments.Get(ctx, &arg)
m := make(map[string]interface{})
_ = arg.As(ctx, &m)
- I have attempted to implement a CustomType of DynamicTypable with a CustomValue of DynamicValuable, to no avail; in the implementation of
func (i CustomValue) As(ctx context.Context, target interface{}) diag.Diagnostics {}
, which receives attr.Value, fields of CustomValue.UnderlyingValue() are unexported and thus unaccessable through reflection. Reflection code is:
func valueToMap(ctx context.Context, val interface{}) (map[interface{}]interface{}, diag.Diagnostics) {
data := make(map[interface{}]interface{})
varType := reflect.TypeOf(val)
if varType.Kind() != reflect.Struct {
err := fmt.Errorf("not a struct")
return nil, diag.Diagnostics{
diag.NewErrorDiagnostic(
"Value Conversion Error",
fmt.Sprintf("An unexpected error was encountered trying to convert the value. This is always an error in the provider. Please report the following to the provider developer:\n\nError: %s", err.Error()),
),
}
}
value := reflect.ValueOf(val)
for i := 0; i < value.NumField(); i++ {
if !value.Field(i).CanInterface() {
//Skip unexported fields
continue
}
fieldName := varType.Field(i).Name
if varType.Field(i).Type.Kind() != reflect.Struct {
data[fieldName] = value.Field(i).Interface()
} else {
data[fieldName], _ = valueToMap(ctx, value.Field(i).Interface())
}
}
return data, diag.Diagnostics{}
}
- I have attempted to use asgotypes.GoPrimitive from terraform-plugin-go-contrib
var dynValue types.Dynamic
if err := req.Arguments.Get(ctx, &dynValue); err != nil {
resp.Error = function.ConcatFuncErrors(err)
return
}
var m asgotypes.GoPrimitive
switch value := dynValue.UnderlyingValue().(type) {
case types.Map:
diags := value.ElementsAs(ctx, &m, true)
if diags.HasError() {
resp.Error = function.ConcatFuncErrors(resp.Error, function.FuncErrorFromDiags(ctx, diags))
return
}
default:
resp.Error = function.NewArgumentFuncError(0, "should be map")
return
}
but it errored with
cannot reflect tftypes.Map[tftypes.Object["this": ...] into a struct, must be an object.
Been stuck on this task for a few days, went through tftypes, basetypes, reflect packages in terraform-plugin-framework repo… I really do not want to jsonencode() the map on Terraform’s side before passing it to the function as a string.