Search code examples
curlcmdcommand-lineterraform

How to write Windows-compatible command lines in Terraform local-exec provisioner


I am trying to run a command (a curl invocation) using terraform's local-exec provisioner under windows. It runs using cmd /C according to the logs.

Same command pasted in the terminal works fine.

resource "null_resource" "search_indexes" {
  for_each = local.indexes_set
  provisioner "local-exec" {
    command =  <<EOT
curl  https://${var.cognitive_search_name}.search.windows.net/indexes?api-version=2021-04-30-Preview  --header 'Accept: application/json' --header 'api-key: ${var.key}' --data '@${each.key}'
     EOT
  }
}

The output is as follows (abriged from progress reports):

module.(...).search_indexes["test-index.json"] (local-exec): Executing: ["cmd" "/C" "curl ...

exit status 6.  
curl: (6) Could not resolve host: application // actually part of first header argument
curl: (6) Could not resolve host: 1...F' // actually part of second header argument, api-key

so I see that it is run with cmd /C and words after the space in --header parameters are considered new URLs.

Using double quotes around parameters such as --headers, some escaping, but it still gives the same error.

Copy-pasted command placed in windows terminal works fine.

I suspect cmd or terraform do something to alter the command line so it is not looking correctly. How can I achieve correct behavior?


Solution

  • The procedure and conventions for quoting and escaping on the command line are quite different between Unix and Windows.

    On Unix, quoting and escaping is a concern of the shell itself, and Unix shells typically understand the contents of single quotes ' as totally verbatim text, while the contents of " can contain some interpolation metacharacters but spaces are still taken literally, rather than separators. The shell handles the tokenization into a set of arguments and passes those arguments to the program as an array of strings.

    On Windows, quoting and escaping is the concern of the program being run1, and not of the command interpreter cmd.exe. The command line you type will (more or less) be passed exactly as you wrote it to the program you're running -- curl in this case -- and it's left entirely up to that program to decide what symbols like " and ' might mean and where to draw the boundaries between separate arguments.

    In practice, most modern CLI applications on Windows are either built using Microsoft Visual C++ or with some other software that emulates its command line parsing conventions. If curl is such a program (which is likely, since it is written in C) then unfortunately the relevant rules are quite complicated, but for your purposes here we can reduce it to a few key facts:

    • The single quote character ' is taken literally. Unlike on Unix, it does not represent verbatim text.
    • You can use the double quote character " to represent a sequence where spaces should be taken literally rather than used as argument separators.

    Therefore in order to write a command line that is portable across both Unix and Windows, you will need to use the double quote character " and ensure that the strings you interpolate within the quotes don't contain any characters that would be interpreted as special by either set of parsing rules.

    In your case, I think that would be the following as long as you can be sure that none of the variables this refers to will contain quote characters, $ characters, or anything else that a Unix shell might interpret as special inside double quotes:

        command =  <<-EOT
          curl "https://${var.cognitive_search_name}.search.windows.net/indexes?api-version=2021-04-30-Preview" --header "Accept: application/json" --header "api-key: ${var.key}" --data "@${each.key}"
        EOT
    

    Writing portable command lines is quite challenging due to this significant architectural difference between Unix and Windows, which is one of the reasons why the Terraform documentation recommends considering provisioners as a last resort. If possible it would typically be better to use a specialized provider to make a request like this, but I'm not familiar enough with the API you're requesting here to know which provider would potentially offer it.


    1 Technically, the command interpreter does do some parsing of its own to handle environment variable substitution like %%FOO%% and the I/O redirection sequences like pipes and >/< markers.

    The command interpreter therefore has its own separate escaping syntax which can potentially come into play if your command line ended up including any of the command interpreter's special characters.

    Fortunately the command interpreter's parser also has some basic understanding of " as a marker for turning off some of its special processing, but we still need to watch out for %%VARIABLE%% sequences inside quotes, so you'll need to be sure that your variables to substitute don't contain anything like that.