Terraform 0.12 + Habitat Provisioner using `validateFn`


I was looking through the Habitat Provisioner in the main terraform repo (to get it updated to support recent versions of Habitat + Terraform 0.12, and some other bug fixes). However, I am finding it difficult to figure out how the provisioner is wired in to the rest of terraform, specifically in regards to the validateFn implementation.

Here’s an example of the error I am getting from inside the validateFn in the provisioner:

Warning: Builder Url: 74D93920-ED26-11E3-AC10-0800200C9A66 (74D93920-ED26-11E3-AC10-0800200C9A66)

Warning: Builder Url: 74D93920-ED26-11E3-AC10-0800200C9A66 (74D93920-ED26-11E3-AC10-0800200C9A66)

Warning: Builder Url: https://foo.bar (https://foo.bar)

The first 2 “URLs” are UUIDs, which are from string interpolation of an input variable like this:

  provisioner "habitat" {
    [ ... ]
    url                = "${var.hab_bldr_url}"
    [ ... ]

The third one works successfully, but only if I hard-code the string to a URL: “https://foo.bar

I am not sure how I can get the post-interpolation string value inside my validateFn, which I can then use to validate my URL (or other items) against? Can someone point me in the right direction?

Hi @kmott!

That UUID you’re seeing is the way Terraform’s SDK represents “unknown value”, and is expected here because validation happens early in Terraform’s workflow, before it’s planning or applying changes.

A general pattern with Terraform plugin development is to skip running certain logic as long as the values it depends on are not known yet. Terraform gradually learns more as it progresses through plan and then apply, until eventually the whole configuration is known.

In your case, the validation logic for this URL must be skipped if the value is that UUID (which you can access as hcl2shim.UnknownVariableValue, where hcl2shim is github.com/hashicorp/terraform/config/hcl2shim).

Implementing validation in that way should allow detection of a syntax error in the URL eventually, once Terraform has made enough progress to know that value.

Since the validation rule you are looking at here is a single-argument check, another option is to add a ValidateFunc at the attribute level, inside its schema.Schema object. For per-argument checks, the SDK should automatically skip calling the ValidateFunc when that argument’s value is unknown, so you don’t have to implement that logic yourself. This per-argument form is more idiomatic, and commonly used in provider codebases although I think less often used in provisioners because it historically wasn’t available there in older versions of Terraform (before the SDK had support for provisioners).

Thank you so much for the info @apparentlymart. Looking at the ValidateFunc inside the schema.Schema object, I was thinking of doing something like this to validate the URL–would this be advisable? I’m using url.Parse to verify it’s valid, and returning an error if not.

			"url": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
					_, err := url.Parse(val.(string))
					if err != nil {
						errs = append(errs, err)
					return warns, errs

I edited my question and removed the question about ring_key and ring_key_contents since I’ve simplified it a bit. However, is there a way to reach out to a target host, and verify a file at a specific path exists as a part of my validateFn?

Another quick validation question, I have a ValidateFunc defined using something like this:

"strategy": &schema.Schema{
        Type:         schema.TypeString,
        Optional:     true,
        ValidateFunc: validation.StringInSlice([]string{"none", "rolling", "at-once"}, false),

However, if I put a bad value in strategy, like foo, the validation doesn’t seem to run, and it tries to still load the service with an invalid strategy.

Using url.Parse in the ValidateFunc is a reasonable idea, though I expect the error messages it returns are lacking in user-friendly context. If so, it’d be best to add some context like “must be a valid URL” to the front of the message to make it more actionable.

Validation functions must be local-only and not reach out to any external hosts or files. However, you can perform that sort of “online” validation during the plan phase using the CustomizeDiff function at the resource level. Validation is focused on whether the configuration is correct, while planning (and thus CustomizeDiff) is focused on making sure the planned change is valid and complete.

I think I have the provisioner just about ready, and should be able to create a PR shortly. However, I wasn’t sure about the documentation portion–should I also update the provisioner doc @ website/docs/provisioners/habitat.html.markdown, and include that in my PR submit, or is there a separate process for updating the docs?

Nevermind, I read the CONTRIBUTING.md, PR incoming soon :-).

EDIT: I was able to figure out the PR fork and submit, which is up here: https://github.com/hashicorp/terraform/pull/21776