Dynamic resources

Hello, could you please say if it possible to do something like described below.
Section ‘limits’ have different set of fields which depend from value of field ‘server_type’?
I’m trying write terraform provider, but stuck on this step. Internal structure of
‘limits’ can be map[string]interface{}, but how to define this by terraform I don’t know.

resource "access_control" "ac" {
   bucket_id     = "${bucket.bucket.0.id}"
   target_id     = "${hypervisor_group.hvg.id}"

   server_type   = "${hypervisor_group.hvg.0.server_type}"
   type          = "compute_zone_resource"

   # if server_type == "virtual"
   limits {
      limit_cpu               = 0.0
      limit_cpu_share         = 0.0
      limit_cpu_units         = 0.0
      limit_memory            = 0.0
      limit_default_cpu       = 0.0
      limit_min_cpu           = 0.0
      limit_min_memory        = 0.0
      limit_default_cpu_share = 0.0
      limit_min_cpu_priority  = 0.0

      use_cpu_units           = false
      use_default_cpu         = false
      use_default_cpu_share   = false
   }
   # if server_type == "smart"
   limits {
     limit_cpu               = 0.0
     limit_cpu_share         = 0.0
     limit_cpu_units         = 0.0
     limit_memory            = 0.0

     use_cpu_units           = false
     use_default_cpu         = false
     use_default_cpu_share   = false
   }
 }

Hi @skydion,

It’s not possible to vary the schema depending on the dynamic data. Terraform must decode the configuration before any provider logic runs, and decoding the configuration requires knowing its schema.

For situations where this has arisen before, providers have defined a separate block type for each situation. In this case, perhaps the block types would be named virtual_server_limits and smart_server_limits.

To handle this, you’d add a CustomizeDiff function to this resource type and make it return an error if the block type used is inconsistent with the server type, with logic something like this:

func accessControlCustomizeDiff(d *schema.ResourceDiff, meta interface{}) error {
    if d.NewValueKnown("server_type") {
        serverType := d.Get("server_type").(string)
        virtualLimits := d.Get("virtual_server_limits").([]interface{})
        smartLimits := d.Get("smart_server_limits").([]interface{})
        if serverType != "virtual" && len(virtualLimits) > 0 {
            return errors.New("virtual_server_limits blocks can be used only with server type 'virtual'")
        }
        if serverType != "smart" && len(smartLimits) > 0 {
            return errors.New("smart_server_limits blocks can be used only with server type 'smart'")
        }
    }
}

This sort of check (which depends on the values of arguments rather than just their presence/absence) must be done in CustomizeDiff so that we can handle the situation where server_type isn’t known yet, using d.NewValueKnown. If server_type is known during planning, this check will block the creation of a plan. If server_type is derived from a value that can’t be known until the apply phase, this check will be skipped during the plan phase and will be run during the apply phase instead.

Thank you, @apparentlymart

This is maybe one of acceptable solution in this situation. But when we can change ‘type’ field then number of combination which we must check increasing… Okay I will think about this.

Hello,

I’m trying use CustomizeDiff as recomended by @apparentlymart, but I got situation where customize diff function don’t give possibility to destroy plan. Can I skip CustomizeDiff check for destroy and use only for plan and apply?

  if d.NewValueKnown("target_id") {
    resourceType  := d.Get("type").(string)
    targetID      := d.Get("target_id").(int)
    serverType    := d.Get("server_type").(string)

    switch resourceType {
    case "compute_zone_resource":
      res, _, err := client.HypervisorGroups.Get(context.Background(), targetID)
      if err != nil {
        log.Printf("[WARN] HypervisorGroup (%s) not found", res.Label)
        return err
      }

      if res.ServerType != serverType {
        return fmt.Errorf("target ID must point to the HypervisorGroup only with server type '%s'", serverType)
      }
    case "data_store_zone_resource":
      res, _, err := client.DataStoreGroups.Get(context.Background(), targetID)
      if err != nil {
        log.Printf("[WARN] DataStoreGroups (%s) not found", res.Label)
        return err
      }

      if res.ServerType != serverType {
        return fmt.Errorf("target ID must point to the DataStoreGroups only with server type '%s'", serverType)
      }
    }

terraform destroy -auto-approve

Error: target ID must point to the DataStoreGroups only with server type ‘smart’

on resource_access_conrol_ac.tf line 98, in resource “access_control” “ac_dszrs”:
98: resource “access_control” “ac_dszrs” {