Skip to content

Commit

Permalink
feat(sample): Add samples how to use test skeleton with VM-Series mod…
Browse files Browse the repository at this point in the history
…ules (#2)
  • Loading branch information
sebastianczech authored Jul 27, 2023
1 parent 1681317 commit b32df3d
Show file tree
Hide file tree
Showing 34 changed files with 1,837 additions and 2 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/pr_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ jobs:
- name: Download Go dependencies
run: go get ./... && go mod tidy

- name: Test code
run: go test -v ./...
### Uncomment it, when tests are prepared
# - name: Test code
# run: go test -v ./...
51 changes: 51 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# This is a minimal list, OK to add things per repo
# mac specific
.DS_Store
.ansible
.azure/
.bash_history
# don't check storage creds into GH
.boto
.cache
*.code-workspace
# may contain gcloud files
.config
.gitconfig
.local
# .netrc contains secrets for service tokens
.netrc
*.plan
.sentinel
# .ssh dir may contain private keys
.ssh
.terrascan
*.pem
.pem
# Terraform dot files
.terraform
.terraform.lock.hcl
.terraformrc
**/.terraform/*
.terraform.d
*.tfstate
*.tfstate.*
.terragrunt-cache
.vscode
.idea
**/test_report.html
# Palo auth codes
authcodes
# Crash log files
crash.log
credentials.json
# Palo specific
init-cfg.txt
temp
terraform.rc
tmp
# Auto variables
terraform.tfvars
terraform.tfvars.json
*.auto.tfvars
*.auto.tfvars.json
modules/asg/lambda_payload.zip
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,33 @@ Test skeleton with Terratest in Go used to execute integration and e2e tests for
- [terraform-azurerm-vmseries-modules](https://github.com/PaloAltoNetworks/terraform-azurerm-vmseries-modules)
- [terraform-google-vmseries-modules](https://github.com/PaloAltoNetworks/terraform-google-vmseries-modules)

## Usage

Terratest can be used to check whole examples and single modules. Below there are few samples of usage:
- [check VM-Series example](samples/vmseries_example_plan_and_deploy/)
- [verify module by checking errors from Terraform plan](samples/vmseries_module_check_terraform_plan_errors/)
- [verify module by checking output and accessing URL after deploying VM-Series](samples/vmseries_module_check_terraform_output_and_vmseries_url/)
- [verify module by deplyoing additional changes after initial deployment](samples/vmseries_module_check_additional_changes_after_deployment/)

In order to execute tests, go to folder with sample and use command:

```
go test -v -timeout 30m -count=1 ./...
```

For tests with example there is possibility to run only plan (using above command) or to deploy whole example using command:

```
DO_APPLY=true go test -v -timeout 30m -count=1 ./...
```

Some of the tests are quick (e.g. checking plan erros), so then there is no need to change default timeout (10 minutes) and below command can be used:

```
go test -v -count=1 ./...
```


## License

This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details
95 changes: 95 additions & 0 deletions samples/vmseries_example_plan_and_deploy/example.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
### GENERAL
region = "eu-central-1" # TODO: update here
name_prefix = "example-" # TODO: update here

global_tags = {
ManagedBy = "terraform"
Application = "Palo Alto Networks VM-Series NGFW"
Owner = "PS Team"
}

ssh_key_name = "example-ssh-key" # TODO: update here

### VPC
vpcs = {
# Do not use `-` in key for VPC as this character is used in concatation of VPC and subnet for module `subnet_set` in `main.tf`
security_vpc = {
name = "security-vpc"
cidr = "10.100.0.0/16"
nacls = {}
security_groups = {
vmseries_mgmt = {
name = "vmseries_mgmt"
rules = {
all_outbound = {
description = "Permit All traffic outbound"
type = "egress", from_port = "0", to_port = "0", protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
https = {
description = "Permit HTTPS"
type = "ingress", from_port = "443", to_port = "443", protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # TODO: update here (replace 0.0.0.0/0 by your IP range)
}
ssh = {
description = "Permit SSH"
type = "ingress", from_port = "22", to_port = "22", protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # TODO: update here (replace 0.0.0.0/0 by your IP range)
}
}
}
}
subnets = {
# Do not modify value of `set=`, it is an internal identifier referenced by main.tf
# Value of `nacl` must match key of objects stored in `nacls`
"10.100.0.0/24" = { az = "eu-central-1a", set = "mgmt", nacl = null }
}
routes = {
# Value of `vpc_subnet` is built from key of VPCs concatenate with `-` and key of subnet in format: `VPCKEY-SUBNETKEY`
# Value of `next_hop_key` must match keys use to create TGW attachment, IGW, GWLB endpoint or other resources
# Value of `next_hop_type` is internet_gateway, nat_gateway, transit_gateway_attachment or gwlbe_endpoint
mgmt_default = {
vpc_subnet = "security_vpc-mgmt"
to_cidr = "0.0.0.0/0"
next_hop_key = "security_vpc"
next_hop_type = "internet_gateway"
}
}
}
}

### VM-SERIES
vmseries = {
vmseries = {
instances = {
"01" = { az = "eu-central-1a" }
}

# Value of `panorama-server`, `auth-key`, `dgname`, `tplname` can be taken from plugin `sw_fw_license`
bootstrap_options = {
mgmt-interface-swap = "disable"
plugin-op-commands = "aws-gwlb-inspect:enable,aws-gwlb-overlay-routing:enable" # TODO: update here
dhcp-send-hostname = "no" # TODO: update here
dhcp-send-client-id = "no" # TODO: update here
dhcp-accept-server-hostname = "no" # TODO: update here
dhcp-accept-server-domain = "no" # TODO: update here
}

panos_version = "10.2.3" # TODO: update here
ebs_kms_id = "alias/aws/ebs" # TODO: update here

# Value of `vpc` must match key of objects stored in `vpcs`
vpc = "security_vpc"

interfaces = {
mgmt = {
device_index = 0
private_ip = "10.100.0.4"
security_group = "vmseries_mgmt"
vpc_subnet = "security_vpc-mgmt"
create_public_ip = true
source_dest_check = true
}
}
}
}
156 changes: 156 additions & 0 deletions samples/vmseries_example_plan_and_deploy/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
### VPCS ###

module "vpc" {
source = "github.com/PaloAltoNetworks/terraform-aws-vmseries-modules//modules/vpc"

for_each = var.vpcs

name = "${var.name_prefix}${each.value.name}"
cidr_block = each.value.cidr
nacls = each.value.nacls
security_groups = each.value.security_groups
create_internet_gateway = true
enable_dns_hostnames = true
enable_dns_support = true
instance_tenancy = "default"
}

### SUBNETS ###

module "subnet_sets" {
for_each = toset(flatten([for _, v in { for vk, vv in var.vpcs : vk => distinct([for sk, sv in vv.subnets : "${vk}-${sv.set}"]) } : v]))
source = "github.com/PaloAltoNetworks/terraform-aws-vmseries-modules//modules/subnet_set"

name = split("-", each.key)[1]
vpc_id = module.vpc[split("-", each.key)[0]].id
has_secondary_cidrs = module.vpc[split("-", each.key)[0]].has_secondary_cidrs
nacl_associations = {
for i in flatten([
for vk, vv in var.vpcs : [
for sk, sv in vv.subnets :
{
az : sv.az,
nacl_id : lookup(module.vpc[split("-", each.key)[0]].nacl_ids, sv.nacl, null)
} if sv.nacl != null && each.key == "${vk}-${sv.set}"
]]) : i.az => i.nacl_id
}
cidrs = {
for i in flatten([
for vk, vv in var.vpcs : [
for sk, sv in vv.subnets :
{
cidr : sk,
subnet : sv
} if each.key == "${vk}-${sv.set}"
]]) : i.cidr => i.subnet
}
}

### ROUTES ###

locals {
vpc_routes = flatten(concat([
for vk, vv in var.vpcs : [
for rk, rv in vv.routes : {
subnet_key = rv.vpc_subnet
to_cidr = rv.to_cidr
next_hop_set = (
rv.next_hop_type == "internet_gateway" ? module.vpc[rv.next_hop_key].igw_as_next_hop_set : null
)
}
]
]))
}

module "vpc_routes" {
for_each = { for route in local.vpc_routes : "${route.subnet_key}_${route.to_cidr}" => route }
source = "github.com/PaloAltoNetworks/terraform-aws-vmseries-modules//modules/vpc_route"

route_table_ids = module.subnet_sets[each.value.subnet_key].unique_route_table_ids
to_cidr = each.value.to_cidr
next_hop_set = each.value.next_hop_set
}

### IAM ROLES AND POLICIES ###

resource "aws_iam_role" "vm_series_ec2_iam_role" {
name = "${var.name_prefix}vmseries"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Principal": {"Service": "ec2.amazonaws.com"}
}
]
}
EOF
}

resource "aws_iam_role_policy" "vm_series_ec2_iam_policy" {
role = aws_iam_role.vm_series_ec2_iam_role.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"cloudwatch:PutMetricAlarm",
"cloudwatch:GetMetricData",
"cloudwatch:PutMetricData",
"cloudwatch:ListMetrics",
"cloudwatch:DescribeAlarms",
"logs:CreateLogGroup"
],
"Resource": [
"*"
],
"Effect": "Allow"
}
]
}
EOF
}

resource "aws_iam_instance_profile" "vm_series_iam_instance_profile" {

name = "${var.name_prefix}vmseries_instance_profile"
role = aws_iam_role.vm_series_ec2_iam_role.name
}

### VM-Series INSTANCES

locals {
vmseries_instances = flatten([for kv, vv in var.vmseries : [for ki, vi in vv.instances : { group = kv, instance = ki, az = vi.az, common = vv }]])

bootstrap_options = { for i, j in var.vmseries : i => [
for k, v in j.bootstrap_options : "${k}=${v}"
] }
}

module "vmseries" {
for_each = { for vmseries in local.vmseries_instances : "${vmseries.group}-${vmseries.instance}" => vmseries }
source = "github.com/PaloAltoNetworks/terraform-aws-vmseries-modules//modules/vmseries"

name = "${var.name_prefix}${each.key}"
vmseries_version = each.value.common.panos_version

interfaces = {
for k, v in each.value.common.interfaces : k => {
device_index = v.device_index
private_ips = [v.private_ip]
security_group_ids = try([module.vpc[each.value.common.vpc].security_group_ids[v.security_group]], [])
source_dest_check = try(v.source_dest_check, false)
subnet_id = module.subnet_sets[v.vpc_subnet].subnets[each.value.az].id
create_public_ip = try(v.create_public_ip, false)
}
}

bootstrap_options = join(";", compact(concat(local.bootstrap_options[each.value.group], ["hostname=${var.name_prefix}${each.key}"])))

iam_instance_profile = aws_iam_instance_profile.vm_series_iam_instance_profile.name
ssh_key_name = var.ssh_key_name
tags = var.global_tags
}
47 changes: 47 additions & 0 deletions samples/vmseries_example_plan_and_deploy/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package vmseries_example_plan_and_deploy

import (
"fmt"
"math/rand"
"os"
"testing"
"time"

"github.com/PaloAltoNetworks/terraform-modules-vmseries-tests-skeleton/pkg/testskeleton"
"github.com/gruntwork-io/terratest/modules/logger"
"github.com/gruntwork-io/terratest/modules/terraform"
)

func TestVmseriesExamplePlanAndDeploy(t *testing.T) {
// prepare random prefix
source := rand.NewSource(time.Now().UnixNano())
random := rand.New(source)
number := random.Intn(1000)
namePrefix := fmt.Sprintf("terra%d-", number)

// define options for Terraform
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
TerraformDir: ".",
VarFiles: []string{"example.tfvars"},
Vars: map[string]interface{}{
"name_prefix": namePrefix,
"ssh_key_name": "test-ssh-key",
},
Logger: logger.Default,
Lock: true,
Upgrade: true,
SetVarsAfterVarFiles: true,
})

// prepare list of items to check
assertList := []testskeleton.AssertExpression{}

// if DO_APPLY is not empty and equal true, then Terraform apply is used, in other case only Terraform plan
if os.Getenv("DO_APPLY") == "true" {
// deploy test infrastructure and verify outputs and check if there are no planned changes after deployment
testskeleton.DeployInfraCheckOutputsVerifyChanges(t, terraformOptions, assertList)
} else {
// plan test infrastructure and verify outputs
testskeleton.PlanInfraCheckErrors(t, terraformOptions, assertList, "No errors are expected")
}
}
Loading

0 comments on commit b32df3d

Please sign in to comment.