Terraform is traditionally used for managing virtual infrastructure, but there are organisations out there that use Terraform end-to-end and also want to manage configuration state using the same methods for managing infrastructure. Sure, we can run a provisioner with Terraform, but that wasn't asked for!
Much the same as you can use Terraform to create an AWS EC2 instance, you can manage the configurational state of Junos. In essence, we treat Junos configuration as declarative resources.
So what is JTAF? It's a framework, meaning, it's an opinionated set of tools and steps that allow you to go from YANG models to a custom Junos Terraform provider. With all frameworks, there are some dependencies.
To use JTAF, you'll need machine that can run Go, Python, Git and Terraform. This can be Linux, OSX or Windows. Some easy to consume videos are below.
Introduction: https://youtu.be/eH24eCZc7pE
Installation: https://youtu.be/aTF7_Uscd9Q
Generate: https://youtu.be/UgsFU7UplRE
Execution: https://youtu.be/Lfkc38wzhNg
Interface Configuration: https://youtu.be/iCnnkDodUgQ
BGP Configuration: https://youtu.be/nQVNCNCJZRc
- Junos-Terraform Demo (Build from scratch)
- Junos-Terraform Developer Guide (Build from pre-existing junos config)
- Using the Provider
- Testing with Terraform
- Question & Answers/ Common Problems
Following this demo, developers will learn to be able to manually create custom junos-terrraform providers with the ability to configure any junos device given explicitly defined xpaths and yang files.
In this demo specifically, we will create a simple provider for a vSRX of version 19.4R1 that has the capability to do two things:
- Add a description to an interface
- Add an
inet
address to a sub-interface
Don't worry about commits just yet, we'll discuss that later. This demo is for the purpose of creating terrafom providers from scratch with custom device capabilities related to the use of Juniper's yang file resources.
In this document, if you see $JTAF_PROJECT
you can replace it with the path of the JTAF project on your system or create an environment variable. On the author's system, this happens to be below:
cd Documents/GoDev/src/github.com/Juniper
git clone https://github.com/Juniper/junos-terraform.git
export JTAF_PROJECT=/Users/dgee/Documents/GoDev/src/github.com/Juniper/junos-terraform
# Let's check
echo $JTAF_PROJECT
/Users/dgee/Documents/GoDev/src/github.com/Juniper/junos-terraform
Having this variable set, helps to navigate the system without lots of tedious typing.
Go version tested with: go1.22.0 darwin/amd64
Python version tested with: 3.7
Terraform version: v0.12.26
Other versions beyond these will work, but this is what was tested for the writing of this document.
For our example, our provider will only be able to create an interface description and place an inet address on a sub-interface. We only need a handful of YANG models for this.
For developers wanting to add more capabilities to the provider, they will need to also add the neccesary
yang_files
required for those capabilites outlined by thexpath
inputs which are listed in thexpath_inputs.xml
file created later on
- say you want to add firewalls or policy-option options in addition to interfaces; you will need to add the yang files along with the xpaths associated with the custom capability (See
/Samples/vsrx_tf_module_template
directory for examples of an xpath file,xpath_example.xml
, and the correspondingyang_files
directory)
Let's put the YANG files from Juniper's YANG GitHub repository in to a memorable location. Let's use the home directory /junos-terraform
. Don't worry, once the necesary files are copied over, the /yang
folder can be removed.
/junos-terraform $ [from this directory]
git clone https://github.com/Juniper/yang.git
# this can take some time depending on your internet connection
mkdir yang_files
# Note, the common-types and conf-root YANG models are dependencies
cp yang/19.4/19.4R1/common/[email protected] ./yang_files
cp yang/19.4/19.4R1/junos-es/conf/[email protected] ./yang_files
# Insert the models required here
cp yang/19.4/19.4R1/junos-es/conf/[email protected] ./yang_files
If you wanted to remove the YANG directory, you can do it like this:
cd /junos-terraform
rm -rf yang
Prior to this step, ensure python and go is installed.
This file can be compiled by running
chmod +x generateFiles.sh
from the/junos-terraform
directory followed by./generateFiles.sh
to run the script.
Select "Build a provider from scratch" option: [1] out of the options if following along with the demo
If you've never seen a TOML file before, don't worry! It's just a structured file containing configuration that can be parsed by a program, in this case the two main compiled programs that form JTAF. TOML stands for Tom's Obvious Minimal Langage.
Creates a config file in the home directory. Don't worry about the xPath or fileType keys. They'll be explained shortly.
You can find this file config.toml
in the home directory (/junos-terraform)
yangDir = "$(pwd)/yang_files"
providerDir = "$(pwd)/terraform_providers"
xpathPath = "$(pwd)/xpath_inputs.xml"
providerName = "vsrx"
fileType = "both"
You can also replace the fileType field to text
or xml
. The text files are for us humans.
The next step, depending on the size of YANG model/s, may take some time. Prepare some popcorn!
This step will activate a python vitual enviornment (make sure python is downloaded) and install pyang
so it can be used
to generate the yin
files.
cd $JTAF_PROJECT/cmd/processYang
go build
./processYang -config /path_to_junos-terraform/junos-terraform/config.toml
# OUTPUT - WARNING >> This can take some time. Lack of activity does not mean broken!
___ _____ ___ ______
|_ |_ _/ _ \ | ___|
| | | |/ /_\ \| |_
| | | || _ || _|
/\__/ / | || | | || |
\____/ \_/\_| |_/\_|
0.1.5
-------------------------------------------------------------------------
- Creating Yin files from Yang file directory: /path_to_junos-terraform/junos-terraform/yang_files -
-------------------------------------------------------------------------
Yin file for junos-common-types@2019-01-01 is generated
Yin file for junos-es-conf-interfaces@2019-01-01 is generated
Yin file for junos-es-conf-root@2019-01-01 is generated
--------------------------------------------
- Creating _xpath files from the Yin files -
--------------------------------------------
Creating Xpath file: junos-common-types@2019-01-01_xpath.txt
Creating Xpath file: junos-es-conf-interfaces@2019-01-01_xpath.txt
Creating Xpath file: junos-es-conf-root@2019-01-01_xpath.txt
At this point, venv
is deactivated
and the first script has terminated.
Great, at this point now we have text file and YIN versions of the YANG files. We need those for the next step.
Let's create a file, which provides a list of inputs to the part of JTAF which writes the .go
code automagically.
This input identifies the content of the provider that JTAF will create.
For developers wanting to add more capabilities to the provider, this is where the additional xpath inputs need to be added. Assuming that the neccesary
yang_files
required for those capabilites outlined by thexpath
inputs are added, the inputs can be incorporated into this file following the format below.
- Again, examples of this implementation cam be found in the
/Samples/vsrx_tf_module_template
directory which include examples of an xpath file,xpath_example.xml
, and the correspondingyang_files
directory)
Create a file /junos-terraform/xpath_inputs.xml
and populate it with the content below.
The name must match the name defined in config.toml
<file-list>
<xpath name="/interfaces/interface/description"/>
<xpath name="/interfaces/interface/unit/family/inet/address/name"/>
</file-list>
A simple explanation of the above XPaths:
- The first xpath entry is for the interface description and references a YANG leaf.
- The second xpath entry identifies the inet YANG leaf in the YANG model.
You can view these expressions as a simple way to identify the fields inside the YANG model we're interested in. JTAF generated providers has a requirement of the smallest data set possible for each resources. That means, in a single resource you would place a description, and in another, you will place the inet address. Terraform is essentially a dependency aware declarative resource manager and so, we have to model resources in a way that's compatible with Terraform and Junos.
- IF you want to test the output of the configuration from the
terraform test
files, set the ENV variableMOCK_FILE
to the path of an emptyxml
file you create where the system can display the expected config defined in the .tf files.
- example: create a
jtaf_output.xml
file in the/junos-terraform
directory and runexport MOCK_FILE=/path_to_junos-terraform/jtaf_output.xml
)- Warning:
MOCK_FILE
should beunset
unless wanting to enter Mock mode, otherwise system will look for path setup in the terraform.tf
test files created later on.Mock Mode allows developers to test terraform commands and commits to a local file prior to device communication.
IfMOCK_FILE
is set, terraform commands will output to the file declared by the variable and not the device declared in themain.tf
file.
This file can be compiled by running
chmod +x buildProvider.sh
from the home directory followed by./buildProvider.sh
to run the script.
Below describes what the script does:
First, we need JTAF to create some .go
code from the YANG models and XML data we provided.
cd $JTAF_PROJECT/cmd/processProviders
go build
./processProviders -config /path_to_config/config.toml
# This next step is rapid
___ _____ ___ ______
|_ |_ _/ _ \ | ___|
| | | |/ /_\ \| |_
| | | || _ || _|
/\__/ / | || | | || |
\____/ \_/\_| |_/\_|
0.1.5
------------------------------------------------------------
- Autogenerating Terraform Provider code from _xpath files -
------------------------------------------------------------
Terraform API resource_InterfacesInterfaceDescription created
Terraform API resource_InterfacesInterfaceUnitFamilyInetAddressName created
# --------------------------------------------------------------------------------
Number of Xpaths processed: 2
Number of potential issues: 0
---------------------------------------------
- Copying the rest of the required Go files -
---------------------------------------------
Copied file: config.go to /path_to_junos-terraform/junos-terraform/terraform_providers
Copied file: main.go to /path_to_junos-terraform/junos-terraform/terraform_providers
Copied file: resource_junos_destroy_commit.go to /path_to_junos-terraform/junos-terraform/terraform_providers
Copied file: resource_junos_device_commit.go to /path_to_junos-terraform/junos-terraform/terraform_providers
-------------------
- Creating Go Mod -
-------------------
The output of this step is written to the /junos-terraform/terraform_provider
directory. Let's build the provider!
If there are any found issues during the building of the provider resources, a new file called updated_xpath_inputs.xml
will be created with a trimmed version of the provided xpath inputs file. This file can be used to replace the data in the xpath_inputs.xml
.
cd /terraform_providers
go build
This provider without any Go cross-compilation directives, will work on the system it's been generated with. If you happen to be on an OSX machine, then the provider will work for Terraform on OSX and the same is true for Linux, if you use JTAF on Linux, then natively the generated provider will operate on Linux. However, you can cross-compile the provider so that it will operate on another operating system and even CPU architecture.
# Validate the file kind
file terraform-provider-junos-device
# terraform-provider-junos-device: Mach-O 64-bit executable x86_64
If you want this provider to work with Linux, then you can cross-compile using the GOOS
input. See below.
GOOS=linux go build -o terraform-provider-junos-device
file terraform-provider-junos-device
# terraform-provider-junos-device: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=SWvAslM7UiUlMNJJOG8f/MV8jDWinx0vKkuo7Zmec/-2fk9ZDz88J7folCoc0q/ftWLT5N4tiPWQ8DlXY2J, not stripped
The binary file terraform-provider-junos-vsrx
is actually our fresh new and shiny Terraform Provider. If you got this far, congratulations. You just created a Terraform provider for Junos and you are ready to use it. Jump to Using the Provider
This section is aimed at users who are a little more comfortable with the way junos-terraform works and want to develop, test, configure Juniper devices with a pre-existing configuration.
The only requirement for this section is to upload a configuration in
xml
format to the/junos-terraform/user_config_files
folder.
- When adding this
xml
file to the folder, remove any starting and ending<configuration>
and<cli>
tags.- For reference, see
test.xml
in the/Samples/user_config_files
folder which contains configuration forvqfx
spine for Junos version23.1
- After configuration is uploaded, the rest is taken care of (provider build and test file templates created)
-
IF you want to test the output of the configuration from the
terraform test
files, set the ENV variableMOCK_FILE
to the path of an emptyxml
file you create where the system can display the expected config defined in the .tf files.- example: create a
jtaf_output.xml
file in the/junos-terraform
directory and runexport MOCK_FILE=/path_to_junos-terraform/jtaf_output.xml
)
- example: create a
-
Warning:
MOCK_FILE
should beunset
unless wanting to enter Mock mode, otherwise system will look for path setup.Mock Mode allows developers to test terraform commands and commits to a local file prior to device communication.
IfMOCK_FILE
is set, terraform commands will output to the file declared by the variable and not the device declared in themain.tf
file.
Prior to this step, ensure python and go is installed.
This file can be compiled by running
chmod +x generateFiles.sh
from the home directory followed by./generateFiles.sh
to run the script.
Select "Provide a configuration" option: [2] out of the options if wanting to create a resource provider based on an existing configuration
Below describes what the script does:
- Creates a config file in the home directory. Don't worry about the xPath or fileType keys. They'll be explained shortly.
- You can find this file
config.toml
in the home directory/junos-terraform
- This part of the script calls for the cloning of the Juniper
yang
directory in order to copy necessary yang files for the givendevice
andversion
. This will only happen if the folderyang_files
does not exist already.
- This part of the script enables the call to create YIN and Xpath files. For more details, look at the demo for this section.
- This part of the script uses a go file called
createXpathInputs.go
to parse the configuration loaded by the user and creates anxpath_inputs.xml
file. This file contains a skeleton of all the configured xpaths contained in the pre-loaded configuration. These xpaths will be used to generate the provider resources.
- Creates updated xpath input file (if needed)
- If there are any found issues during the building of the provider resources, a new file called
updated_xpath_inputs.xml
will be created with a trimmed version of the provided xpath inputs file. This file can be used to replace the data in thexpath_inputs.xml
.
- If there are any found issues during the building of the provider resources, a new file called
- We need JTAF to create some
.go
code from the YANG models and XML data we provided which is written to the/terraform_provider
directory.
- Similar to the demo, the script run the creation of the binary file
terraform-provider-junos-[device-name]
which is actually our fresh new and shiny Terraform Provider. If you got this far, congratulations. You just created a Terraform provider for Junos.
- For the next section, refer to the
/TFtemaplates
directory created by the script providing a basic.tf
template for themain
andtest
files
To test the provider we need to do two more things, one, put the provider where Terraform can find it and two, create a simple set of .tf
files as inputs to Terraform!
Terraform Search Locations
More recent versions of Terraform will look for providers (plugins) in the Hashicorp hosted registry and in a number of local locations.
Firstly, there is a default location, which is located under ~/.terraform.d/
. You need to create a hierarchy for each provider, it's version and the CPU architecture type the provider was compiled for. Here is what the author's looks like:
tree /Users/dgee/.terraform.d
├── checkpoint_cache
├── checkpoint_signature
└── plugins
└── juniper
└── providers
└── junos-vsrx
└── 19.41.101
├── darwin_amd64
│ └── terraform-provider-junos-vsrx
└── linux_amd64
└── terraform-provider-junos-vsrx
Notice that the R is missing in the version 19.41.101
. This is an official Juniper method of naming providers. It basically says: 19.4R1.01 of Junos, with version 01 of the provider. Due to semantic versioning, you'll notice that the leading zero has been stripped, leaving us with the last two digits for the provider number and any other digits in front being the Junos version patch number.
If you're building providers locally, it's worth considering how to version control them. Each provider generated can have a different set of capabilities, even for the same software release, so it's important that you keep a track through a simple version control system. MD5 hashing of the binary is also recommended, so as a worst case, you can identify the binaries by their computed hash.
You can use the method above, enabling Terraform to find the provider locally. Here's how:
mkdir -p ~/.terraform.d/plugins/juniper/providers/junos-vsrx/19.41.101/darwin_amd64
It's probably a good idea to replace the juniper
part with your own organisation's name to prevent any confusion.
The second way is to create a .terraformrc
file in your home directory, where you tell Terraform where to look for the same file structure. That can be in a project directory if you so wish. Here is an example of that file.
provider_installation {
filesystem_mirror {
path = "/path_to_junos-terraform/junos-terraform/plugins"
include = ["*/*/*"]
}
}
Make sure that the same file tree exists from plugins
as before.
The other option of course, is to publish to your provider/s to the Hashicorp registry and not have them stored locally.
Ok, now we've got the Terraform provider in place, we can actually test Terraform! For this section, you will replace the provider
section with the information for the device which is being configured.
If testing the provider from scatch, skip this message. If building a provider from a pre-loaded configuration, the following steps have been more or less done for you. Look for the
junos-terraform/TFtemplates
which will have prcompiled test files to use for testing. Thetestbed
and required files and folders have also already been made for you. The only requirment is to manually fill in the resource information.
You are free to choose a directory in which to test this. I'm going to stick with the home /junos-terraform
directory.
cd /junos-terraform
mkdir testbed && cd testbed
We need to create a number of files.
mkdir vsrx_1
touch main.tf
touch vsrx_1/main.tf
Content of main.tf
terraform {
required_providers {
junos-vsrx = {
source = "juniper/providers/junos-vsrx"
version = "19.41.101"
}
}
}
provider "junos-vsrx" {
host = "localhost"
port = 8300
username = "root"
password = "juniper123"
sshkey = ""
}
module "vsrx_1" {
source = "./vsrx_1"
providers = {junos-vsrx = junos-vsrx}
depends_on = [junos-vsrx_destroycommit.commit-main]
}
resource "junos-vsrx_commit" "commit-main" {
resource_name = "commit"
depends_on = [module.vsrx_1]
}
resource "junos-vsrx_destroycommit" "commit-main" {
resource_name = "destroycommit"
}
Content of vsrx_1/main.tf
terraform {
required_providers {
junos-vsrx = {
source = "juniper/providers/junos-vsrx"
version = "19.41.101"
}
}
}
resource "junos-vsrx_InterfacesInterfaceDescription" "vsrx_1" {
resource_name = "vsrx_1"
name = "ge-0/0/0"
description = "Test description"
}
resource "junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName" "vsrx_2" {
resource_name = "vsrx_2"
name = "ge-0/0/0"
name__1 = "0"
name__2 = "10.0.0.1/24"
}
Let's Initialise Terraform
We're getting so close! Let's initialize Terraform. From the testbed
folder, run:
testbed $ terraform init
Initializing modules...
Initializing the backend...
Initializing provider plugins...
- Finding juniper/providers/junos-vsrx versions matching "19.41.101"...
- Installing juniper/providers/junos-vsrx v19.41.101...
- Installed juniper/providers/junos-vsrx v19.41.101 (unauthenticated)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
The next step is to actually run the plan and apply steps.
testbed $ terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# junos-vsrx_commit.commit-main will be created
+ resource "junos-vsrx_commit" "commit-main" {
+ id = (known after apply)
+ resource_name = "commit"
}
# junos-vsrx_destroycommit.commit-main will be created
+ resource "junos-vsrx_destroycommit" "commit-main" {
+ id = (known after apply)
+ resource_name = "destroycommit"
}
# module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1 will be created
+ resource "junos-vsrx_InterfacesInterfaceDescription" "vsrx_1" {
+ description = "Test description"
+ id = (known after apply)
+ name = "ge-0/0/0"
+ resource_name = "vsrx_1"
}
# module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2 will be created
+ resource "junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName" "vsrx_2" {
+ id = (known after apply)
+ name = "ge-0/0/0"
+ name__1 = "0"
+ name__2 = "10.0.0.1/24"
+ resource_name = "vsrx_2"
}
Plan: 4 to add, 0 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
Our plan is simple! Because we do not have any local Terraform state, the plan has been generated quickly and it's straight forward to read. The commit
and destroycommit
resources will be covered after this step. We can also tell Terraform to auto-approve the apply without any further manual input.
testbed $ terraform apply -auto-approve
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# junos-vsrx_commit.commit-main will be created
+ resource "junos-vsrx_commit" "commit-main" {
+ id = (known after apply)
+ resource_name = "commit"
}
# junos-vsrx_destroycommit.commit-main will be created
+ resource "junos-vsrx_destroycommit" "commit-main" {
+ id = (known after apply)
+ resource_name = "destroycommit"
}
# module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1 will be created
+ resource "junos-vsrx_InterfacesInterfaceDescription" "vsrx_1" {
+ description = "Test description"
+ id = (known after apply)
+ name = "ge-0/0/0"
+ resource_name = "vsrx_1"
}
# module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2 will be created
+ resource "junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName" "vsrx_2" {
+ id = (known after apply)
+ name = "ge-0/0/0"
+ name__1 = "0"
+ name__2 = "10.0.0.1/24"
+ resource_name = "vsrx_2"
}
Plan: 4 to add, 0 to change, 0 to destroy.
junos-vsrx_destroycommit.commit-main: Creating...
junos-vsrx_destroycommit.commit-main: Creation complete after 0s [id=localhost_destroycommit]
module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1: Creating...
module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2: Creating...
module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2: Creation complete after 5s [id=localhost_vsrx_2]
module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1: Still creating... [10s elapsed]
module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1: Creation complete after 10s [id=localhost_vsrx_1]
junos-vsrx_commit.commit-main: Creating...
junos-vsrx_commit.commit-main: Creation complete after 6s [id=localhost_commit]
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Let's just check the input on the vSRX instance to make sure we have the correct configs!
Terraform deals with configuration as a set of remote resources, which are stored in Junos configuration groups
. These groups are then inherited by Junos at commit time. Here is the inherited configuration.
# show interfaces ge-0/0/0 | display inheritance no-comments
description "Test description";
unit 0 {
family inet {
address 10.0.0.1/24;
}
}
Here are the groups:
# show groups
vsrx_2 {
interfaces {
ge-0/0/0 {
unit 0 {
family inet {
address 10.0.0.1/24;
}
}
}
}
}
vsrx_1 {
interfaces {
ge-0/0/0 {
description "Test description";
}
}
}
Junos and Terraform are not natural buddies. Their life-cycles are somewhat orthogonal in nature.
We handle the commit based transactional nature of Junos by using a simple commit pattern, in which Terraform creates phony commit resources. The resources are only tracked locally as commits are nothing more than Junos remote procedure calls.
Thankfully, Terraform has a way of creating logical groupings, called modules
. Terraform modules represent re-usable chunks of logic, in which we can give a name. Because we can name these groups, it means we can also place dependencies up on them!
You might have noticed in the top level main.tf
file, there was some depends_on
Terraform HCL keys.
It's by using these concrete dependencies, we are able to trigger the resources commit
and destroycommit
to be created.
The dependency order is thus:
- The
commit
resource depends on the module - The module contains the actual desired state (which may have further ordered structure)
- The module depends on the
destroycommit
resource
This ordering means the destroycommit
is created first. No action is taken when this resource is created other than local state is stored on the system. The contents of the module are executed next, which consists of NETCONF sessions being made against the target system and stored in configuration groups, which are applied. Lastly, the commit resource is created, which actually runs a commit on Junos via a NETCONF RPC.
Making Changes
When you make either a change on Junos, or a change to the local resorce .tf
files, then run a Terraform plan, Terraform doesn't understand a commit must be run. Therefore you must a terraform taint $terraform_address.commit
command against the commit resource, which tells Terraform to re-run the commit after making other changes in the module.
Let's try it out in context of this demonstration. In the vsrx_1/main.tf
, change the inet address to .2
instead of .1
.
Run the terraform taint
command and re-run the plan and apply sequences.
terraform taint junos-vsrx_commit.commit-main
junos-vsrx_destroycommit.commit-main: Refreshing state... [id=localhost_destroycommit]
module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2: Refreshing state... [id=localhost_vsrx_2]
module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1: Refreshing state... [id=localhost_vsrx_1]
junos-vsrx_commit.commit-main: Refreshing state... [id=localhost_commit]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
-/+ destroy and then create replacement
Terraform will perform the following actions:
# junos-vsrx_commit.commit-main is tainted, so must be replaced
-/+ resource "junos-vsrx_commit" "commit-main" {
~ id = "localhost_commit" -> (known after apply)
# (1 unchanged attribute hidden)
}
# module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2 will be updated in-place
~ resource "junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName" "vsrx_2" {
id = "localhost_vsrx_2"
name = "ge-0/0/0"
~ name__2 = "10.0.0.1/24" -> "10.0.0.2/24"
# (2 unchanged attributes hidden)
}
Plan: 1 to add, 1 to change, 1 to destroy.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
# Now run the apply
terraform apply -auto-approve
junos-vsrx_destroycommit.commit-main: Refreshing state... [id=localhost_destroycommit]
module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1: Refreshing state... [id=localhost_vsrx_1]
module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2: Refreshing state... [id=localhost_vsrx_2]
junos-vsrx_commit.commit-main: Refreshing state... [id=localhost_commit]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
-/+ destroy and then create replacement
Terraform will perform the following actions:
# junos-vsrx_commit.commit-main is tainted, so must be replaced
-/+ resource "junos-vsrx_commit" "commit-main" {
~ id = "localhost_commit" -> (known after apply)
# (1 unchanged attribute hidden)
}
# module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2 will be updated in-place
~ resource "junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName" "vsrx_2" {
id = "localhost_vsrx_2"
name = "ge-0/0/0"
~ name__2 = "10.0.0.1/24" -> "10.0.0.2/24"
# (2 unchanged attributes hidden)
}
Plan: 1 to add, 1 to change, 1 to destroy.
junos-vsrx_commit.commit-main: Destroying... [id=localhost_commit]
junos-vsrx_commit.commit-main: Destruction complete after 0s
module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2: Modifying... [id=localhost_vsrx_2]
module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2: Modifications complete after 5s [id=localhost_vsrx_2]
junos-vsrx_commit.commit-main: Creating...
junos-vsrx_commit.commit-main: Creation complete after 6s [id=localhost_commit]
Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
# Re-run the plan to confirm!
terraform plan
junos-vsrx_destroycommit.commit-main: Refreshing state... [id=localhost_destroycommit]
module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2: Refreshing state... [id=localhost_vsrx_2]
module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1: Refreshing state... [id=localhost_vsrx_1]
junos-vsrx_commit.commit-main: Refreshing state... [id=localhost_commit]
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
You can also check the Junos config to make sure the reflected change has happened and that the commit has been executed.
show interfaces ge-0/0/0 | display inheritance no-comments
description "Test description";
unit 0 {
family inet {
address 10.0.0.2/24;
}
}
There we have it.
Now for the fun part! Let's clean up and destroy the Terraform state.
terraform destroy -auto-approve
junos-vsrx_destroycommit.commit-main: Refreshing state... [id=localhost_destroycommit]
module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1: Refreshing state... [id=localhost_vsrx_1]
module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2: Refreshing state... [id=localhost_vsrx_2]
junos-vsrx_commit.commit-main: Refreshing state... [id=localhost_commit]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# junos-vsrx_commit.commit-main will be destroyed
- resource "junos-vsrx_commit" "commit-main" {
- id = "localhost_commit" -> null
- resource_name = "commit" -> null
}
# junos-vsrx_destroycommit.commit-main will be destroyed
- resource "junos-vsrx_destroycommit" "commit-main" {
- id = "localhost_destroycommit" -> null
- resource_name = "destroycommit" -> null
}
# module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1 will be destroyed
- resource "junos-vsrx_InterfacesInterfaceDescription" "vsrx_1" {
- description = "Test description" -> null
- id = "localhost_vsrx_1" -> null
- name = "ge-0/0/0" -> null
- resource_name = "vsrx_1" -> null
}
# module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2 will be destroyed
- resource "junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName" "vsrx_2" {
- id = "localhost_vsrx_2" -> null
- name = "ge-0/0/0" -> null
- name__1 = "0" -> null
- name__2 = "10.0.0.2/24" -> null
- resource_name = "vsrx_2" -> null
}
Plan: 0 to add, 0 to change, 4 to destroy.
junos-vsrx_commit.commit-main: Destroying... [id=localhost_commit]
junos-vsrx_commit.commit-main: Destruction complete after 0s
module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2: Destroying... [id=localhost_vsrx_2]
module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1: Destroying... [id=localhost_vsrx_1]
module.vsrx_1.junos-vsrx_InterfacesInterfaceDescription.vsrx_1: Destruction complete after 3s
module.vsrx_1.junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName.vsrx_2: Destruction complete after 5s
junos-vsrx_destroycommit.commit-main: Destroying... [id=localhost_destroycommit]
junos-vsrx_destroycommit.commit-main: Destruction complete after 4s
Destroy complete! Resources: 4 destroyed.
# You can check the vSRX manually to make sure the config has gone
show interfaces ge-0/0/0 | display inheritance no-comments
There is some inverse logic here. The destroycommit
is 'created' on the delete cycle within the provider, whereas the commit
is 'created' on the create cycle. Terraform providers do nothing more than CRUD (create/read/update/delete) on data structures, which in turn have methods on them. The Terraform apply cycle runs create
and update
. Terraform destroy in turns runs the delete
method.
You can read more on this commit 'bookend' pattern here.
1. When I downloaded, terraform, the commands are not recognized by my device
Ensure the that the terraform download is found in the /usr/local/bin
directory
- Go to where terraform download is located
cd /Downloads
- Use the sudo
command
to move it to the correct location.
sudo mv terraform /usr/local/bin/
2. Where do I find the names of the resources I have created?
Check in the provider.go
file that is dynamically generated. You will find a data structure with this signature: ResourcesMap: map[string]*schema.Resource
. Your resources are named in a map. Here is an example:
// Output of a provider.go example
func Provider() *schema.Provider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"host": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"port": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"username": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"password": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"sshkey": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
// These are your resources
ResourcesMap: map[string]*schema.Resource{
"junos-vsrx_InterfacesInterfaceDescription": junosInterfacesInterfaceDescription(),
"junos-vsrx_InterfacesInterfaceUnitFamilyInetAddressName": junosInterfacesInterfaceUnitFamilyInetAddressName(),
"junos-vsrx_commit": junosCommit(),
"junos-vsrx_destroycommit": junosDestroyCommit(),
},
ConfigureFunc: providerConfigure,
}
}
3. Running .generte.sh
script sometimes fails due to go runtime error or pyang error.
- Ensure that pyang and go are installed. If so, re-run the script and it should continue.
- If errors persist, try examining to ensure the right version and yang file types are being used. Deleting the autogenerated folders may be required.
4. Running terraform init
is giving me errors regarding the location of the provider.
- Some systems have issues recognizing the
.terraformrc
file so if using this method, delete this file and follow below. - Ensure that you place the provider in the
~/.terraform.d/plugins/juniper/providers/junos-vsrx/19.41.101/darwin_amd64/
folder.- If using
darwin_arm64
, make sure to rename the folder to match the device's core.
- If using
- Once this is double-checked, check the
main.tf
to ensure that the terraformrequired_providers
matches the naming of the path and try again.
5. Running terraform apply
is giving me errors related to Plugin not Responding. How do I fix this?
This only applies when NOT using the
$MOCK_FILE
testing env variable
- If terraform cannot connect to a Juniper Device during the
apply
stage, the messageError: Plugin did not respond
will occur.- To fix this issue, ensure that the
provider "junos-deviceName"
section in themain.tf
file is correctly filled out and matches a running Junos device which can recieves data. If the host and port does not connect to a running device, theterraform apply
will not work.
- To fix this issue, ensure that the
- If using the
$MOCK_FILE
env variable --> the information in theprovider "junos-deviceName"
section in themain.tf
is not relevant to the output in the log file defined by the varible.
This covers the use of JTAF with a full example. Please post any issues or bug reports using GitHub issues at the top of this page. Enjoy!
Juniper Networks is actively contributing to and maintaining this repo.
Contributors: