Thoughts on framework v0.15.0

edit: I seem to have solved my problem (see bottom of this post)

Hey @bflad, in another thread you asked for feedback about the various *ValueFrom() methods in 0.15.0

I’ve tried it out, and, while it’s supremely unforgiving to typos in struct tags, I like it! It looks like it’s going to change my provider for the better, but I have some questions…

I’ve got some data I want to represent to the user like this (using HCL for brevity):

  parent = {
    parent_string = "string"
    parent_int = 5
    child_1 = {
      child_1_string = "string"
      child_1_string = 7
    }
    child_2 = {
      child_2_int = 1
      child_2_list = ["foo", "bar", "bar"]
    }
  }

These values do not come from the API this way, but rather need to be gathered from several different data structures.

I now have Go types and methods like this:

type parent struct {
	ParentString types.String `tfsdk:"parent_string"`
	ParentInt    types.Int64  `tfsdk:"parent_int"`
	Child1       types.Object `tfsdk:"child_1"`
	Child2       types.Object `tfsdk:"child_2"`
}

type child1 struct {
	Child1String string `tfsdk:"child_1_string"`
	Child1Int    int64  `tfsdk:"child_1_int"`
}

func (o child1) attrTypes() map[string]attr.Type {
	return map[string]attr.Type{
		"child_1_string": types.StringType,
		"child_1_int":    types.Int64Type,
	}
}

type child2 struct {
	Child2Val1 int64    `tfsdk:"child_2_int"`
	Child2Val2 []string `tfsdk:"child_2_list"`
}

func (o child2) attrTypes() map[string]attr.Type {
	return map[string]attr.Type{
		"child_2_int":  types.StringType,
		"child_2_list": types.ListType{ElemType: types.StringType},
	}
}

In the Read() method, I make the required API calls and assemble a parent{} object:

	parentString := getParentStringFromApi()
	parentInt := getParentIntFromApi()
	ch1 := &child1{}
	someFuncWhichFillsInChild1(ch1)
	ch2 := &child2{}
	someFuncWhichFillsInChild2(ch2)

	child1Obj, diags := types.ObjectValueFrom(ctx, ch1.attrTypes(), ch1)
	resp.Diagnostics.Append(diags...)
	child2Obj, diags := types.ObjectValueFrom(ctx, ch2.attrTypes(), ch2)
	resp.Diagnostics.Append(diags...)

	foundState := parent{
		ParentString: types.StringValue(parentString),
		ParentInt:    types.Int64Value(parentInt),
		Child1:       child1Obj,
		Child2:       child2Obj,
	}

	diags = resp.State.Set(ctx, foundState)
	resp.Diagnostics.Append(diags...)

This kind of thing works great! I’m now able to use native Go structs and quickly/easily populate native terraform types.

Is… Is this the intended use case?

Also, I’m a little stuck:

Some API responses might need special handling: If the API returns 0 or “unassigned”, or an empty list of strings, I might like to set those values null, for example.

Without access to a types.Object’s Attrs or a types.List’s Elems, what’s the right way to nudge go types into appropriately null types.Values?

Thanks!

edit: It looks like any values I want to make null, can be handled by using a nil-able type (pointer, slice) in the Go struct, and then leaving them nil at the time *ValueFrom() is called. I feel silly for not seeing this sooner.

2 Likes