Framework 1.6 provider functions - best way to take keyword arguments

What is the best way to take keyword type arguments? For my function I have two values which must be passed and three more which are optional. I thought of just taking a map and reading the values from there, but this doesn’t work because maps seem to be homogeneous.

I see that I can make values nullable but that will be very ugly to make clients pass a bunch of nulls. Making more functions with different parameters is also not viable because I would need one for each permeation. Can something be done with CustomType to basically make it an object? I’ve read through the Go documentation on the API but it wasn’t clear to me from there if something like that would be possible or not.

Hi @jason-johnson :wave: Thank you for raising this great topic and welcome to HashiCorp Discuss!

I think we might want to see some additional details about what exactly you are trying or wanting to do, but I will attempt to answer this using my reading that you are looking to create function(s) that have two required arguments, then three function configuration options that constitute optional argument(s). If I misunderstood, please let me know!

In this sort of setup, the function definition might be something like:

  • Parameter 1: Whatever type required
  • Parameter 2: Whatever type required
  • Parameter 3: Object with three object attributes representing the three function configuration options. This could enable the AllowNullValue flag so practitioners don’t need to necessarily pass an empty/unconfigured object of {}.

As you found, in Terraform, maps are collection types which have a single element type. Objects are mappings of specific object attribute names to values. Those values can have differing types.

This would be represented in configuration calls as (with comments describing how the data looks to the function logic):

# null object
provider::TYPE::NAME(param1, param2, null)

# empty object (all options are null)
provider::TYPE::NAME(param1, param2, {})

# partially configured object (option2 is null, option3 is null)
provider::TYPE::NAME(param1, param2, {
  option1 = true
})

# fully configured object
provider::TYPE::NAME(param1, param2, {
  option1 = true
  option2 = 123
  option3 = "test"
})

Another way to handle the configuration object situation, would be to switch that final parameter from a regular parameter to a variadic parameter.

In that case, configuration calls could look like:

# null tuple of objects
provider::TYPE::NAME(param1, param2)

# tuple of one null object
provider::TYPE::NAME(param1, param2, null)

# tuple of one empty object
provider::TYPE::NAME(param1, param2, {})

# tuple of one partially configured object (option2 is null, option3 is null)
provider::TYPE::NAME(param1, param2, {
  option1 = true
})

# tuple of one fully configured object
provider::TYPE::NAME(param1, param2, {
  option1 = true
  option2 = 123
  option3 = "test"
})

# tuple of multiple partially configured objects
provider::TYPE::NAME(
  param1,
  param2,
  {
    option1 = true
  },
  {
    option2 = 123
  },
)

# tuple of multiple fully configured objects
provider::TYPE::NAME(
  param1,
  param2,
  {
    option1 = true
    option2 = 123
    option3 = "test"
  },
  {
    option1 = false
    option2 = 456
    option3 = "not-test"
  }
)

As you might be able to see here, the variadic parameter option is nice in the sense that the configuration does not necessarily need to include the final argument of options, but it also complicates the situation by allowing multiple arguments, potentially with “conflicting” configuration. You would need to decide how to best handle that situation in your function logic, whether it be returning an error if multiple options are found, returning an error only if multiple “conflicting” option values are found, or having later “conflicting” options overwrite earlier options.

If you have any particular suggestions on how we can improve either the terraform-plugin-framework website documentation or Go documentation, please let us know by creating an issue in GitHub.

Thanks and hope this helps.

Hi and thanks for the suggestions. To make it more concrete, I currently handle this situation with a data type in my provider but I feel like a function would be much cleaner (assuming terraform is able to realize that if no parameters change, the function will always return the same thing or at least the function is called at plan time).

So in pseudo code the function would be like this:

func generate_name(name: string, type: string, location: string = null, extra_vars: map[string])

That is to say, location is optional. It has a default but the default comes from the provider. extra_vars is also optional but your variadic solution handles that well.

Hi,

I tried out your solution and it doesn’t work for the object. I have this:

function.ObjectParameter{
				Name:           "args",
				Description:    "Extra variables for use in format (see Supported Variables) for this data source (may override provider extra_tokens).",
				AllowNullValue: true,
				AttributeTypes: map[string]attr.Type{
					locationProp:    types.StringType,
					extraTokensProp: types.MapType{ElemType: types.StringType},
				},
			},

But this requires me to specify every field, it’s just that I can set some fields to null. Is this a bug?