From 91a4af9c8a605b3586e966d09143a5454539e9a4 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 30 Oct 2017 17:34:04 -0700 Subject: [PATCH] website: rewrite modules usage documentation for new features The modules mechanism has changed quite a bit for version 0.11 and so although simple usage remains broadly compatible there are some significant changes in the behavior of more complex modules. Since large parts of this were rewritten anyway, I also took the opportunity to do some copy-editing to make the prose on this page more consistent with our usual editorial voice and to wrap the long lines. --- website/docs/configuration/modules.html.md | 56 ++-- website/docs/configuration/variables.html.md | 6 + website/docs/modules/usage.html.markdown | 335 +++++++++++++++---- 3 files changed, 296 insertions(+), 101 deletions(-) diff --git a/website/docs/configuration/modules.html.md b/website/docs/configuration/modules.html.md index 7c6353b07..15c1b37e1 100644 --- a/website/docs/configuration/modules.html.md +++ b/website/docs/configuration/modules.html.md @@ -19,8 +19,6 @@ already. ## Example -Module configuration looks like the following: - ```hcl module "consul" { source = "hashicorp/consul/aws" @@ -30,40 +28,30 @@ module "consul" { ## Description -The `module` block configures a module and tells Terraform to build -its resources. Multiple module blocks may be used to configure and use -multiple modules. +A `module` block instructs Terraform to create an instance of a module, +and in turn to instantiate any resources defined within it. -The NAME of the module is logical: it is used only to reference the -module in other places in the configuration. It has no effect on the -source of the module. Therefore, you may name modules whatever you'd like. +The name given in the block header is used to reference the particular module +instance from expressions within the calling module, and to refer to the +module on the command line. It has no meaning outside of a particular +Terraform configuration. -Within the block (the `{ }`) is configuration for the module. -The only required key is `source`, which tells Terraform where this module -can be downloaded from. Valid source values are covered in more detail -in the -[module section](/docs/modules/index.html). +Within the block body is the configuration for the module. All attributes +within the block must correspond to [variables](/docs/configuration/variables.html) +within the module, with the exception of the following which Terraform +treats as special: -Other configuration within the module are dependent on the module itself. -Module configuration maps directly to -[variables](/docs/configuration/variables.html) within the module, so -parameters can have any of the data types that variables support, including -lists and maps. +* `source` - (Required) A [module source](/docs/modules/sources.html) string + specifying the location of the child module source code. -## Syntax +* `version` - (Optional) A [version constraint](/docs/modules/usage.html#module-versions) + string that specifies which versions of the referenced module are acceptable. + The newest version matching the constraint will be used. `version` is supported + only for modules retrieved from module registries. -The full syntax is: - -```text -module NAME { - source = SOURCE_URL - - CONFIG ... -} -``` - -where `CONFIG` is: - -```text -KEY = VALUE -``` +* `providers` - (Optional) A map whose keys are provider configuration names + that are expected by child module and whose values are corresponding + provider names in the calling module. This allows + [provider configurations to be passed explicitly to child modules](/docs/modules/usage.html#providers-within-modules). + If not specified, the child module inherits all of the default (un-aliased) + provider configurations from the calling module. diff --git a/website/docs/configuration/variables.html.md b/website/docs/configuration/variables.html.md index 63e64c4b9..719723ab2 100644 --- a/website/docs/configuration/variables.html.md +++ b/website/docs/configuration/variables.html.md @@ -69,6 +69,12 @@ These are the parameters that can be set: future version of Terraform will expose these descriptions as part of some Terraform CLI command. +The name of a variable can be any valid identifier. However, due to the +interpretation of [module configuration blocks](/docs/configuration/modules.html), +the names `source`, `version` and `providers` are reserved for Terraform's own +use and are thus not recommended for any module intended to be used as a +child module. + -> **Note**: Default values can be strings, lists, or maps. If a default is specified, it must match the declared type of the variable. diff --git a/website/docs/modules/usage.html.markdown b/website/docs/modules/usage.html.markdown index 3ba349e57..da9f87bbb 100644 --- a/website/docs/modules/usage.html.markdown +++ b/website/docs/modules/usage.html.markdown @@ -7,7 +7,7 @@ description: Using modules in Terraform is very similar to defining resources. # Module Usage -Using modules in Terraform is very similar to defining resources: +Using child modules in Terraform is very similar to defining resources: ```shell module "consul" { @@ -18,18 +18,237 @@ module "consul" { You can view the full documentation for configuring modules in the [Module Configuration](/docs/configuration/modules.html) section. -In modules we only specify a name rather than a name and a type (as in resources). This name can be used elsewhere in the configuration to reference the module and its outputs. +In modules we only specify a name, rather than a name and a type as for resources. +This name is used elsewhere in the configuration to reference the module and +its outputs. -The source tells Terraform what to create. In this example, we create +The source tells Terraform what to create. In this example, we instantiate the [Consul module for AWS](https://registry.terraform.io/modules/hashicorp/consul/aws) -from the [Terraform Registry](https://registry.terraform.io). You can learn -more about the [source configuration below](#source). +from the [Terraform Registry](https://registry.terraform.io). Other source +types are supported, as described in the following section. -Just like a resource, the module configuration can be deleted to remove the module. +Just like a resource, the a module's configuration can be deleted to destroy the +resources belonging to the module. -## Multiple instances of a module +## Source -You can instantiate a module multiple times. +The only required configuration key for a module is the `source` parameter. The +value of this tells Terraform where to download the module's source code. +Terraform comes with support for a variety of module sources. + +The recommended source for external modules is a +[Terraform Registry](/docs/registry/index.html), which provides the full +capabilities of modules such as version constraints. +Registry modules are specified using a simple slash-separated path like the +`hashicorp/consul/aws` path used in the above example. The full source string +for each registry module can be found from the registry website. + +Terraform also supports modules in local directories, identified by a relative +path starting with either `./` or `../`. Such local modules are useful to +organize code more complex repositories, and are described in more detail +in [_Creating Modules_](/docs/modules/create.html). + +Finally, Terraform can download modules directly from various storage providers +and version control systems. These sources do not support versioning and other +registry benefits, but can be convenient for getting started when already +available within an organization. The full list of available sources +are documented in [the module sources documentation](/docs/modules/sources.html). + +When a configuration uses modules, they must first be installed by running +[`terraform init`](/docs/commands/init.html): + +```shell +$ terraform init +``` + +This command will download any modules that haven't been updated already, +as well as performing other Terraform working directory initialization such +as installing providers. + +By default the command will not check for available updates to already-installed +modules, but you can use the `-update` option to check for available upgrades. +When version constraints are specified (as described in the following section) +a newer version will be used only if it is within the given constraint. + +## Module Versions + +It is recommended to explicitly constrain the acceptable version numbers for +each external module so that upstream changes aren't automatically adopted, +since this may result in unexpected or unwanted changes changes. + +The `version` attribute within the `module` block is used for this purpose: + +```shell +module "consul" { + source = "hashicorp/consul/aws" + version = "0.0.5" + + servers = 3 +} +``` + +The `version` attribute value may either be a single explicit version or +a version constraint expression. Constraint expressions use the following +syntax to specify a _range_ of versions that are acceptable: + +* `>= 1.2.0`: version 1.2.0 or newer +* `<= 1.2.0`: version 1.2.0 or older +* `~> 1.2`: any non-beta patch release within the `1.2` range +* `>= 1.0.0, <= 2.0.0`: any version between 1.0.0 and 2.0.0 inclusive + +When depending on third-party modules, references to specific versions are +recommended since this ensures that updates only happen when convenient to you. + +For modules maintained within your organization, a version range strategy +may be appropriate if a semantic versioning methodology is used consistently +or if there is a well-defined release process that avoids unwanted updates. + +Version constraints are supported only for modules installed from a module +registry, such as the [Terraform Registry](https://registry.terraform.io/). +Other module sources may provide their own versioning mechanisms within the +source string itself, or they may not support versions at all. In particular, +modules whose sources are local file paths do not support `version` because +they are constrained to share the same version as their caller by being +obtained by the same source repository. + +## Configuration + +The arguments used in a `module` block, such as the `servers` parameter above, +correspond to [variables](/docs/configuration/variables.html) within the module +itself. You can therefore discover all the available variables for a module by +inspecting the source of it. + +The special arguments `source`, `version` and `providers` are exceptions. These +are used for special purposes by Terraform and should therefore not be used +as variable names within a module. + +## Outputs + +Modules encapsulate their resources. A resource in one module cannot directly depend on resources or attributes in other modules, unless those are exported through [outputs](/docs/configuration/outputs.html). These outputs can be referenced in other places in your configuration, for example: + +```hcl +resource "aws_instance" "client" { + ami = "ami-408c7f28" + instance_type = "t1.micro" + availability_zone = "${module.consul.server_availability_zone}" +} +``` + +This is deliberately very similar to accessing resource attributes. Instead of +referencing a resource attribute, however, the expression in this case +references an output of the module. + +Just like with resources, interpolation expressions can create implicit +dependencies on resources and other modules. Since modules encapsulate +other resources, however, the dependency is not on the module as a whole +but rather on the `server_availability_zone` output specifically, which +allows Terraform to work on resources in different modules concurrently rather +than waiting for the entire module to be complete before proceeding. + +## Providers within Modules + +For convenience in simple configurations, child modules by default inherit +provider configurations from their parent. This means that in most cases +only the root module needs explicit `provider` blocks, and then any defined +provider can be freely used with the same settings in child modules. + +In more complex situations it may be necessary for a child module to use +different provider settings than its parent. In this situation it is +possible to define +[multiple provider instances](/docs/configuration/providers.html#multiple-provider-instances) +and pass them explicitly and selectively to a child module: + +```hcl +# The default "aws" configuration is used for AWS resources in the root +# module where no explicit provider instance is selected. +provider "aws" { + region = "us-west-1" +} + +# A non-default, or "aliased" configuration is also defined for a different +# region. +provider "aws" { + alias = "usw2" + region = "us-west-2" +} + +# An example child module is instantiated with the _aliased_ configuration, +# so any AWS resources it defines will use the us-west-2 region. +module "example" { + source = "./example" + providers = { + aws = "aws.usw2" + } +} +``` + +The `providers` argument within a `module` block serves the same purpose as +the `provider` argument within a resource as described for +[multiple provider instances](/docs/configuration/providers.html#multiple-provider-instances), +but is a map rather than a single string because a module may contain resources +from many different providers. + +Once the `providers` argument is used in a `module` block it overrides all of +the default inheritance behavior, so it is necessary to enumerate mappings +for _all_ of the required providers. This is to avoid confusion and surprises +when mixing both implicit and explicit provider passing. + +In more complex situations it may be necessary for a child module _itself_ +to have multiple instances of the same provider. For example, a module +that configures connectivity between networks in two AWS regions is likely +to need both a source and a destination region. In that case, the root module +may look something like this: + +```hcl +provider "aws" { + alias = "usw1" + region = "us-west-1" +} + +provider "aws" { + alias = "usw2" + region = "us-west-2" +} + +module "tunnel" { + source = "./tunnel" + providers = { + "aws.src" = "aws.usw1" + "aws.dst" = "aws.usw2" + } +} +``` + +The subdirectory `./tunnel` should then contain configuration like the +following, to declare the two provider aliases it expects: + +``` +provider "aws" { + alias = "src" +} + +provider "aws" { + alias = "dst" +} +``` + +Each resource should then have its own `provider` attribute set to either +`"aws.src"` or `"aws.dst"` to choose which of the provider instances to use. + +It is recommended to use the default inheritance behavior in most cases where +only a single default instance of each provider is used, and switch to +passing providers explicitly as soon as multiple instances are needed. + +In all cases it is recommended to keep explicit provider declarations only in +the root module and pass them (either implicitly or explicitly) down to +descendent modules. This avoids the provider configurations being "lost" +when descendent providers are removed from the configuration. It also allows +the user of a configuration to determine which providers require credentials +by inspecting only the root module. + +## Multiple Instances of a Module + +A particular module source can be instantiated multiple times: ```hcl # my_buckets.tf @@ -50,7 +269,7 @@ module "media_bucket" { variable "name" {} # this is the input parameter of the module -resource "aws_s3_bucket" "the_bucket" { +resource "aws_s3_bucket" "example" { # ... } @@ -59,82 +278,64 @@ resource "aws_iam_user" "deploy_user" { } ``` -In this example you define a module in the `./publish_bucket` subdirectory. That module has configuration to create a bucket resource, set access and caching rules. The module wraps the bucket and all the other implementation details required to configure a bucket. +This example defines a local child module in the `./publish_bucket` +subdirectory. That module has configuration to create an S3 bucket. The module +wraps the bucket and all the other implementation details required to configure +a bucket. -We can then define the module multiple times in our configuration by naming each instantiation of the module uniquely, here `module "assets_bucket"` and `module "media_bucket"`, whilst specifying the same module `source`. +We can then instantiate the module multiple times in our configuration by +giving each instance a unique name -- here `module "assets_bucket"` and +`module "media_bucket"` -- whilst specifying the same `source` value. -The resource names in your module get prefixed by `module.` when instantiated, for example the `publish_bucket` module creates `aws_s3_bucket.the_bucket` and `aws_iam_access_key.deploy_user`. The full name of the resulting resources will be `module.assets_bucket.aws_s3_bucket.the_bucket` and `module.assets_bucket.aws_iam_access_key.deploy_user`. Be cautious of this when extracting configuration from your files into a module, the name of your resources will change and Terraform will potentially destroy and recreate them. Always check your configuration with `terraform plan` before running `terraform apply`. +Resources from child modules are prefixed with `module.` +when displayed in plan output and elsewhere in the UI. For example, the +`./publish_bucket` module contains `aws_s3_bucket.example`, and so the two +instances of this module produce S3 bucket resources with [_resource addresses_](/docs/internals/resource-addressing.html) +`module.assets_bucket.aws_s3_bucket.example` and `module.media_bucket.aws_s3_bucket.example` +respectively. These full addresses are used within the UI and on the command +line, but are not valid within interpolation expressions due to the +encapsulation behavior described above. -## Source +When refactoring an existing configuration to introduce modules, moving +resource blocks between modules causes Terraform to see the new location +as an entirely separate resource to the old. Always check the execution plan +after performing such actions to ensure that no resources are surprisingly +deleted. -The only required configuration key for a module is the `source` parameter. The value of this tells Terraform where the module can be downloaded, updated, etc. Terraform comes with support for a variety of module sources. The recommended source for modules is a -[Terraform Registry](/docs/registry/index.html) since this enables additional -features for modules such as versioning. +Each instance of a module may optionally have different providers passed to it +using the `providers` argument described above. This can be useful in situations +where, for example, a duplicated set of resources must be created across +several regions or datacenters. -Terraform can also download modules directly from various storage providers -and version control systems. These sources do not support versioning and other -registry benefits. The full list of available sources -are documented in the [module sources documentation](/docs/modules/sources.html). +## Summarizing Modules in the UI -Prior to running any Terraform command with a configuration that uses modules, you'll have to [get](/docs/commands/get.html) the modules. This is done using the [get command](/docs/commands/get.html). +By default, commands such as the [plan command](/docs/commands/plan.html) and +[graph command](/docs/commands/graph.html) will show each resource in a nested +module to represent the full scope of the configuration. For more complex +configurations, the `-module-depth` option may be useful to summarize some or all +of the modules as single objects. -```shell -$ terraform get -``` - -This command will download the modules if they haven't been already. - -By default, the command will not check for updates, so it is safe (and fast) to run multiple times. You can use the `-update` flag to check and download updates. - -## Configuration - -The parameters used to configure modules, such as the `servers` parameter above, map directly to [variables](/docs/configuration/variables.html) within the module itself. Therefore, you can quickly discover all the configuration -for a module by inspecting the source of it. - -Additionally, because these map directly to variables, module configuration can have any data type available for variables, including maps and lists. - -## Outputs - -Modules encapsulate their resources. A resource in one module cannot directly depend on resources or attributes in other modules, unless those are exported through [outputs](/docs/configuration/outputs.html). These outputs can be referenced in other places in your configuration, for example: - -```hcl -resource "aws_instance" "client" { - ami = "ami-408c7f28" - instance_type = "t1.micro" - availability_zone = "${module.consul.server_availability_zone}" -} -``` - -This purposely is very similar to accessing resource attributes. Instead of mapping to a resource, however, the variable in this case maps to an output of a module. - -Just like resources, this will create a dependency from the `aws_instance.client` resource to the module, so the module will be built first. - -To use module outputs via command line you have to specify the module name before the variable, for example: - -```shell -$ terraform output -module=consul server_availability_zone -``` - -## Plans and Graphs - -Commands such as the [plan command](/docs/commands/plan.html) and [graph command](/docs/commands/graph.html) will expand modules by default. You can use the `-module-depth` parameter to limit the graph. - -For example, with a configuration similar to what we've built above, here is what the graph output looks like by default: +For example, with a configuration similar to what we've built above, the default +graph output looks like the following: ![Terraform Expanded Module Graph](docs/module_graph_expand.png) -If instead we set `-module-depth=0`, the graph will look like this: +If we instead set `-module-depth=0`, the graph will look like this: ![Terraform Module Graph](docs/module_graph.png) -Other commands work similarly with modules. Note that the `-module-depth` flag is purely a formatting flag; it doesn't affect what modules are created or not. +Other commands work similarly with modules. Note that `-module-depth` only +affects how modules are presented in the UI; it does not affect how modules +and their contained resources are processed by Terraform operations. ## Tainting resources within a module -The [taint command](/docs/commands/taint.html) can be used to _taint_ specific resources within a module: +The [taint command](/docs/commands/taint.html) can be used to _taint_ specific +resources within a module: ```shell $ terraform taint -module=salt_master aws_instance.salt_master ``` -It is currently not possible to taint an entire module. +It is not possible to taint an entire module. Instead, each resource within +the module must be tainted separately.