Conflicts_with fails the plan when some of the attributes is set from a variable

Hi everyone

I am working on a terraform provider using the terraform plugin framework.

I am using ConflictsWith to make these two attributes image and source_instance mutually exclusive.

	"image": schema.StringAttribute{
		Optional: true,
		PlanModifiers: []planmodifier.String{
			stringplanmodifier.RequiresReplace(),
		},
        Validators: []validator.String{
          stringvalidator.ConflictsWith(
            path.Expressions{
              path.MatchRoot("source_instance"), 
            }...
          ),
          stringvalidator.AtLeastOneOf(
            path.Expressions{
              path.MatchRoot("image"), 
              path.MatchRoot("source_instance"), 
            }...
          ),
        },
	},
	"source_instance": schema.SingleNestedAttribute{
		Optional: true,
		Attributes: map[string]schema.Attribute{
			"project": schema.StringAttribute{
				Required: true,
			},
			"name": schema.StringAttribute{
				Required: true,
			},
			"snapshot": schema.StringAttribute{
				Optional: true,
			},
		},
		PlanModifiers: []planmodifier.Object{
			objectplanmodifier.RequiresReplace(),
		},
                Validators: []validator.Object{
                   objectvalidator.ConflictsWith(
                       path.Expressions{
                            path.MatchRoot("image"), 
                   }...),
                   objectvalidator.AtLeastOneOf(
                       path.Expressions{
                            path.MatchRoot("image"), 
                            path.MatchRoot("source_instance"), 
                        }...
                  ),
              },
        },

This works perfectly when I set the values of the attributes to literal values inside the resource or where I use variables with defaults for both values.

But when I use a mix of both the plan fails with

 Attribute "image" cannot be specified when "source_instance" is specified

Here is an example hcl

# This works
resource "my_resource"{
   image = "my_image"
   source_instance = null

# This works

variable "image"{ 
  default = "my_image"
}

variable "source_instance"{
  default = null
}

resource "my_resource"{
   image = var.image
   source_instance = var.source_instance
}

# This Does not work

variable "source_instance"{
  default = null
}

resource "my_resource"{
   image = "my_image"
   source_instance = var.source_instance
}

From the source code of of SchemaValidator, it says that it waits for the resource value to be known before the evaluation. In this case the value of source_instance will be null not unknown and the plan will succeed

Can you please help me understand what going on? What is the recommendations in this case?

Hey there @m.iduoad :wave:, thanks for posting the question and sorry you’re running into trouble here.

I think the root problem here seems to be likely a bug in the ConflictsWith validator when dealing with unknown values.

During static config validation in Terraform (which happens right before plan, you can test it directly with terraform validate), variables will be unknown (I believe since the source can be outside of the configuration, like an external file or env variable), so the ConflictsWith validator should have ignored that unknown value and wait until it’s fully evaluated.

When fixed, I would expect the following config to pass terraform validate and fail on terraform plan (currently they both fail):

$ cat resource.tf
variable "source_instance" {
  type = object({
    name    = string,
    project = string
  })
  default = null
}

resource "examplecloud_thing" "test" {
  image           = "my_image"
  source_instance = var.source_instance
}


$ TF_VAR_source_instance='{ project = "bar", name = "qux" }' terraform validate

Success! The configuration is valid.


$ TF_VAR_source_instance='{ project = "bar", name = "qux" }' terraform plan 

Planning failed. Terraform encountered an error while generating this plan.

╷
│ Error: Invalid Attribute Combination
│ 
│   with examplecloud_thing.test,
│   on resource.tf line 48, in resource "examplecloud_thing" "test":
│   48:   image           = "my_image"
│ 
│ Attribute "source_instance" cannot be specified when "image" is specified

I would imagine this bug affects more than just ConflictsWith, would you be willing to open a GH issue in the Go module that has this validator in it?