Skip to content

ConvergeHCL ProposedChanges

Rebecca Skinner edited this page Feb 24, 2017 · 2 revisions

This proposal is open for community discussion in Issue #605. Please comment there. This wiki page will be updated to reflect any changes that have been made based on the discussion.

New Module Syntax

We propose to add a new method of including modules using the full, imported, or qualified name of an HCL file. We will also add a new mechanism for specifying module parameters. The existing method of module imports will still be supported.

Resource-style imports

We propose a new method of including modules, dubbed "resource-style imports", that allow HCL modules to be included with syntax similar to nnative resources.

With resource style imports the resource name is the path of the resource, starting from any location or qualified name identifier in the import path, and without the trailing `.hcl`.

import "/usr/include/converge/stdlib/homebrew" {
  as = "brew"
}

brew.install "zsh" {}

module "/usr/include/converge/stdlib/homebrew/install.hcl" "zsh" {}

Improved param syntax

We propose an improvement to the param syntax for imported modules. In place of specifying an explict param map, each param may be set by name as with native resources. The two examples below demonstrate the improved and traditional syntax for param definition:

stdlib.file "testfile" {
  destination = "/tmp/file"
  contents    = "testdata"
  uid         = 1
  gid         = 1
}

stdlib.file "testfile" {
  params = {
    "destination" = "/tmp/file"
    "contents"    = "testdata"
    "uid"         = 1
    "gid"         = 1
  }
}

Module Exports

We propose a new keyword, export which will allow a module to export a value or resource so that it will be available from an importing module.

Export syntax

An export block will allow a module to export values that can be referenced by including modules. Exported values may be any valid HCL datatype, or may reference an existing resource accessible to the module. Examples are given below.

Value Exports

Modules will be able to export a value in an export block. The type of the exported value is inferred using the same rules as param. In the example below we export a static string, as well as a templated string. Any templated values will be rendered automatically.

export {
  constant = "constant string"
  lookedup = "{{lookup `file.content.foo.destination`}}"
}

Resource Exports

Modules may also re-export any resources that they have defined. Re-exported fields are given the name of the field, and support an optional field, as, allowing the re-exported field to be qualified. In the example below we re-export a pair of task.query resources, one qualifed as q, and the other unqualified.

export {
  "task.query.foo" { }
  "task.query.bar" { as = "q" }
}

task.query "foo" {
  query = "echo foo"
}

task.query "bar" {
  query = "echo bar"
}

Referencing exported values

Exported values will be available through calls to lookup as with any values exported from native resources.

examples.exported "values" { }

file.content "exported-values" {
  destination = "/tmp/exported-values"
  content = <<EOF
constant:    {{lookup `examples.exported.values.foo`}}
lookedup:    {{lookup `examples.exported.values.lookedup`}}
foo-stdout:  {{lookup `examples.exported.values.task.query.foo.status.stdout`}}
bar-stdout:  {{lookup `examples.exported.values.q.status.stdout`}}
EOF
}

Rules for exported values

Exported values will adhere to the following semantics:

  • Any number of export blocks are allowed. The values in all exported blocks will be exported.
  • No field may be exported more than once with different values 1

Switch Statements

We propose a mechanism to allow users to extract data and resources from switch statements. The following changes will be made to facilitate this:

  • A new value will be exported from switch and case fields to identify which branch was executed
  • Data may now be exported from switch statements as long as it is well defined under all branches

Evaluated property

Switch and Case statements will now support new fields through lookup. Switch statements will gain map[string]bool branches, a mapping of branch names to a bool indicating whether or not they were evaluated, and string evaluated, a string that will represent the evaluated branch. Case statements will gain a boolean field, evaluated, that will be set to true if the branch was evaluated.

The following example demonstrates how to use the branches and evaluated fields for switches and cases:

switch "foo" {
  case "{{eq `1` `0`}}" "a" { }
  case "{{eq `1` `1`}}" "b" { }
  case "{{eq `1` `2`}}" "c" { }
}

file.content "results" {
  destination = "results"
  content = <<EOF
Evaluated Branch (switch): {{lookup `switch.foo.evaluated`}}

Known Conditionals:
{{range $name, $eval := {{lookup `switch.foo.branches`}}}}
$name = $eval
{{end}}

Evaluated (a): {{lookup `switch.foo.case.a.evaluated`}}
Evaluated (b): {{lookup `switch.foo.case.b.evaluated`}}
Evaluated (c): {{lookup `switch.foo.case.c.evaluated`}}
EOF
}

Exported Values

Switch statements support exporting both fields and templated or constant values. To export a field from a switch statement, all branches must have an export block with the same exported field names and types. Resources may be exported provided that they have the same real or qualified name, and are of the same type. When exporting fields a default branch is required to ensure that there will never be undefined values for exported fields.

switch "exports" {
  case "{{eq `1` `0`}}" "a" {
    export {
      "file.content.a" = {as = "f"}
      dest = "{{file.content.a.destination}}"
    }
    file.content "a" {
      destination = "branch-a"
      content     = "branch-a"
    }
  }
  case "{{eq `1` `1`}}" "b" {
    export {
      "file.content.b" = {as = "f"}
      dest = "{{file.content.c.destination}}"
    }
    file.content "b" {
      destination = "branch-b"
      content     = "branch-b"
    }
  }
  case "{{eq `1` `2`}}" "c" {
    export {
      "file.content.c" = {as = "f"}
      dest = "{{file.content.c.destination}}"
    }
    file.content "c" {
      destination = "branch-c"
      content     = "branch-c"
    }
  }
  default {
    export {
      "file.content.default" = {as = "f"}
      dest = "{{file.content.a.destination}}"
    }
    file.content "default" {
      destination = ""
      content     = ""
    }
  }
}

Fields that have been exported from switch blocks are accessible in the same way as all other exported fields. The example below demonstrates looking up the exported fields that we've just defined:

examples.exported.switch {}

file.content "output" {
  destination = "output"

  content = <<EOF
Content: {{lookup `examples.exported.switch.f.content`}}
Dest: {{lookup `examples.exported.switch.dest`}}
EOF
}

Footnotes

1 except for in the case of switch statements.