- Okay, now that we understand
a little bit about using
the configuration language,
it's important to understand
how we can encapsulate
and reuse our code in Terraform.
Just like in any other software project,
it's important that your code
doesn't repeat itself,
so it's DRY, don't repeat yourself,
and that it's modular
so that the pieces can be reused.
So, in Terraform what is a module?
All Terraform code is in a module.
The parts, the configurations
that we looked at before
were a single module called the root module.
A root module is always required
to plan or apply Terraform.
But in addition to the root module,
there can be other modules.
Okay.
And what comprises a module
is simply a directory
that contains Terraform code.
So remember when I talked about evaluating
the Terraform configuration
and how it doesn't recurse,
well, if you have subdirectories in your Terraform,
then those are considered modules
and you have to specifically tell Terraform
to use those.
Now, since it's a directory structure
it can be nested,
so you can have modules within modules,
and sometimes that's a good idea
and sometimes it's not.
So, when should you use a module
versus just putting config
into the root module?
Well, the first thing to look for
is if somebody else has already written it.
So, just like in other software development disciplines,
there are standard libraries in Terraform,
and HashiCorp maintains a public registry
of Terraform modules
that other people have written.
Some of these are very high quality
written by HashiCorp,
aome of these are lower quality
written by individual contributors
in the community.
So it's up to you to sort of determine
whether or not the modules in the registry
meet your requirements for that.
They did have a registry
at terraform.io/registry, I believe.
Okay, so the Terraform registry
is just a searchable interface
and you can look for modules
relating to anything that you're interested in,
things like VPC.
Now, as I mentioned,
there's a vast difference in quality
with these modules, so it's a good idea,
if you can, to select the verified modules,
and these will essentially filter
the module list by ones
that have been vetted by HashiCorp,
and in most cases actually developed by HashiCorp.
So VPC, the AWS VPC module
is a very powerful and popular module.
I use this all the time.
It's extremely complex to set up
a VPC with all the options in AWS,
and so this module does a good job
of encapsulating that.
Okay, so if somebody has already written
a module for you, great, use that.
Otherwise, when should you use a module?
Well, some good rules of thumb
are any code that is frequently used.
So things like setting up AWS accounts,
areas where you need to enforce
organizational standards,
any time you find yourself cutting
and pasting big blocks of code,
you're more than likely
to probably encapsulate that
into a module that can be reused.
Another common case is
to add a level of abstraction.
So one thing I'll mention about Terraform
that beginners often miss
is that Terraform is a very low level tool.
So Terraform maps right on to the provider API.
So, you're interacting almost directly
with the resources exposed by the AWS API
or the Google API, and sometimes
that's a level of granularity
that you just don't need or want.
So a good example is things
that are separate resources in Terraform
but are logically one thing.
Azure does this where the virtual machine,
the NIC and the disk are all separate resources.
And there is not really a case
where you don't want to bring
all those things up at the same time,
and so using a module there
to encapsulate those sorts of things,
that just all go together,
is a good way to do it.
Kubernetes is another place
where this is really common.
So you create an EKS cluster
but you also want to create worker nodes.
That's in our minds,
it seems like one thing,
but from an API level it's not,
so that's a common place to add abstraction.
DNS providers is another one.
So you may be using Route 53
and another DNS provider,
and you can make the interface of those things
look the same to your internal code
by using a module
and implementing the same interface.
Occasionally, the providers are,
the Terraform providers are not up-to-date
with the latest API and there may be a resource
that is not exposed to the Terraform provider.
And so in that case you can create a module to sort of
implement that functionality yourself.
An example that comes to mind
is in the API Gateway.
It does not support WebSockets APIs
in the Terraform provider.
They're fully supported
by the Gateway itself,
but that support has not yet made it
to the Terraform provider.
And so people have created modules
that sort of wrap that functionality.
Now that we know what a module is
and when we should use one,
how do we create a module?
Well, it's a really simple process.
Simply create a directory
and put some Terraform in it.
That's really all there is to it.
That being said, there are a bunch of conventions
to go around creating modules,
one of which is the naming convention.
Now, if you're planning to publish your modules,
either to a private Terraform registry
or to the public Terraform registry,
you have to follow this naming convention.
You have to use the literal string terraform,
dash, and then the name of the provider,
so that would be something like AWS,
Google, Kubernetes,
something like that.
And then another dash,
and then the name, and the name can be
whatever makes sense to you,
and that can also contain hyphens.
But the first few fields
will actually be parsed out
by the registry, okay?
So you have to keep that in mind
when you're creating your modules.
If you don't plan on publishing these to registry
then you can name them however you like.
Most people leave off the Terraform part
and sometimes they leave off the provider part.
A module layout.
So again, your modules can be laid out
anyway that you like,
that's valid Terraform,
but this is the convention
and this is also required
for the module registries.
So, by convention,
the entry point is called main.tf.
Also by convention, the input variables
are stored in variables.tf,
and the outputs are stores in outputs.tf.
If you have any submodules
those should go into a directory
named modules,
and then within there there're subdirectories
for each submodule.
And then those follow the same pattern recursively.
By convention, there is also an examples directory
and this is used, also this is broken out
by the module registry.
But I highly encourage you
to create the examples directory,
and it's very, very useful to have that
during your development process
because it gives you a little piece of,
it gives you a root module that you can use
to call your own Terraform.
Additionally, a README file is required
if you want to publish this to a registry.
It's highly recommended anyway.
By convention, submodules don't get README files,
and that's actually how Terraform tells
whether something is intended to be
a private module.
So include README and LICENSE methods
going into the public repository.
A LICENSE file is required.
You can get the language for those
from GitHub or online or anywhere else
to paste in there.
So modules follow the exact same format
as the root module.
There's a few differences
that you need to be aware of.
Like the root module, variables are used
to pass parameters into a module,
and output is used to return
values from a module.
But unlike the root module,
variables that don't have a default defined
are required.
So it won't prompt you.
In the root module, if you don't define a variable
then Terraform, we saw Terraform
prompting you for the value.
It's just simply an error
if you are calling a module
and one of its required input values
is specified.
And then the outputs are slightly different as well.
So the outputs don't get printed out at the end,
the way they do in the root module.
The outputs are fed back
to the root module
as the values of the module,
and we'll see this.
It's a little bit confusing to explain,
but we'll see this in action
when we actually go through some module code.
So this is the HCL syntax
for using a module.
So it's a little bit different
than a resource specification.
The syntax is simply the word module
and then the label,
and then within the specification
a required parameter is source,
and the source can be a couple
of different syntaxes.
The first one is just a filesystem path.
So Terraform will attempt to resolve that
into a filesystem path,
and you can use relative directories
like dot-dot for one directory above,
that's really common in using your examples.
A Terraform registry URL,
which is not a full URL,
it's username and then the name hashed
including the provider, and I'll show you
in the registry what that actually looks like.
So if you look here you can see
that it gives you a little example
including this into your code,
and this is what the actual string looks like
to be included in your code.
You can also include a version
if you're using a registry.
And then lastly, you can include a link
to your SCM Repo.
And Terraform supports
a number of different SCMs directly,
GitHub being one of them,
as opposed to just RawGit.
You could also use the RawGit provider
with GitHub, but the GitHub gives you
a little bit of a shortcut.
You don't have to give it the full SSH path.
But if you're using some other git provider
you can still use that.
It also supports Bitbucket, Subversion,
and possibly some others.
Also GitLinks and I think
other SCMs as well.
You can also specify a reference
or a branch number or a tag as well.
So you can do fully reproducible builds
using that method.
This is just a little example
of what a module would look like in action,
and we're gonna go through this
in more detail when we get there,
but this top part defines the module itself.
So this would be in your module file.
Okay?
So this is just a really quick trivial module,
showing you how to accept parameters
and how to output values,
and then it shows you in the main.tf
down below how to call that.
Okay?
And we'll go and see some concrete examples here
in the next section.