diff --git a/website/docs/configuration/modules.html.md b/website/docs/configuration/modules.html.md index 91dac4ec2..b77d1cad1 100644 --- a/website/docs/configuration/modules.html.md +++ b/website/docs/configuration/modules.html.md @@ -53,6 +53,9 @@ calling module can use to refer to this instance of the module. Within the block body (between `{` and `}`) are the arguments for the module. Most of the arguments correspond to [input variables](./variables.html) defined by the module, including the `servers` argument in the above example. +Terraform also defines a few meta-arguments that are reserved by Terraform +and used for its own purposes; we will discuss those throughout the rest of +this section. All modules require a `source` argument, which is a meta-argument defined by Terraform CLI. Its value is either the path to a local directory of the @@ -65,6 +68,11 @@ The same source address can be specified in multiple `module` blocks to create multiple copies of the resources defined within, possibly with different variable values. +After adding, removing, or modifying `module` blocks, you must re-run +`terraform init` to allow Terraform the opportunity to adjust the installed +modules. By default this command will not upgrade an already-installed module; +use the `-upgrade` option to instead upgrade to the newest available version. + ## Accessing Module Output Values The resources defined in a module are encapsulated, so the calling module @@ -87,6 +95,48 @@ resource "aws_elb" "example" { For more information about referring to named values, see [Expressions](./expressions.html). +## Module Versions + +We recommend explicitly constraining the acceptable version numbers for +each external module to avoid unexpected or unwanted changes. + +Use the `version` attribute in the `module` block to specify versions: + +```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.0`: any non-beta version `>= 1.2.0` and `< 1.3.0`, e.g. `1.2.X` +* `~> 1.2`: any non-beta version `>= 1.2.0` and `< 2.0.0`, e.g. `1.X.Y` +* `>= 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/) or +[Terraform Enterprise's private module registry](/docs/enterprise/registry/index.html). +Other module sources can provide their own versioning mechanisms within the +source string itself, or might not support versions at all. In particular, +modules sourced from local file paths do not support `version`; since +they're loaded from the same source repository, they always share the same +version as their caller. + ## Other Meta-arguments Along with the `source` meta-argument described above, module blocks have @@ -112,3 +162,255 @@ future features. Since modules are a complex feature in their own right, further detail about how modules can be used, created, and published is included in [the dedicated section on modules](/docs/modules/index.html). + +## Providers within Modules + +In a configuration with multiple modules, there are some special considerations +for how resources are associated with provider configurations. + +While in principle `provider` blocks can appear in any module, it is recommended +that they be placed only in the _root_ module of a configuration, since this +approach allows users to configure providers just once and re-use them across +all descendent modules. + +Each resource in the configuration must be associated with one provider +configuration, which may either be within the same module as the resource +or be passed from the parent module. Providers can be passed down to descendent +modules in two ways: either _implicitly_ through inheritance, or _explicitly_ +via the `providers` argument within a `module` block. These two options are +discussed in more detail in the following sections. + +In all cases it is recommended to keep explicit provider configurations only in +the root module and pass them (whether implicitly or explicitly) down to +descendent modules. This avoids the provider configurations from being "lost" +when descendent modules 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. + +Provider configurations are used for all operations on associated resources, +including destroying remote objects and refreshing state. Terraform retains, as +part of its state, a reference to the provider configuration that was most +recently used to apply changes to each resource. When a `resource` block is +removed from the configuration, this record in the state is used to locate the +appropriate configuration because the resource's `provider` argument (if any) +is no longer present in the configuration. + +As a consequence, it is required that all resources created for a particular +provider configuration must be destroyed before that provider configuration is +removed, unless the related resources are re-configured to use a different +provider configuration first. + +### Implicit Provider Inheritance + +For convenience in simple configurations, a child module automatically inherits +default (un-aliased) provider configurations from its parent. This means that +explicit `provider` blocks appear only in the root module, and downstream +modules can simply declare resources for that provider and have them +automatically associated with the root provider configurations. + +For example, the root module might contain only a `provider` block and a +`module` block to instantiate a child module: + +```hcl +provider "aws" { + region = "us-west-1" +} + +module "child" { + source = "./child" +} +``` + +The child module can then use any resource from this provider with no further +provider configuration required: + +```hcl +resource "aws_s3_bucket" "example" { + bucket = "provider-inherit-example" +} +``` + +This approach is recommended in the common case where only a single +configuration is needed for each provider across the entire configuration. + +In more complex situations there may be [multiple provider instances](/docs/configuration/providers.html#multiple-provider-instances), +or a child module may need to use different provider settings than +its parent. For such situations, it's necessary to pass providers explicitly +as we will see in the next section. + +## Passing Providers Explicitly + +When child modules each need a different configuration of a particular +provider, or where the child module requires a different provider configuration +than its parent, the `providers` argument within a `module` block can be +used to define explicitly which provider configs are made available to the +child module. For example: + +```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 is similar to +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 +that may result when mixing both implicit and explicit provider passing. + +Additional provider configurations (those with the `alias` argument set) are +_never_ inherited automatically by child modules, and so must always be passed +explicitly using the `providers` map. 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" + } +} +``` + +In the `providers` map, the keys are provider names as expected by the child +module, while the values are the names of corresponding configurations in +the _current_ module. The subdirectory `./tunnel` must then contain +_proxy configuration blocks_ like the following, to declare that it +requires configurations to be passed with these from the `providers` block in +the parent's `module` block: + +```hcl +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 two provider instances to use. + +At this time it is required to write an explicit proxy configuration block +even for default (un-aliased) provider configurations when they will be passed +via an explicit `providers` block: + +```hcl +provider "aws" { +} +``` + +If such a block is not present, the child module will behave as if it has no +configurations of this type at all, which may cause input prompts to supply +any required provider configuration arguments. This limitation will be +addressed in a future version of Terraform. + +## Multiple Instances of a Module + +A particular module source can be instantiated multiple times: + +```hcl +# my_buckets.tf + +module "assets_bucket" { + source = "./publish_bucket" + name = "assets" +} + +module "media_bucket" { + source = "./publish_bucket" + name = "media" +} +``` + +```hcl +# publish_bucket/bucket-and-cloudfront.tf + +variable "name" {} # this is the input parameter of the module + +resource "aws_s3_bucket" "example" { + # ... +} + +resource "aws_iam_user" "deploy_user" { + # ... +} +``` + +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 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. + +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. + +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. + +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. + +## Tainting 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 not possible to taint an entire module. Instead, each resource within +the module must be tainted separately. diff --git a/website/docs/modules/composition.html.markdown b/website/docs/modules/composition.html.markdown new file mode 100644 index 000000000..c1f98008c --- /dev/null +++ b/website/docs/modules/composition.html.markdown @@ -0,0 +1,282 @@ +--- +layout: "docs" +page_title: "Module Composition" +sidebar_current: "docs-modules-composition" +description: |- + Module composition allows infrastructure to be described from modular + building blocks. +--- + +# Module Composition + +In a simple Terraform configuration with only one root module, we create a +flat set of resources and use Terraform's expression syntax to describe the +relationships between these resources: + +```hcl +resource "aws_vpc" "example" { + cidr_block = "10.1.0.0/16" +} + +resource "aws_subnet" "example" { + vpc_id = aws_vpc.example.id + + availability_zone = "us-west-2b" + cidr_block = cidrsubnet(aws_vpc.example.cidr_block, 4, 1) +} +``` + +When we introduce `module` blocks, our configuration becomes heirarchical +rather than flat: each module contains its own set of resources, and possibly +its own child modules, which can potentially create a deep, complex tree of +resource configurations. + +However, in most cases we strongly recommend keeping the module tree flat, +with only one level of child modules, and use a technique similar to the +above of using expressions to describe the relationships between the modules: + +```hcl +module "network" { + source = "./modules/aws-network" + + base_cidr_block = "10.0.0.0/8" +} + +module "consul_cluster" { + source = "./modules/aws-consul-cluster" + + vpc_id = module.network.vpc_id + subnet_ids = module.network.subnet_ids +} +``` + +We call this flat style of module usage _module composition_, because it +takes multiple [composable](https://en.wikipedia.org/wiki/Composability) +building-block modules and assembles them together to produce a larger system. +Instead of a module _embedding_ its dependencies, creating and managing its +own copy, the module _receives_ its dependencies from the root module, which +can therefore connect the same modules in different ways to produce different +results. + +The rest of this page discusses some more specific composition patterns that +may be useful when describing larger systems with Terraform. + +## Dependency Inversion + +In the example above, we saw a `consul_cluster` module that presumably describes +a cluster of [HashiCorp Consul](https://www.consul.io/) servers running in +an AWS VPC network, and thus it requires as arguments the identifiers of both +the VPC itself and of the subnets within that VPC. + +An alternative design would be to have the `consul_cluster` module describe +its _own_ network resources, but if we did that then it would be hard for +the Consul cluster to coexist with other infrastructure in the same network, +and so where possible we prefer to keep modules relatively small and pass in +their dependencies. + +This [dependency inversion](https://en.wikipedia.org/wiki/Dependency_inversion_principle) +approach also improves flexibility for future +refactoring, because the `consul_cluster` module doesn't know or care how +those identifiers are obtained by the calling module. A future refactor may +separate the network creation into its own configuration, and thus we may +pass those values into the module from data sources instead: + +```hcl +data "aws_vpc" "main" { + tags { + Environment = "production" + } +} + +data "aws_subnet_ids" "main" { + vpc_id = data.aws_vpc.main.id +} + +module "consul_cluster" { + source = "./modules/aws-consul-cluster" + + vpc_id = data.aws_vpc.main.id + subnet_ids = data.aws_subnet_ids.main.ids +} +``` + +This technique is also an answer to the common situation where in one +configuration a particular object already exists and just needs to be queried, +while in another configuration the equivalent object must be managed directly. +This commonly arises, for example, in development environment scenarios where +certain infrastructure may be shared across development environments for cost +reasons but managed directly inline in production for isolation. + +This is the best way to model such situations in Terraform. We do not recommend +attempting to construct "if this already exists then use it, otherwise create it" +conditional configurations; instead, decompose your system into modules and +decide for each configuration whether each modular object is directly managed or +merely used by reference. This makes each configuration a more direct description +of your intent, allowing Terraform to produce a more accurate plan, and making +it clearer to future maintainers how each configuration is expected to behave. + +## Multi-cloud Abstractions + +Terraform itself intentionally does not attempt to abstract over similar +services offered by different vendors, because we want to expose the full +functionality in each offering and yet unifying multiple offerings behind a +single interface will tend to require a "lowest common denominator" approach. + +However, through composition of Terraform modules it is possible to create +your own lightweight multi-cloud abstractions by making your own tradeoffs +about which platform features are important to you. + +Opportunities for such abstractions arise in any situation where multiple +vendors implement the same concept, protocol, or open standard. For example, +the basic capabilities of the domain name system are common across all vendors, +and although some vendors differentiate themselves with unique features such +as geolocation and smart load balancing, you may conclude that in your use-case +you are willing to eschew those features in return for creating modules that +abstract the common DNS concepts across multiple vendors: + +```hcl +module "webserver" { + source = "./modules/webserver" +} + +locals { + fixed_recordsets = [ + { + name = "www" + type = "CNAME" + ttl = 3600 + records = [ + "webserver01", + "webserver02", + "webserver03", + ] + }, + ] + server_recordsets = [ + for i, addr in module.webserver.public_ip_addrs : { + name = format("webserver%02d", i) + type = "A" + records = [addr] + } + ] +} + +module "dns_records" { + source = "./modules/route53-dns-records" + + route53_zone_id = var.route53_zone_id + recordsets = concat(local.fixed_recordsets, local.server_recordsets) +} +``` + +In the above example, we've created a lightweight abstraction in the form of +a "recordset" object. This contains the attributes that describe the general +idea of a DNS recordset that should be mappable onto any DNS provider. + +We then instantiate one specific _implementation_ of that abstraction as a +module, in this case deploying our recordsets to Amazon Route53. + +If we later wanted to switch to a different DNS provider, we'd need only to +replace the `dns_records` module with a new implementation targeting that +provider, and all of the configuration that _produces_ the recordset +definitions can remain unchanged. + +We can create lightweight abstractions like these by defining Terraform object +types representing the concepts involved and then using these object types +for module input variables. In this case, all of our "DNS records" +implementations would have the following variable declared: + +```hcl +variable "recordsets" { + type = object({ + name = string + type = string + ttl = number + records = list(string) + }) +} +``` + +While DNS serves as a simple example, there are many more opportunities to +exploit common elements across vendors. A more complex example is Kubernetes, +where there are now many different vendors offering hosted Kubernetes clusters +and even more ways to run Kubernetes yourself. + +If the common functionality across all of these implementations is sufficient +for your needs, you may choose to implement a set of different modules that +describe a particular Kubernetes cluster implementation and all have the common +trait of exporting the hostname of the cluster as an output value: + +```hcl +output "hostname" { + value = azurerm_kubernetes_cluster.main.fqdn +} +``` + +You can then write _other_ modules that expect only a Kubernetes cluster +hostname as input and use them interchangably with any of your Kubernetes +cluster modules: + +```hcl +module "k8s_cluster" { + source = "modules/azurerm-k8s-cluster" + + # (Azure-specific configuration arguments) +} + +module "monitoring_tools" { + source = "modules/monitoring_tools" + + cluster_hostname = module.k8s_cluster.hostname +} +``` + +## Data-only Modules + +Most modules contain `resource` blocks and thus describe infrastructure to be +created and managed. It may sometimes be useful to write modules that do not +describe any new infrastructure at all, but merely retrieve information about +existing infrastructure that was created elsewhere using +[data sources](/docs/configuration/data-sources.html). + +As with conventional modules, we suggest using this technique only when the +module raises the level of abstraction in some way, in this case by +encapsulating exactly how the data is retrieved. + +A common use of this technique is when a system has been decomposed into several +subsystem configurations but there is certain infrastructure that is shared +across all of the subsystems, such as a common IP network. In this situation, +we might write a shared module called `join-network-aws` which can be called +by any configuration that needs information about the shared network when +deployed in AWS: + +``` +module "network" { + source = "./modules/join-network-aws" + + environment = "production" +} + +module "k8s_cluster" { + source = "./modules/aws-k8s-cluster" + + subnet_ids = module.network.aws_subnet_ids +} +``` + +The `network` module itself could retrieve this data in a number of different +ways: it could query the AWS API directly using +[`aws_vpc`](/docs/providers/aws/d/vpc.html) +and +[`aws_subnet_ids`](/docs/providers/aws/d/subnet_ids.html) +data sources, or it could read saved information from a Consul cluster using +[`consul_keys`](https://www.terraform.io/docs/providers/consul/d/keys.html), +or it might read the outputs directly from the state of the configuration that +manages the network using +[`terraform_remote_state`](https://www.terraform.io/docs/providers/terraform/d/remote_state.html). + +The key benefit of this approach is that the source of this information can +change over time without updating every configuration that depends on it. +Furthermore, if you design your data-only module with a similar set of outputs +as a corresponding management module, you can swap between the two relatively +easily when refactoring. diff --git a/website/docs/modules/create.html.markdown b/website/docs/modules/create.html.markdown deleted file mode 100644 index bb0ed39bf..000000000 --- a/website/docs/modules/create.html.markdown +++ /dev/null @@ -1,225 +0,0 @@ ---- -layout: "docs" -page_title: "Creating Modules" -sidebar_current: "docs-modules-create" -description: How to create modules. ---- - -# Creating Modules - -Creating modules in Terraform is easy. You may want to do this to better organize your code, to make a reusable component, or just to learn more about Terraform. For any reason, if you already know the basics of Terraform, then creating a module is a piece of cake. - -Modules in Terraform are folders with Terraform files. In fact, when you run `terraform apply`, the current working directory holding -the Terraform files you're applying comprise what is called the _root module_. This itself is a valid module. - -Therefore, you can enter the source of any module, satisfy any required variables, run `terraform apply`, and expect it to work. - -Modules that are created for reuse should follow the -[standard structure](#standard-module-structure). This structure enables tooling -such as the [Terraform Registry](/docs/registry/index.html) to inspect and -generate documentation, read examples, and more. - -## An Example Module - -Within a folder containing Terraform configurations, create a subfolder called `child`. In this subfolder, make one empty `main.tf` file. Then, back in the root folder containing the `child` folder, add this to one of your Terraform configuration files: - -```hcl -module "child" { - source = "./child" -} -``` - -You've now created your first module! You can now add resources to the `child` module. - -**Note:** Prior to running the above, you'll have to run [the get command](/docs/commands/get.html) for Terraform to sync -your modules. This should be instant since the module is a local path. - -## Inputs/Outputs - -To make modules more useful than simple isolated containers of Terraform configurations, modules can be configured and also have outputs that can be consumed by your Terraform configuration. - -Inputs of a module are [variables](/docs/configuration/variables.html) and outputs are [outputs](/docs/configuration/outputs.html). There is no special syntax to define these, they're defined just like any other variables or outputs. You can think about these variables and outputs as the API interface to your module. - -Let's add a variable and an output to our `child` module. - -```hcl -variable "memory" {} - -output "received" { - value = "${var.memory}" -} -``` - -This will create a required variable, `memory`, and then an output, `received`, that will be the value of the `memory` variable. - -You can then configure the module and use the output like so: - -```hcl -module "child" { - source = "./child" - - memory = "1G" -} - -output "child_memory" { - value = "${module.child.received}" -} -``` - -If you now run `terraform apply`, you see how this works. - -## Paths and Embedded Files - -It is sometimes useful to embed files within the module that aren't Terraform configuration files, such as a script to provision a resource or a file to upload. - -In these cases, you can't use a relative path, since paths in Terraform are generally relative to the working directory from which Terraform was executed. Instead, use a module-relative path, by interpolating `path.module`: - -```hcl -resource "aws_instance" "server" { - # ... - - provisioner "remote-exec" { - script = "${path.module}/script.sh" - } -} -``` - -## Nested Modules - -You can nest a module within another module. This module will be hidden from your root configuration, so you'll have to re-expose any -variables and outputs you require. - -The [get command](/docs/commands/get.html) will automatically get all nested modules. - -You don't have to worry about conflicting versions of modules, since Terraform builds isolated subtrees of all dependencies. For example, one module might use version 1.0 of module `foo` and another module might use version 2.0, and this will all work fine within Terraform since the modules are created separately. - -## Standard Module Structure - -The standard module structure is a file and folder layout we recommend for -reusable modules. Terraform tooling is built to understand the standard -module structure and use that structure to generate documentation, index -modules for the registry, and more. - -The standard module expects the structure documented below. The list may appear -long, but everything is optional except for the root module. All items are -documented in detail. Most modules don't need to do any work to follow the -standard structure. - -* **Root module**. This is the **only required element** for the standard - module structure. Terraform files must exist in the root directory of - the module. This should be the primary entrypoint for the module and is - expected to be opinionated. For the - [Consul module](https://registry.terraform.io/modules/hashicorp/consul) - the root module sets up a complete Consul cluster. A lot of assumptions - are made, however, and it is fully expected that advanced users will use - specific nested modules to more carefully control what they want. - -* **README**. The root module and any nested modules should have README - files. This file should be named `README` or `README.md`. The latter will - be treated as markdown. There should be a description of the module and - what it should be used for. If you want to include an example for how this - module can be used in combination with other resources, put it in an [examples - directory like this](https://github.com/hashicorp/terraform-aws-consul/tree/master/examples). - Consider including a visual diagram depicting the infrastructure resources - the module may create and their relationship. The README doesn't need to - document inputs or outputs of the module because tooling will automatically - generate this. If you are linking to a file or embedding an image contained - in the repository itself, use a commit-specific absolute URL so the link won't - point to the wrong version of a resource in the future. - -* **LICENSE**. The license under which this module is available. If you are - publishing a module publicly, many organizations will not adopt a module - unless a clear license is present. We recommend always having a license - file, even if the license is non-public. - -* **main.tf, variables.tf, outputs.tf**. These are the recommended filenames for - a minimal module, even if they're empty. `main.tf` should be the primary - entrypoint. For a simple module, this may be where all the resources are - created. For a complex module, resource creation may be split into multiple - files but all nested module usage should be in the main file. `variables.tf` - and `outputs.tf` should contain the declarations for variables and outputs, - respectively. - -* **Variables and outputs should have descriptions.** All variables and - outputs should have one or two sentence descriptions that explain their - purpose. This is used for documentation. See the documentation for - [variable configuration](/docs/configuration/variables.html) and - [output configuration](/docs/configuration/outputs.html) for more details. - -* **Nested modules**. Nested modules should exist under the `modules/` - subdirectory. Any nested module with a `README.md` is considered usable - by an external user. If a README doesn't exist, it is considered for internal - use only. These are purely advisory; Terraform will not actively deny usage - of internal modules. Nested modules should be used to split complex behavior - into multiple small modules that advanced users can carefully pick and - choose. For example, the - [Consul module](https://registry.terraform.io/modules/hashicorp/consul) - has a nested module for creating the Cluster that is separate from the - module to setup necessary IAM policies. This allows a user to bring in their - own IAM policy choices. - -* **Examples**. Examples of using the module should exist under the - `examples/` subdirectory at the root of the repository. Each example may have - a README to explain the goal and usage of the example. Examples for - submodules should also be placed in the root `examples/` directory. - -A minimal recommended module following the standard structure is shown below. -While the root module is the only required element, we recommend the structure -below as the minimum: - -```sh -$ tree minimal-module/ -. -├── README.md -├── main.tf -├── variables.tf -├── outputs.tf -``` - -A complete example of a module following the standard structure is shown below. -This example includes all optional elements and is therefore the most -complex a module can become: - -```sh -$ tree complete-module/ -. -├── README.md -├── main.tf -├── variables.tf -├── outputs.tf -├── ... -├── modules/ -│   ├── nestedA/ -│   │   ├── README.md -│   │   ├── variables.tf -│   │   ├── main.tf -│   │   ├── outputs.tf -│   ├── nestedB/ -│   ├── .../ -├── examples/ -│   ├── exampleA/ -│   │   ├── main.tf -│   ├── exampleB/ -│   ├── .../ -``` - -## Publishing Modules - -If you've built a module that you intend to be reused, we recommend -[publishing the module](/docs/registry/modules/publish.html) on the -[Terraform Registry](https://registry.terraform.io). This will version -your module, generate documentation, and more. - -Published modules can be easily consumed by Terraform, and (from Terraform -0.11) you can also [constrain module versions](usage.html#module-versions) for safe and predictable -updates. The following example shows how easy it is to consume a module -from the registry: - -```hcl -module "consul" { - source = "hashicorp/consul/aws" -} -``` - -You can also gain all the benefits of the registry for private modules -by signing up for a [private registry](/docs/registry/private.html). diff --git a/website/docs/modules/index.html.markdown b/website/docs/modules/index.html.markdown index 96db65043..65f660d04 100644 --- a/website/docs/modules/index.html.markdown +++ b/website/docs/modules/index.html.markdown @@ -1,22 +1,194 @@ --- layout: "docs" -page_title: "Modules" +page_title: "Creating Modules" sidebar_current: "docs-modules" description: |- - Modules in Terraform are self-contained packages of Terraform configurations that are managed as a group. Modules are used to create reusable components in Terraform as well as for basic code organization. + A module is a container for multiple resources that are used together. --- -# Modules +# Creating Modules -Modules in Terraform are self-contained packages of Terraform configurations -that are managed as a group. Modules are used to create reusable components -in Terraform as well as for basic code organization. +A _module_ is a container for multiple resources that are used together. +Modules can be used to create lightweight abstractions, so that you can +describe your infrastructure in terms of its architecture, rather than +directly in terms of physical objects. -Modules are very easy to both use and create. Depending on what you're -looking to do first, use the navigation on the left to dive into how -modules work. +The `.tf` files in your working directory when you run [`terraform plan`](/docs/commands/plan.html) +or [`terraform apply`](/docs/commands/apply.html) together form the _root_ +module. That module may [call other modules](/docs/configuration/modules.html#calling-a-child-module) +and connect them together by passing output values from one to input values +of another. -## Definitions -**Root module** -That is the current working directory when you run [`terraform apply`](/docs/commands/apply.html) or [`get`](/docs/commands/get.html), holding the Terraform [configuration files](/docs/configuration/index.html). -It is itself a valid module. +To learn how to _use_ modules, see [the Modules configuration section](/docs/configuration/modules.html). +This section is about _creating_ re-usable modules that other configurations +can include using `module` blocks. + +## Module structure + +Re-usable modules are defined using all of the same +[configuration language](/docs/configuration/) concepts we use in root modules. +Most commonly, modules use: + +* [Input variables](/docs/configuration/variables.html) to accept values from + the calling module. +* [Output values](/docs/configuration/outputs.html) to return results to the + calling module, which it can then use to populate arguments elsewhere. +* [Resources](/docs/configuration/resources.html) to define one or more + infrastructure objects that the module will manage. + +To define a module, create a new directory for it and place one or more `.tf` +files inside just as you would do for a root module. Terraform can load modules +either from local relative paths or from remote repositories; if a module will +be re-used by lots of configurations you may wish to place it in its own +version control repository. + +Modules can also call other modules using a `module` block, but we recommend +keeping the module tree relatively flat and using [module composition](./composition.html) +as an alternative to a deeply-nested tree of modules, because this makes +the individual modules easier to re-use in different combinations. + +## When to write a module + +In principle any combination of resources and other constructs can be factored +out into a module, but over-using modules can make your overall Terraform +configuration harder to understand and maintain, so we recommend moderation. + +A good module should raise the level of abstraction by describing a new concept +in your architecture that is constructed from resource types offered by +providers. + +For example, `aws_instance` and `aws_elb` are both resource types belonging to +the AWS provider. You might use a module to represent the higher-level concept +"[HashiCorp Consul](https://www.consul.io/) cluster running in AWS" which +happens to be constructed from these and other AWS provider resources. + +We _do not_ recommend writing modules that are just thin wrappers around single +other resource types. If you have trouble finding a name for your module that +isn't the same as the main resource type inside it, that may be a sign that +your module is not creating any new abstraction and so the module is +adding unnecessary complexity. Just use the resource type directly in the +calling module instead. + +## Standard Module Structure + +The standard module structure is a file and directory layout we recommend for +reusable modules distributed in separate repositories. Terraform tooling is +built to understand the standard module structure and use that structure to +generate documentation, index modules for the module registry, and more. + +The standard module structure expects the layout documented below. The list may +appear long, but everything is optional except for the root module. Most modules +don't need to do any extra work to follow the standard structure. + +* **Root module**. This is the **only required element** for the standard + module structure. Terraform files must exist in the root directory of + the repository. This should be the primary entrypoint for the module and is + expected to be opinionated. For the + [Consul module](https://registry.terraform.io/modules/hashicorp/consul) + the root module sets up a complete Consul cluster. It makes a lot of assumptions + however, and we expect that advanced users will use specific _nested modules_ + to more carefully control what they want. + +* **README**. The root module and any nested modules should have README + files. This file should be named `README` or `README.md`. The latter will + be treated as markdown. There should be a description of the module and + what it should be used for. If you want to include an example for how this + module can be used in combination with other resources, put it in an [examples + directory like this](https://github.com/hashicorp/terraform-aws-consul/tree/master/examples). + Consider including a visual diagram depicting the infrastructure resources + the module may create and their relationship. + + The README doesn't need to document inputs or outputs of the module because + tooling will automatically generate this. If you are linking to a file or + embedding an image contained in the repository itself, use a commit-specific + absolute URL so the link won't point to the wrong version of a resource in the + future. + +* **LICENSE**. The license under which this module is available. If you are + publishing a module publicly, many organizations will not adopt a module + unless a clear license is present. We recommend always having a license + file, even if it is not an open source license. + +* **`main.tf`, `variables.tf`, `outputs.tf`**. These are the recommended filenames for + a minimal module, even if they're empty. `main.tf` should be the primary + entrypoint. For a simple module, this may be where all the resources are + created. For a complex module, resource creation may be split into multiple + files but any nested module calls should be in the main file. `variables.tf` + and `outputs.tf` should contain the declarations for variables and outputs, + respectively. + +* **Variables and outputs should have descriptions.** All variables and + outputs should have one or two sentence descriptions that explain their + purpose. This is used for documentation. See the documentation for + [variable configuration](/docs/configuration/variables.html) and + [output configuration](/docs/configuration/outputs.html) for more details. + +* **Nested modules**. Nested modules should exist under the `modules/` + subdirectory. Any nested module with a `README.md` is considered usable + by an external user. If a README doesn't exist, it is considered for internal + use only. These are purely advisory; Terraform will not actively deny usage + of internal modules. Nested modules should be used to split complex behavior + into multiple small modules that advanced users can carefully pick and + choose. For example, the + [Consul module](https://registry.terraform.io/modules/hashicorp/consul) + has a nested module for creating the Cluster that is separate from the + module to setup necessary IAM policies. This allows a user to bring in their + own IAM policy choices. + + If the root module includes calls to nested modules, they should use relative + paths like `./modules/consul-cluster` so that Terraform will consider them + to be part of the same repository or package, rather than downloading them + again separately. + + If a repository or package contains multiple nested modules, they should + ideally be [composable](./composition.html) by the caller, rather than + calling directly to each other and creating a deeply-nested tree of modules. + +* **Examples**. Examples of using the module should exist under the + `examples/` subdirectory at the root of the repository. Each example may have + a README to explain the goal and usage of the example. Examples for + submodules should also be placed in the root `examples/` directory. + + Because examples will often be copied into other repositories for + customization, any `module` blocks should have their `source` set to the + address an external caller would use, not to a relative path. + +A minimal recommended module following the standard structure is shown below. +While the root module is the only required element, we recommend the structure +below as the minimum: + +```sh +$ tree minimal-module/ +. +├── README.md +├── main.tf +├── variables.tf +├── outputs.tf +``` + +A complete example of a module following the standard structure is shown below. +This example includes all optional elements and is therefore the most +complex a module can become: + +```sh +$ tree complete-module/ +. +├── README.md +├── main.tf +├── variables.tf +├── outputs.tf +├── ... +├── modules/ +│   ├── nestedA/ +│   │   ├── README.md +│   │   ├── variables.tf +│   │   ├── main.tf +│   │   ├── outputs.tf +│   ├── nestedB/ +│   ├── .../ +├── examples/ +│   ├── exampleA/ +│   │   ├── main.tf +│   ├── exampleB/ +│   ├── .../ +``` diff --git a/website/docs/modules/publish.html.markdown b/website/docs/modules/publish.html.markdown new file mode 100644 index 000000000..317ec46cd --- /dev/null +++ b/website/docs/modules/publish.html.markdown @@ -0,0 +1,42 @@ +--- +layout: "docs" +page_title: "Publishing Modules" +sidebar_current: "docs-modules-publish" +description: |- + A module is a container for multiple resources that are used together. +--- + +## Publishing Modules + +If you've built a module that you intend to be reused, we recommend +[publishing the module](/docs/registry/modules/publish.html) on the +[Terraform Registry](https://registry.terraform.io). This will version +your module, generate documentation, and more. + +Published modules can be easily consumed by Terraform, and users can +[constrain module versions](/docs/configuration/modules.html#module-versions) +for safe and predictable updates. The following example shows how a caller +might use a module from the Terraform Registry: + +```hcl +module "consul" { + source = "hashicorp/consul/aws" +} +``` + +If you do not wish to publish your modules in the public registry, you can +instead use a [private registry](/docs/registry/private.html) to get +the same benefits. + +### Distribution via other sources + +Although the registry is the native mechanism for distributing re-usable +modules, Terraform can also install modules from +[various other sources](/docs/modules/sources.html). The alternative sources +do not support the first-class versioning mechanism, but some sources have +their own mechanisms for selecting particular VCS commits, etc. + +We recommend that modules distributed via other protocols still use the +[standard module structure](./#standard-module-structure) so that it can +be used in a similar way to a registry module, or even _become_ a registry +module at a later time. diff --git a/website/docs/modules/sources.html.markdown b/website/docs/modules/sources.html.markdown index 7c8f6357a..24d15a18f 100644 --- a/website/docs/modules/sources.html.markdown +++ b/website/docs/modules/sources.html.markdown @@ -7,9 +7,8 @@ description: The source argument within a module block specifies the location of # Module Sources -As introduced in [the _Usage_ section](/docs/modules/usage.html), the `source` -argument in a `module` block tells Terraform where to find the source code -for the desired child module. +The `source` argument in [a `module` block](/docs/configuration/modules.html) +tells Terraform where to find the source code for the desired child module. Terraform uses this during the module installation step of `terraform init` to download the source code to a directory on local disk so that it can be diff --git a/website/docs/modules/usage.html.markdown b/website/docs/modules/usage.html.markdown deleted file mode 100644 index 1f39c1e55..000000000 --- a/website/docs/modules/usage.html.markdown +++ /dev/null @@ -1,417 +0,0 @@ ---- -layout: "docs" -page_title: "Using Modules" -sidebar_current: "docs-modules-usage" -description: Using modules in Terraform is very similar to defining resources. ---- - -# Module Usage - -Using child modules in Terraform is very similar to defining resources: - -```shell -module "consul" { - source = "hashicorp/consul/aws" - servers = 3 -} -``` - -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 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 instantiate -the [Consul module for AWS](https://registry.terraform.io/modules/hashicorp/consul/aws) -from the [Terraform Registry](https://registry.terraform.io). Other source -types are supported, as described in the following section. - -Just like a resource, a module's configuration can be deleted to destroy the -resources belonging to the module. - -## Source - -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. - -We recommend using modules from the public [Terraform Registry](/docs/registry/index.html) -or from [Terraform Enterprise's private module registry](/docs/enterprise/registry/index.html). -These sources support version constraints for a more reliable experience, and -provide a searchable marketplace for finding the modules you need. - -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 in 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 `-upgrade` 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 - -We recommend explicitly constraining the acceptable version numbers for -each external module to avoid unexpected or unwanted changes. - -Use the `version` attribute in the `module` block to specify versions: - -```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.0`: any non-beta version `>= 1.2.0` and `< 1.3.0`, e.g. `1.2.X` -* `~> 1.2`: any non-beta version `>= 1.2.0` and `< 2.0.0`, e.g. `1.X.Y` -* `>= 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/) or -[Terraform Enterprise's private module registry](/docs/enterprise/registry/index.html). -Other module sources can provide their own versioning mechanisms within the -source string itself, or might not support versions at all. In particular, -modules sourced from local file paths do not support `version`; since -they're loaded from the same source repository, they always share the same -version as their caller. - -## 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 - -In a configuration with multiple modules, there are some special considerations -for how resources are associated with provider configurations. - -While in principle `provider` blocks can appear in any module, it is recommended -that they be placed only in the _root_ module of a configuration, since this -approach allows users to configure providers just once and re-use them across -all descendent modules. - -Each resource in the configuration must be associated with one provider -configuration, which may either be within the same module as the resource -or be passed from the parent module. Providers can be passed down to descendent -modules in two ways: either _implicitly_ through inheritance, or _explicitly_ -via the `providers` argument within a `module` block. These two options are -discussed in more detail in the following sections. - -In all cases it is recommended to keep explicit provider configurations only in -the root module and pass them (whether implicitly or explicitly) down to -descendent modules. This avoids the provider configurations from being "lost" -when descendent modules 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. - -Provider configurations are used for all operations on associated resources, -including destroying remote objects and refreshing state. Terraform retains, as -part of its state, a reference to the provider configuration that was most -recently used to apply changes to each resource. When a `resource` block is -removed from the configuration, this record in the state is used to locate the -appropriate configuration because the resource's `provider` argument (if any) -is no longer present in the configuration. - -As a consequence, it is required that all resources created for a particular -provider configuration must be destroyed before that provider configuration is -removed, unless the related resources are re-configured to use a different -provider configuration first. - -### Implicit Provider Inheritance - -For convenience in simple configurations, a child module automatically inherits -default (un-aliased) provider configurations from its parent. This means that -explicit `provider` blocks appear only in the root module, and downstream -modules can simply declare resources for that provider and have them -automatically associated with the root provider configurations. - -For example, the root module might contain only a `provider` block and a -`module` block to instantiate a child module: - -```hcl -provider "aws" { - region = "us-west-1" -} - -module "child" { - source = "./child" -} -``` - -The child module can then use any resource from this provider with no further -provider configuration required: - -```hcl -resource "aws_s3_bucket" "example" { - bucket = "provider-inherit-example" -} -``` - -This approach is recommended in the common case where only a single -configuration is needed for each provider across the entire configuration. - -In more complex situations there may be [multiple provider instances](/docs/configuration/providers.html#multiple-provider-instances), -or a child module may need to use different provider settings than -its parent. For such situations, it's necessary to pass providers explicitly -as we will see in the next section. - -## Passing Providers Explicitly - -When child modules each need a different configuration of a particular -provider, or where the child module requires a different provider configuration -than its parent, the `providers` argument within a `module` block can be -used to define explicitly which provider configs are made available to the -child module. For example: - -```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 is similar to -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 -that may result when mixing both implicit and explicit provider passing. - -Additional provider configurations (those with the `alias` argument set) are -_never_ inherited automatically by child modules, and so must always be passed -explicitly using the `providers` map. 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" - } -} -``` - -In the `providers` map, the keys are provider names as expected by the child -module, while the values are the names of corresponding configurations in -the _current_ module. The subdirectory `./tunnel` must then contain -_proxy configuration blocks_ like the following, to declare that it -requires configurations to be passed with these from the `providers` block in -the parent's `module` block: - -```hcl -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 two provider instances to use. - -At this time it is required to write an explicit proxy configuration block -even for default (un-aliased) provider configurations when they will be passed -via an explicit `providers` block: - -```hcl -provider "aws" { -} -``` - -If such a block is not present, the child module will behave as if it has no -configurations of this type at all, which may cause input prompts to supply -any required provider configuration arguments. This limitation will be -addressed in a future version of Terraform. - -## Multiple Instances of a Module - -A particular module source can be instantiated multiple times: - -```hcl -# my_buckets.tf - -module "assets_bucket" { - source = "./publish_bucket" - name = "assets" -} - -module "media_bucket" { - source = "./publish_bucket" - name = "media" -} -``` - -```hcl -# publish_bucket/bucket-and-cloudfront.tf - -variable "name" {} # this is the input parameter of the module - -resource "aws_s3_bucket" "example" { - # ... -} - -resource "aws_iam_user" "deploy_user" { - # ... -} -``` - -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 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. - -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. - -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. - -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. - -## Summarizing Modules in the UI - -By default the [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. - -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 we instead set `-module-depth=0`, the graph will look like this: - -![Terraform Module Graph](docs/module_graph.png) - -## Tainting 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 not possible to taint an entire module. Instead, each resource within -the module must be tainted separately. diff --git a/website/layouts/docs.erb b/website/layouts/docs.erb index 8c1464e3a..809f3bba5 100644 --- a/website/layouts/docs.erb +++ b/website/layouts/docs.erb @@ -360,16 +360,16 @@ > Modules