Skip to content

Commit

Permalink
Update parent module detection algorithm (#123)
Browse files Browse the repository at this point in the history
* Add example of live repo structure

* get test example working

* update parent detection algorithm

* cleanup debug log statements

* bump versions

Co-authored-by: Alan Mangashev <a.mangashev@admitad.com>
  • Loading branch information
dmattia and Alan Mangashev authored Mar 10, 2021
1 parent 58c67a3 commit 9537b11
Show file tree
Hide file tree
Showing 44 changed files with 1,233 additions and 5 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION=1.1.1
VERSION=1.2.0
PATH_BUILD=build/
FILE_COMMAND=terragrunt-atlantis-config
FILE_ARCH=darwin_amd64
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Then, make sure `terragrunt-atlantis-config` is present on your Atlantis server.

```hcl
variable "terragrunt_atlantis_config_version" {
default = "1.1.1"
default = "1.2.0"
}
build {
Expand Down Expand Up @@ -167,7 +167,7 @@ You can install this tool locally to checkout what kinds of config it will gener
Recommended: Install any version via go get:

```bash
cd && GO111MODULE=on go get github.com/transcend-io/terragrunt-atlantis-config@v1.1.1 && cd -
cd && GO111MODULE=on go get github.com/transcend-io/terragrunt-atlantis-config@v1.2.0 && cd -
```

This module officially supports golang versions v1.13, v1.14, and v1.15, tested on CircleCI with each build
Expand Down
2 changes: 1 addition & 1 deletion cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func readOldConfig() (*AtlantisConfig, error) {
// The old file not existing is not an error, as it should not exist on the very first run
bytes, err := ioutil.ReadFile(outputPath)
if err != nil {
log.Warn("Could not find an old config file. Starting from scratch")
log.Info("Could not find an old config file. Starting from scratch")
return nil, nil
}

Expand Down
9 changes: 9 additions & 0 deletions cmd/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,15 @@ func TestInfrastructureLive(t *testing.T) {
})
}

func TestModulesWithNoTerraformSourceDefinitions(t *testing.T) {
runTest(t, filepath.Join("golden", "no_terraform_blocks.yml"), []string{
"--root",
filepath.Join("..", "test_examples", "no_terraform_blocks"),
"--parallel",
"--autoplan",
})
}

func TestInfrastructureMutliAccountsVPCRoute53TGWCascading(t *testing.T) {
runTest(t, filepath.Join("golden", "multi_accounts_vpc_route53_tgw.yaml"), []string{
"--root",
Expand Down
45 changes: 45 additions & 0 deletions cmd/golden/no_terraform_blocks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
automerge: false
parallel_apply: true
parallel_plan: true
projects:
- autoplan:
enabled: true
when_modified:
- '*.hcl'
- '*.tf*'
- ../network/terragrunt.hcl
- ../../stage/network/terragrunt.hcl
dir: myproject/eu-south-1/infra/apps
- autoplan:
enabled: true
when_modified:
- '*.hcl'
- '*.tf*'
- ../../stage/network/terragrunt.hcl
dir: myproject/eu-south-1/infra/network
- autoplan:
enabled: true
when_modified:
- '*.hcl'
- '*.tf*'
- ../network/terragrunt.hcl
dir: myproject/eu-south-1/stage/dbs
- autoplan:
enabled: true
when_modified:
- '*.hcl'
- '*.tf*'
dir: myproject/eu-south-1/stage/network
- autoplan:
enabled: true
when_modified:
- '*.hcl'
- '*.tf*'
dir: myproject/global/dns
- autoplan:
enabled: true
when_modified:
- '*.hcl'
- '*.tf*'
dir: myproject/global/iam
version: 3
16 changes: 16 additions & 0 deletions cmd/parse_hcl.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,22 @@ import (

type parsedHcl struct {
Terraform *terraformConfig `hcl:"terraform,block"`
Includes *includeConfig `hcl:"include,block"`
}

type terraformConfig struct {
Source *string `hcl:"source,attr"`
}

type includeConfig struct {
Path *string `hcl:"path,attr"`
}

// Not all modules need an include statement, as they could define everything in one file without a parent
// The key signifiers of a parent are:
// - no include statement
// - no terraform source defined
// If both of those are true, it is likely a parent module
func isParentModule(path string, terragruntOptions *options.TerragruntOptions) (bool, error) {
configString, err := util.ReadFileAsString(path)
if err != nil {
Expand All @@ -40,6 +50,12 @@ func isParentModule(path string, terragruntOptions *options.TerragruntOptions) (
var parsed parsedHcl
gohcl.DecodeBody(file.Body, evalContext, &parsed)

// If the file has an `includes` block, it cannot be a parent as terragrunt only allows one level of inheritance
if parsed.Includes != nil && parsed.Includes.Path != nil {
return false, nil
}

// If the file does not define a terraform source block, it is likely a parent (though not guaranteed)
if parsed.Terraform == nil || parsed.Terraform.Source == nil {
return true, nil
}
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "github.com/transcend-io/terragrunt-atlantis-config/cmd"
// This variable is set at build time using -ldflags parameters.
// But we still set a default here for those using plain `go get` downloads
// For more info, see: http://stackoverflow.com/a/11355611/483528
var VERSION string = "1.1.1"
var VERSION string = "1.2.0"

func main() {
cmd.Execute(VERSION)
Expand Down
7 changes: 7 additions & 0 deletions test_examples/no_terraform_blocks/myproject/account.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
locals {
account_name = "myproject"
aws_account_id = "999999999999"
aws_profile_name = "test"
tf_s3_bucket = "test-tf"
tf_dynamodb_table = "terraform-lock-table"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# data "aws_ami" "proxy_ami" {
# most_recent = true
# owners = ["self"]

# filter {
# name = "name"
# values = [
# "debian-buster-proxy-ami*",
# ]
# }
# }

data "aws_ami" "ubuntu_focal_arm" {
most_recent = true
owners = ["099720109477"]

filter {
name = "architecture"
values = ["arm64"]
}

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal*"]
}
}

data "aws_ami" "ubuntu_focal_x86" {
most_recent = true
owners = ["099720109477"]

filter {
name = "architecture"
values = ["x86_64"]
}

filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal*"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
variable "env" {
type = string
}

variable "region" {
type = string
}

variable "vpc_id" {
type = string
}

variable "public_subnets" {
type = list(string)
}

variable "sg_ssh_global_id" {
type = string
}

variable "sg_vpc_local_id" {
type = string
}

variable "sg_web_global_id" {
type = string
}

variable "sg_openvpn_global_id" {
type = string
}

variable "dns_zone_id" {
type = map(any)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
locals {
vm_name = "openvpn"
user_data_openvpn = <<EOF
#cloud-config
hostname: ${local.vm_name}
fqdn: ${local.vm_name}.${var.env}.local
manage_etc_hosts: false
system_info:
default_user:
name: myuser
EOF
}

resource "random_shuffle" "subnet" {
input = var.public_subnets
result_count = 1
}

module "openvpn" {
source = "github.com/Sebor/terraform-aws-ec2-instance"

name = local.vm_name
instance_count = 1
user_data_base64 = base64encode(local.user_data_openvpn)

ami = data.aws_ami.ubuntu_focal_arm.id
ebs_optimized = true
instance_type = "t4g.micro"
subnet_id = random_shuffle.subnet.result[0]
associate_public_ip_address = true
key_name = "test"

root_block_device = [
{
volume_type = "gp3"
volume_size = 30
},
]

vpc_security_group_ids = [var.sg_vpc_local_id, var.sg_ssh_global_id, var.sg_openvpn_global_id]
}

resource "aws_eip" "openvpn" {
instance = module.openvpn.id[0]
vpc = true
}

resource "aws_route53_record" "openvpn" {
zone_id = values(var.dns_zone_id)[0]
name = "${local.vm_name}.${var.env}.local"
type = "A"
ttl = "300"
records = module.openvpn.private_ip
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
### Slack outputs
output "slack_lamba_cw_log_group_arn" {
description = "The ARN of lambda log group for slack"
value = module.main_slack.lambda_cloudwatch_log_group_arn
}

output "slack_lambda_iam_role_arn" {
description = "The ARN of lambda IAM role for slack"
value = module.main_slack.lambda_iam_role_arn
}

output "slack_lambda_iam_role_name" {
description = "The Name of lambda IAM role for slack"
value = module.main_slack.lambda_iam_role_name
}

output "slack_lambda_function_name" {
description = "The name of the Lambda function for slack"
value = module.main_slack.notify_slack_lambda_function_name
}

output "slack_topic_arn" {
description = "The ARN of the SNS topic from which messages will be sent to Slack"
value = module.main_slack.this_slack_topic_arn
}

### OpenVPN outputs
output "openvpn_private_ip" {
description = "OpenVPN private address"
value = module.openvpn.private_ip
}

output "openvpn_private_dns" {
description = "OpenVPN private DNS names"
value = module.openvpn.private_dns
}

output "openvpn_public_ip" {
description = "OpenVPN public address"
value = module.openvpn.public_ip
}

output "openvpn_public_dns" {
description = "OpenVPN public DNS names"
value = module.openvpn.public_dns
}

output "openvpn_public_eip_address" {
description = "OpenVPN public address"
value = aws_eip.openvpn.public_ip
}

output "openvpn_subnet_id" {
description = "OpenVPN subnet id"
value = module.openvpn.subnet_id
}

output "openvpn_availability_zone" {
description = "OpenVPN availability zone"
value = module.openvpn.availability_zone
}

output "openvpn_instance_ids" {
description = "OpenVPN instance ids"
value = module.openvpn.id
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module "main_slack" {
source = "github.com/terraform-aws-modules/terraform-aws-notify-slack"

sns_topic_name = "main-slack-topic"

slack_webhook_url = "https://hooks.slack.com/services/AAA/BBB/CCC"
slack_channel = "main-aws-notification"
slack_username = "AWS"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
include {
path = find_in_parent_folders()
}

locals {
# Automatically load environment-level variables
environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl"))
region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl"))

# Extract out common variables for reuse
env = local.environment_vars.locals.environment
region = local.region_vars.locals.aws_region
}

dependency "network" {
config_path = "../network"
mock_outputs_allowed_terraform_commands = ["plan", "plan-all"]
mock_outputs = {
vpc_id = "vpc-1234567890"
public_subnets = ["az-1", "az-2", "az-3"]
sg_ssh_global_id = "sg-1234567890"
sg_vpc_local_id = "sg-2443645664"
sg_web_global_id = "sg-24344645645645"
sg_openvpn_global_id = "sg-98798769736"
dns_zone_id = { "zone.local" : "PZ1234567890" }
}
}

inputs = {
env = local.env
region = local.region
vpc_id = dependency.network.outputs.vpc_id
public_subnets = dependency.network.outputs.public_subnets
sg_ssh_global_id = dependency.network.outputs.sg_ssh_global_id
sg_vpc_local_id = dependency.network.outputs.sg_vpc_local_id
sg_web_global_id = dependency.network.outputs.sg_web_global_id
sg_openvpn_global_id = dependency.network.outputs.sg_openvpn_global_id
dns_zone_id = dependency.network.outputs.dns_zone_id
}
Loading

0 comments on commit 9537b11

Please sign in to comment.