From 1b782a615dba2dbe567a1f1bd8acedacd188879a Mon Sep 17 00:00:00 2001 From: badra001 Date: Thu, 12 Dec 2024 12:40:43 -0500 Subject: [PATCH] add emr module (first pass) --- emr/.terraform.lock.hcl | 10 ++ emr/.tf-control | 32 +++++ emr/.tf-control.tfrc | 24 ++++ emr/_module_name.tf | 3 + emr/locals.tf | 7 + emr/main.tf | 208 +++++++++++++++++++++++++++++ emr/main.tf.off | 43 ++++++ emr/settings.yml | 86 ++++++++++++ emr/sg-emr-core.tf.sample | 38 ++++++ emr/sg-emr-master.tf.sample | 42 ++++++ emr/sg-emr-service.tf.sample | 39 ++++++ emr/sg-emr-studio-engine.tf.sample | 32 +++++ emr/sg-emr-studio.tf.sample | 50 +++++++ emr/variables.tf | 75 +++++++++++ emr/version.tf | 1 + emr/versions.tf | 1 + 16 files changed, 691 insertions(+) create mode 100644 emr/.terraform.lock.hcl create mode 100644 emr/.tf-control create mode 100644 emr/.tf-control.tfrc create mode 100644 emr/_module_name.tf create mode 100644 emr/locals.tf create mode 100644 emr/main.tf create mode 100644 emr/main.tf.off create mode 100644 emr/settings.yml create mode 100644 emr/sg-emr-core.tf.sample create mode 100644 emr/sg-emr-master.tf.sample create mode 100644 emr/sg-emr-service.tf.sample create mode 100644 emr/sg-emr-studio-engine.tf.sample create mode 100644 emr/sg-emr-studio.tf.sample create mode 100644 emr/variables.tf create mode 120000 emr/version.tf create mode 120000 emr/versions.tf diff --git a/emr/.terraform.lock.hcl b/emr/.terraform.lock.hcl new file mode 100644 index 0000000..d3db8e6 --- /dev/null +++ b/emr/.terraform.lock.hcl @@ -0,0 +1,10 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.80.0" + constraints = ">= 5.0.0" + hashes = [ + "h1:N5Wfsf4xe5DJfSeo0G/ulkIxzyfmUIoSj/hAiZ2DaKU=", + ] +} diff --git a/emr/.tf-control b/emr/.tf-control new file mode 100644 index 0000000..340816b --- /dev/null +++ b/emr/.tf-control @@ -0,0 +1,32 @@ +# .tf-control +# allows for setting a specific command to be used for tf-* commands under this git repo +# see tf-control.sh help for more info + +TFCONTROL_VERSION="1.0.7" +#TFCOMMAND="terraform_latest" +TFCOMMAND="terraform_current" + +# TF_CLI_CONFIG_FILE=PATH-TO-FILE/.tf-control.tfrc +# TFARGS="" +# TFNOLOG="" +# TFNOCOLOR="" + +# from issue: https://github.com/hashicorp/terraform/issues/32901 +# to get to TF 1.4 and beyond in a shared cache environment +# this is currently in the tf-control.sh script explicitly +#TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE=1 + +# use the following to force a specific version. An upgrade of an existing 0.12.31 to 1.x +# needs you to cycle through 0.13.17, 0.14.11, and then latest (0.15.5 not needed). Other +# steps in between. See https://github.e.it.census.gov/terraform/support/tree/master/docs/how-to/terraform-upgrade for details +# +#TFCOMMAND="terraform_0.12.31" +#TFCOMMAND="terraform_0.13.7" +#TFCOMMAND="terraform_0.14.11" +#TFCOMMAND="terraform_0.15.5" +#TFCOMMAND="terraform_1.3.10" +#TFCOMMAND="terraform_1.4.7" +#TFCOMMAND="terraform_1.5.7" +#TFCOMMAND="terraform_1.6.6" +#TFCOMMAND="terraform_1.7.5" +#TFCOMMAND="terraform_1.8.2" diff --git a/emr/.tf-control.tfrc b/emr/.tf-control.tfrc new file mode 100644 index 0000000..7425488 --- /dev/null +++ b/emr/.tf-control.tfrc @@ -0,0 +1,24 @@ +TFCONTROL_VERSION="1.0.5" + +# https://www.terraform.io/docs/cli/config/config-file.html +plugin_cache_dir = "/data/terraform/terraform.d/plugin-cache" +#disable_checkpoint = true + +provider_installation { +# filesystem_mirror { +# path = "/apps/terraform/terraform.d/providers" +# include = [ "*/*/*" ] +# } + filesystem_mirror { + path = "/data/terraform/terraform.d/providers" + include = [ "*/*/*" ] + } +# filesystem_mirror { +# path = "/apps/terraform/terraform.d/providers" +# include = [ "external.terraform.census.gov/*/*" ] +# } + direct { + include = [ "*/*/*" ] + } +} + diff --git a/emr/_module_name.tf b/emr/_module_name.tf new file mode 100644 index 0000000..77c3879 --- /dev/null +++ b/emr/_module_name.tf @@ -0,0 +1,3 @@ +locals { + _module_name = "aws-common-security-groups/emr" +} diff --git a/emr/locals.tf b/emr/locals.tf new file mode 100644 index 0000000..9dececd --- /dev/null +++ b/emr/locals.tf @@ -0,0 +1,7 @@ +locals { + base_tags = { + "boc:created_by" = "terraform" + "boc:tf_module_version" = local._module_version + "boc:tf_module_name " = local._module_name + } +} diff --git a/emr/main.tf b/emr/main.tf new file mode 100644 index 0000000..ab7462c --- /dev/null +++ b/emr/main.tf @@ -0,0 +1,208 @@ +/** +* # About emr +* +* This describes how to use the aws-common-security-groups submodule for emr. +* +* ## Usage +* +* ```hcl +* module "emr" { +* source = "git@github.e.it.census.gov:terraform-modules/aws-common-security-groups.git//emr" +* +* vpc_id = var.vpc_id +* name_prefix = "edl-dev-124567" +* ## optional +* # ingress_prefix_list_names = [ "rds-postgres.edl.project" ] +* # egress_prefix_list_names = [ ] +* +* ## tags for Name, CostAllocation, and Environment are pre-set, but they can be overriden +* # tags = { } +* } +* +* ## ingress_networks +* This is the list of network CIDR blocks for inbound access to the ports defined for RDS Postgres. +* There is a default set of CIDR blocks provided if this field is not populated. This is comprised of the +* Census networks: +* * 148.129.0.0/16: Census class B +* * 172.16.0.0/12: Census private class B +* * 192.168.0.0/16: Census private class C +* * 10.0.0.0/8: Censsu private class A +* +* Passing a null or empty list to this field will ignore the ingress setting on these networks. +* +* ## ingress_prefix_list_names +* In order to use a managed prefix list, you may pass a list of names in this field. The prefix lists +* will be looked up and the resultant IDs used in the security group for inbound port access to RDS +* Postgres. This will fail if the prefix list does not exist. +* ``` +*/ + +data "aws_vpc" "this_vpc" { + id = var.vpc_id +} + +## data "aws_security_group" "ingress_security_groups" { +## count = length(var.ingress_security_groups) +## id = element(var.ingress_security_groups, count.index) +## } +## +## data "aws_security_group" "egress_security_groups" { +## count = length(var.egress_security_groups) +## id = element(var.egress_security_groups, count.index) +## } + +locals { + n_all = ["0.0.0.0/0"] + n_census = ["148.129.0.0/16", "192.168.0.0/16", "172.16.0.0/12", "10.0.0.0/8"] + + ingress_networks = var.ingress_networks == null ? [] : var.ingress_networks + egress_networks = var.egress_networks == null ? [] : var.egress_networks +} + +locals { + vpc_networks = var.use_vpc_cidr ? [data.aws_vpc.this_vpc.cidr_block] : [] + external_ingress_networks = compact(concat(local.vpc_networks, local.ingress_networks)) + # ingress_sg_names = zipmap(var.ingress_security_groups, data.aws_security_group.ingress_security_groups[*].name) + # egress_sg_names = zipmap(var.egress_security_groups, data.aws_security_group.egress_security_groups[*].name) +} + +locals { + _sg = yamldecode(file("${path.module}/settings.yml")) + sg = { for sg in local._sg["security-groups"] : sg.name => merge(sg, { ingress_networks = flatten(distinct(compact(concat(local.ingress_networks, sg.vpc_cidr ? [data.aws_vpc.this_vpc.cidr_block] : [])))) }) } +} + +# create group with just egress. Add all ingress via secondary resource +resource "aws_security_group" "sg" { + for_each = local.sg + name = format("%v-%v", var.name_prefix, each.key) + description = trimspace(format("%v %v", var.description_prefix, each.value.description)) + vpc_id = var.vpc_id + + egress { + description = "ALL" + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = local.egress_networks + } + + dynamic "ingress" { + for_each = { for i in each.value.ingress : format("%v:%v", i.from, i.proto) => merge({ label = format("%v:%v", i.from, i.proto) }, i) } + iterator = p + content { + description = p.value.short + from_port = p.value.from + to_port = try(p.value.to, p.value.from) + protocol = p.value.proto + cidr_blocks = try(p.value.cidr_blocks, null) == "incoming" ? p.value.ingress_networks : [] + # prefix_list_ids = + security_groups = length(try(p.value.ingress_security_groups, [])) > 0 ? [for k, v in aws_security_group.sg : v.id if contains(p.value.ingress_security_groups, k)] : [] + self = try(p.value.self, false) + } + } + + tags = merge( + local.base_tags, + var.tags, + { "Name" = format("sg-%v-%v", var.name_prefix, each.key) } + ) +} + + +## # ingress with prefix lists +## dynamic "ingress" { +## for_each = length(var.ingress_prefix_list_names) > 0 ? local.port_map["external"] : toset([]) +## iterator = p +## content { +## description = "${local.short_description}: ${p.value["description"]}" +## from_port = p.value["from"] +## to_port = p.value["to"] +## protocol = p.value["proto"] +## prefix_list_ids = [for pl in data.aws_ec2_managed_prefix_list.ingress : pl.id] +## } +## } +## +## +## # ingress security group ids (all) +## dynamic "ingress" { +## for_each = local.ingress_sg +## iterator = sg +## content { +## description = "${local.short_description}: ${local.ingress_sg_names[sg.value]}" +## from_port = 0 +## to_port = 0 +## protocol = -1 +## security_groups = [sg.value] +## } +## } +## +## +## # ingress self (list with one or zero items) +## dynamic "ingress" { +## for_each = local.self +## iterator = sg +## content { +## description = "${local.short_description}: from self" +## from_port = 0 +## to_port = 0 +## protocol = -1 +## self = true +## } +## } +## +## +## +## # egress security group ids (all) +## dynamic "egress" { +## for_each = local.egress_sg +## iterator = sg +## content { +## description = "${local.short_description}: ${local.egress_sg_names[sg]}" +## from_port = 0 +## to_port = 0 +## protocol = -1 +## security_groups = [sg] +## } +## } +## +## # egress with prefix lists +## dynamic "egress" { +## for_each = length(var.egress_prefix_list_names) > 0 ? local.port_map["external"] : toset([]) +## iterator = p +## content { +## description = "${local.short_description}: ${local.egress_sg_names[sg]}" +## from_port = 0 +## to_port = 0 +## protocol = -1 +## prefix_list_ids = [for pl in data.aws_ec2_managed_prefix_list.egress : pl.id] +## } +## } + + +## resource "aws_vpc_security_group_ingress_rule" "example" { +## security_group_id = aws_security_group.example.id +## +## cidr_ipv4 = "10.0.0.0/8" +## from_port = 80 +## ip_protocol = "tcp" +## to_port = 80 +## } +## +## @@@ +## +## { +## "description" = "EMR Service Access" +## "ingress" = [ +## { +## "from" = 9443 +## "proto" = "tcp" +## "security_groups" = "emr-master-node" +## "short" = "Master Node" +## "to" = 9443 +## }, +## ] +## "name" = "emr-service-access" +## "self" = false +## "vpc_cidr" = false +## }, +## diff --git a/emr/main.tf.off b/emr/main.tf.off new file mode 100644 index 0000000..49970d4 --- /dev/null +++ b/emr/main.tf.off @@ -0,0 +1,43 @@ + +locals { + sg_name_emr_master = "edl-prod-7530562-emr-master-node" + sg_description_emr_master = "Security group for EMR Master Node" +} + +data "aws_security_groups" "emr_sg" { + filter { + name = "vpc-id" + values = [local.vpc_id] + } + filter { + name = "tag:Name" + values = ["sg-edl-prod-7530562-emr-core-tasks-node", "sg-edl-prod-7530562-emr-studio", "sg-edl-prod-7530562-emr-service-access"] + } +} + +module "sg_emr_master" { + source = "git@github.e.it.census.gov:terraform-modules/aws-common-security-groups.git//custom?ref=tf-upgrade" + vpc_id = local.vpc_id + name = local.sg_name_emr_master + description = local.sg_description_emr_master + + ingress_security_groups = tolist(data.aws_security_groups.emr_sg.ids) + ingress_port_list = [ + [22, 22, "tcp", "SSH", var.census_private_cidr], + [80, 80, "tcp", "HTTP", var.census_private_cidr], + [443, 443, "tcp", "HTTPS", var.census_private_cidr], + [9870, 9870, "tcp", "HDFS Name Node", var.census_private_cidr], + [18080, 18080, "tcp", "Spark History Server", var.census_private_cidr], + [8088, 8088, "tcp", "Resource Manager", var.census_private_cidr], + ] + use_vpc_cidr = false + enable_self = true + tags = merge( + local.common_tags, + ) +} + +output "sg_emr_master_id" { + description = "Emr Master node security group" + value = module.sg_emr_master.this_security_group_id +} diff --git a/emr/settings.yml b/emr/settings.yml new file mode 100644 index 0000000..c5bac02 --- /dev/null +++ b/emr/settings.yml @@ -0,0 +1,86 @@ +security-groups: + - name: emr-core-tasks-node + description: "EMR Core and Tasks Nodes" + self: true + vpc_cidr: false + ingress: + - from: 9864 + to: 9864 + proto: tcp + short: "HDFS Data Node" + cidr_blocks: incoming + - from: 8042 + to: 8042 + proto: tcp + short: "Node Manager" + cidr_blocks: incoming + ingress_security_groups: + - emr-master-node + - emr-service-access + + - name: emr-master-node + description: "EMR Master Node" + self: true + vpc_cidr: false + ingress: + - from: 22 + to: 22 + proto: tcp + short: "SSH" + cidr_blocks: incoming + - from: 80 + to: 80 + proto: tcp + short: "HTTP" + cidr_blocks: incoming + - from: 443 + to: 443 + proto: tcp + short: "HTTPS" + cidr_blocks: incoming + - from: 8088 + to: 8088 + proto: tcp + short: "Resource Manager" + cidr_blocks: incoming + - from: 9870 + to: 9870 + proto: tcp + short: "HDFS Name Node" + cidr_blocks: incoming + - from: 18080 + to: 18080 + proto: tcp + short: "Spark History Server" + cidr_blocks: incoming + ingress_security_groups: + - emr-core-tasks-node + - emr-studio + - emr-service-access + + - name: emr-service-access + description: "EMR Service Access" + self: false + vpc_cidr: false + ingress: + - from: 9443 + to: 9443 + proto: tcp + short: "Master Node" + security_groups: emr-master-node + + - name: emr-studio-engine + description: "EMR Studio Engine" + self: false + vpc_cidr: false + ingress: + - from: 18888 + to: 18888 + proto: tcp + short: "EMR Studio" + security_groups: emr-studio + + - name: emr-studio + description: "EMR Studio" + self: false + vpc_cidr: false diff --git a/emr/sg-emr-core.tf.sample b/emr/sg-emr-core.tf.sample new file mode 100644 index 0000000..d5b1bb8 --- /dev/null +++ b/emr/sg-emr-core.tf.sample @@ -0,0 +1,38 @@ +locals { + sg_name_emr_core = "edl-prod-7530562-emr-core-tasks-node" + sg_description_emr_core = "Security group for EMR Core and Tasks Nodes" +} + +data "aws_security_groups" "emr" { + filter { + name = "vpc-id" + values = [local.vpc_id] + } + filter { + name = "tag:Name" + values = ["sg-edl-prod-7530562-emr-master-node", "sg-edl-prod-7530562-emr-service-access"] + } +} + +module "sg_emr_core" { + source = "git@github.e.it.census.gov:terraform-modules/aws-common-security-groups.git//custom?ref=tf-upgrade" + vpc_id = local.vpc_id + name = local.sg_name_emr_core + description = local.sg_description_emr_core + + ingress_security_groups = tolist(data.aws_security_groups.emr.ids) + ingress_port_list = [ + [9864, 9864, "tcp", "HDFS Data Node", var.census_private_cidr], + [8042, 8042, "tcp", "Node Manager", var.census_private_cidr], + ] + use_vpc_cidr = false + enable_self = true + tags = merge( + local.common_tags, + ) +} + +output "sg_emr_core_id" { + description = "Emr Core and Task security group" + value = module.sg_emr_core.this_security_group_id +} diff --git a/emr/sg-emr-master.tf.sample b/emr/sg-emr-master.tf.sample new file mode 100644 index 0000000..73576da --- /dev/null +++ b/emr/sg-emr-master.tf.sample @@ -0,0 +1,42 @@ +locals { + sg_name_emr_master = "edl-prod-7530562-emr-master-node" + sg_description_emr_master = "Security group for EMR Master Node" +} + +data "aws_security_groups" "emr_sg" { + filter { + name = "vpc-id" + values = [local.vpc_id] + } + filter { + name = "tag:Name" + values = ["sg-edl-prod-7530562-emr-core-tasks-node", "sg-edl-prod-7530562-emr-studio", "sg-edl-prod-7530562-emr-service-access"] + } +} + +module "sg_emr_master" { + source = "git@github.e.it.census.gov:terraform-modules/aws-common-security-groups.git//custom?ref=tf-upgrade" + vpc_id = local.vpc_id + name = local.sg_name_emr_master + description = local.sg_description_emr_master + + ingress_security_groups = tolist(data.aws_security_groups.emr_sg.ids) + ingress_port_list = [ + [22, 22, "tcp", "SSH", var.census_private_cidr], + [80, 80, "tcp", "HTTP", var.census_private_cidr], + [443, 443, "tcp", "HTTPS", var.census_private_cidr], + [9870, 9870, "tcp", "HDFS Name Node", var.census_private_cidr], + [18080, 18080, "tcp", "Spark History Server", var.census_private_cidr], + [8088, 8088, "tcp", "Resource Manager", var.census_private_cidr], + ] + use_vpc_cidr = false + enable_self = true + tags = merge( + local.common_tags, + ) +} + +output "sg_emr_master_id" { + description = "Emr Master node security group" + value = module.sg_emr_master.this_security_group_id +} diff --git a/emr/sg-emr-service.tf.sample b/emr/sg-emr-service.tf.sample new file mode 100644 index 0000000..6803aa0 --- /dev/null +++ b/emr/sg-emr-service.tf.sample @@ -0,0 +1,39 @@ +locals { + sg_name_emr_service = "edl-prod-7530562-emr-service-access" + sg_description_emr_service = "Security group for EMR Service Access" +} + +resource "aws_security_group" "sg_emr_service" { + vpc_id = local.vpc_id + name = local.sg_name_emr_service + description = local.sg_description_emr_service + + ingress { + description = "Master Node" + from_port = 9443 + to_port = 9443 + protocol = "tcp" + security_groups = [module.sg_emr_master.this_security_group_id] + } + + egress { + description = "All traffic" + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = local.egress_networks + } + + tags = merge( + local.common_tags, + { + "VPC" = var.vpc_full_name, + "Name" = "sg-${local.sg_name_emr_service}", + }, + ) +} + +output "sg_emr_service_id" { + description = "Emr Service Access security group" + value = aws_security_group.sg_emr_service.id +} diff --git a/emr/sg-emr-studio-engine.tf.sample b/emr/sg-emr-studio-engine.tf.sample new file mode 100644 index 0000000..b4d210d --- /dev/null +++ b/emr/sg-emr-studio-engine.tf.sample @@ -0,0 +1,32 @@ +locals { + sg_name_emr_studio_engine = "edl-prod-7530562-emr-studio-engine" + sg_description_emr_studio_engine = "Security group for EMRStudio Engine" +} + +resource "aws_security_group" "sg_emr_studio_engine" { + + vpc_id = local.vpc_id + name = local.sg_name_emr_studio_engine + description = local.sg_description_emr_studio_engine + ingress { + description = "edl-dps-emr-studio" + from_port = 18888 + to_port = 18888 + protocol = "tcp" + security_groups = [aws_security_group.sg_emr_studio.id] + } + egress { + description = "All traffic" + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = local.egress_networks + } + tags = merge( + local.common_tags, + { + "VPC" = var.vpc_full_name, + "Name" = "sg-${local.sg_name_emr_studio_engine}", + } + ) +} diff --git a/emr/sg-emr-studio.tf.sample b/emr/sg-emr-studio.tf.sample new file mode 100644 index 0000000..c524de7 --- /dev/null +++ b/emr/sg-emr-studio.tf.sample @@ -0,0 +1,50 @@ +locals { + sg_name_emr_studio = "edl-prod-7530562-emr-studio" + sg_description_emr_studio = "Security group for EMRStudio" + egress_networks = ["0.0.0.0/0"] +} + +data "aws_security_groups" "emr_studio_sg" { + filter { + name = "vpc-id" + values = [local.vpc_id] + } + filter { + name = "tag:Name" + values = ["sg-edl-prod-7530562-emr-studio-engine"] + } +} + +resource "aws_security_group" "sg_emr_studio" { + + vpc_id = local.vpc_id + name = local.sg_name_emr_studio + description = local.sg_description_emr_studio + egress { + description = "All traffic" + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = local.egress_networks + } + egress { + description = "EMR Studio Engine" + from_port = 18888 + to_port = 18888 + protocol = "tcp" + security_groups = tolist(data.aws_security_groups.emr_studio_sg.ids) + } + + tags = merge( + local.common_tags, + { + "VPC" = var.vpc_full_name, + "Name" = "sg-${local.sg_name_emr_studio}", + } + ) +} + +output "sg_emr_studio_id" { + description = "Emr Studio security group" + value = aws_security_group.sg_emr_studio.id +} diff --git a/emr/variables.tf b/emr/variables.tf new file mode 100644 index 0000000..447f897 --- /dev/null +++ b/emr/variables.tf @@ -0,0 +1,75 @@ +variable "name_prefix" { + description = "Security Group Name Prefix" + type = string +} + +variable "description_prefix" { + description = "Security Group Description Prefix" + type = string + default = "Security Group" +} + +variable "use_vpc_cidr" { + description = "Enable|Disable use of VPC CIDR block in the ingress_networks" + type = bool + default = false +} + +#--- +# others with defaults +#--- +variable "vpc_id" { + description = "VPC ID Number" + type = string +} + +## variable "vpc_full_name" { +## description = "VPC Name" +## type = string +## default = "" +## } + +variable "ingress_networks" { + description = "List of ingress networks for external access (not all ports). Use null to disable built-in settings" + type = list(string) + default = [] +} + +variable "egress_networks" { + description = "List of egress networks (all ports)" + type = list(string) + default = ["0.0.0.0/0"] +} + +## variable "ingress_security_groups" { +## description = "List of ingress security groups for all ports" +## type = list(string) +## default = [] +## } +## +## variable "egress_security_groups" { +## description = "List of egress security groups (all ports)" +## type = list(string) +## default = [] +## } + +variable "tags" { + description = "Tags" + type = map(any) + default = { + "CostAllocation" = "csvd:infrastructure" + "Environment" = "infrastructure" + } +} + +## variable "ingress_prefix_list_names" { +## description = "List of prefix list names for ingress access" +## type = list(string) +## default = [] +## } +## +## variable "egress_prefix_list_names" { +## description = "List of prefix list names for eggress access" +## type = list(string) +## default = [] +## } diff --git a/emr/version.tf b/emr/version.tf new file mode 120000 index 0000000..b83c5b7 --- /dev/null +++ b/emr/version.tf @@ -0,0 +1 @@ +../common/version.tf \ No newline at end of file diff --git a/emr/versions.tf b/emr/versions.tf new file mode 120000 index 0000000..41bb22f --- /dev/null +++ b/emr/versions.tf @@ -0,0 +1 @@ +../common/versions.tf \ No newline at end of file