How to check a nested attribute's presence in a configuration?

resource "cads_application" "app1" {
  service_profiles {
    name = "a3sp"
    lb {
      name = "lbsp"
      lb_params {
        algorithm       = "ROUND_ROBIN"
        stickiness_type = "SOURCE_IP"
        # max_server_connections = 1
      }
    }
}

I am writing a custom terraform provider. I need to read the above configuration.

I have written schema in such a way that lb and lb_params are of TypeList with MaxItems=1.

If I do not pass max_server_connections under lb_params in config (.tf file), the terraform SDK is sending max_server_connections=0 in payload.

I do not need this in my payload if it’s not present in the config.

How can I achieve it?

Even though I tried to use the below if block for max_server_connections, I see the max_server_connections in the payload.

// service_profiles is just like network_functions
if service_profiles, ok := d.GetOk("service_profiles"); ok {
	serviceProfilesList := make([]interface{}, 0)
	sp := make(map[string]interface{})
	for _, v := range service_profiles.([]interface{}) {
		sp["name"] = v.(map[string]interface{})["name"].(string)
		// lb is of TypeList. take only the first element
		// if lb length is 0, then it is a service profile with no lb params
		if len(v.(map[string]interface{})["lb"].([]interface{})) > 0 {
			lbRaw := v.(map[string]interface{})["lb"].([]interface{})[0].(map[string]interface{})
			lbMap := make(map[string]interface{})
			lbMap["name"] = lbRaw["name"].(string)
			//lb_params is of TypeList. take only the first element
			if len(lbRaw["lb_params"].([]interface{})) > 0 {
				lbParamsRaw := lbRaw["lb_params"].([]interface{})[0].(map[string]interface{})
				lbParamsMap := make(map[string]interface{})
				lbParamsMap["algorithm"] = lbParamsRaw["algorithm"].(string)
				lbParamsMap["stickiness_type"] = lbParamsRaw["stickiness_type"].(string)
				if _, ok := lbParamsRaw["max_server_connections"]; ok {
					lbParamsMap["max_server_connections"] = lbParamsRaw["max_server_connections"].(int)
				}
				lbMap["lb_params"] = lbParamsMap
			}
			sp["lb"] = lbMap
		}
	}
}

Below is my Schema:

package schemata

import (
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func ServiceProfileSchema() map[string]*schema.Schema {
	return map[string]*schema.Schema{
		"name": {
			Type:     schema.TypeString,
			Required: true,
		},
		"lb": {
			Type:     schema.TypeList,
			Optional: true,
			MaxItems: 1,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"name": {
						Type:     schema.TypeString,
						Optional: true,
					},
					"lb_params": {
						Type:     schema.TypeList,
						Optional: true,
						MaxItems: 1,
						Elem: &schema.Resource{
							Schema: map[string]*schema.Schema{
								"algorithm": {
									Type:     schema.TypeString,
									Optional: true,
								},
								"stickiness_type": {
									Type:     schema.TypeString,
									Optional: true,
								},
								"max_server_connections": {
									Type:     schema.TypeInt,
									Optional: true,
								},
							},
						},
					},
				},
			},
		},
	}
}

@apparentlymart I see you help many developers who write terraform providers. Can you help me here please?

Hi @sumanth-lingappa :wave: Thank you for bringing this up.

When working with terraform-plugin-sdk as your provider development framework, there are some oddities to be aware of with respect to how it handles data values. In effect, it does not implement support internally that values may be null and instead represents primitives as their Go zero-value (e.g. TypeInt as 0, TypeString as “”, TypeBool as false).

In your situation, it appears that you would need to also introduce a greater than 0 check for the max_server_connections value before including it with the payload. If the value 0 is significant and important to track, then it is possible to workaround the zero-value issue by either using a non-0 default (e.g. Default: -1) and using that new default value as a conditional for including it in the payload or potentially switching the schema implementation to a TypeString so you can distinguish between its zero-value (“”) and “0”.

As an aside: The terraform-plugin-framework provider development framework, which is in technical preview and will soon represent the recommended way to build providers, does not have this oddity. You can inspect values to determine if they are null, unknown, or have an actual value.

Hope this helps.

Thank you for replying @bflad

I am now considering moving to the new terraform-plugin-framework since I am writing a new provider.

Can I assume the new terraform-plugin-framework is backward compatible? Meaning my provider should not fail after 6 months because of the changes in the terraform-plugin-framework

Hi @sumanth-lingappa, great question.

The interface between Terraform CLI/core and providers is the Plugin Protocol and there are no “API services” or anything of that nature otherwise affecting the communication and data between the two. Both Terraform and providers are released as binaries, which means that once it is released, it cannot be changed without releasing a new version on either side. We treat the protocol itself with absolute capability in mind where new features are always additive with a new fully backwards compatible minor release of the protocol, or new major versions of the protocol (very unlikely as they affect the entire ecosystem) would only be introduced in new versions of Terraform, leaving old ones working the same as before.

Please also note the Which SDK? page in the documentation that does mention:

The framework is still under development and its interfaces may change as we learn more about how providers are interacting with it. We will strive to keep these changes as small and as minimally disruptive as possible, but they are a possibility with the framework.

This statement applies mainly to the framework’s Go structure and interface types, which may have some breaking changes (such as field renames, type changes, or package location changes) as the ecosystem adopts the new framework and we try to improve the developer experience. These changes only affect providers upgrading to newer framework versions though, as existing versions cannot be affected. The framework changelog provides upgrade notes when things are deprecated or have a breaking change. The window of these types of changes is getting smaller though and when version 1.0.0 is released, the framework will have code compatibility promises for minor version upgrades as well, similar to the older terraform-plugin-sdk project.