-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Customize service module #474
Conversation
Nice. This PR looks meaty so it'll take me more time to review it. But some initial thoughts:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lot of good ideas in here! Thanks for putting up this PR
# By default, Terraform creates a workspace named “default.” If a non-default workspace is not created this prefix will equal “default”, | ||
# if you choose not to use workspaces set this value to "dev" | ||
# By default, Terraform creates a workspace named “default.” If a non-default workspace is not created this prefix will equal “default”, | ||
# if you choose not to use workspaces set this value to "dev" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you choose not to use workspaces set this value to "dev"
i think this line came from my PR but i don't know what i meant by that lol
# Uncomment the following resource if you want to grant the service access to SSM parameters: | ||
#data "aws_security_groups" "aws_services" { | ||
# filter { | ||
# name = "group-name" | ||
# values = ["${module.project_config.aws_services_security_group_name_prefix}*"] | ||
# } | ||
# | ||
# filter { | ||
# name = "vpc-id" | ||
# values = [data.aws_vpc.default.id] | ||
# } | ||
#} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Over time, I've started to try to minimize the amount of "uncomment"/"comment" instructions in the template. I've noticed a few benefits to this:
- It makes it easier to update the template from the project when the diffs between project files and template files are kept to as few files as possible
- It makes it easier to develop and test the template itself, since updating the platform-test* repos benefit from the same thing as the first bullet
So for something like this, I'd move this into app-config. We could have a config something like module.app_config.use_parameter_store
.
This is important for a second reason. The current implementation would break if the application doesn't have a database. If you look in the networks module you'll see that the list of AWS services that we need to create VPC endpoints for are based on the app config. So for example if there are two apps, and one of them has a database, we'll create "ssm", "kms", and "secretmanager" VPC endpoints, but of both apps don't have a database we won't create any VPC endpoints. So to make this work, we'll also want to condition the creation of the "ssm" endpoint on whether one of the apps has use_parameter_store set to true.
The pseudo-code would be something like:
aws_service_integrations = set()
for app_config in app_configs:
if app_config.use_parameter_store:
aws_service_integrations.add("ssm")
if app_config.has_database:
aws_service_integrations.add("ssm", "kms", "secretmanager")
# Add custom container environment variables like so: | ||
# container_env_vars = [ | ||
# { name : "CUSTOM_ENV_VAR", value : "100" }, | ||
# ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I imagine some environment variables will need to have different values for each environment. In that case we can't put it in this file, otherwise all environments would be forced to use the same value. Rather than use .tfvars files, we'd want to define the environment variables in app-config, per this ADR. Then dev, staging, and prod can all have different values for the environment variables. And you can define defaults and validation rules for environment variables in env-config.
For example, you can do something like
# env-config/variables.tf
variable custom_env_var {
description = "Some custom env var"
type = number
default = 100
validation {
condition = var.custom_env_var > 0
error_message = "The custom_env_var must be a positive number"
}
}
# env-config/outputs.tf
output env_vars {
[
{
name = "CUSTOM_ENV_VAR"
value = var.custom_env_var
}
]
}
@@ -1,7 +1,8 @@ | |||
data "aws_caller_identity" "current" {} | |||
data "aws_region" "current" {} | |||
data "aws_ecr_repository" "app" { | |||
name = var.image_repository_name | |||
count = var.external_image_url == "" ? 1 : 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: stylistically it feels strange to base the existing of data.aws_ecr_repository on var.external_image_url. it seems more direct and robust to base it off of whether image_repository_name was passed in. in other words,
-
make
var.image_repository_name
nullable (set default tonull
) -
update this line to be:
Suggested changecount = var.external_image_url == "" ? 1 : 0 count = var.image_repository_name != null ? 1 : 0 -
add a validation rule to the variables that requires either var.external_image_url or var.image_repository_name to be set
@@ -16,6 +16,12 @@ variable "image_repository_name" { | |||
description = "The name of the container image repository" | |||
} | |||
|
|||
variable "external_image_url" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i'd call this image_repository_url
.
- i don't think we need "external" in the name since i don't think there's any requirement that it has to be external.
- i think we need to qualify that this is a "image repository url" not an "image url", since [image url] = [image repository url] + ":" + [image tag]
@@ -67,3 +73,65 @@ variable "db_vars" { | |||
}) | |||
default = null | |||
} | |||
|
|||
variable "container_env_vars" { | |||
type = list(map(string)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i think we can make this type definition more explicit with something like
type = list(map(string)) | |
type = list(object({ name = string, value = string })) |
see https://developer.hashicorp.com/terraform/language/expressions/type-constraints
} | ||
|
||
variable "container_secrets" { | ||
type = list(map(string)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i think we can make this type definition more explicit with something like
type = list(map(string)) | |
type = list(object({ name = string, value_from = string })) |
note that I also think we should stick with snake case (value_from
) instead of camel case (valueFrom
) since I think that's more common within terraform code)
see https://developer.hashicorp.com/terraform/language/expressions/type-constraints
resource "aws_vpc_security_group_ingress_rule" "vpc_endpoints_ingress_from_app" { | ||
security_group_id = var.aws_services_security_group_id | ||
description = "Allow inbound requests to VPC endpoints from application ${var.service_name}" | ||
|
||
from_port = 443 | ||
to_port = 443 | ||
ip_protocol = "tcp" | ||
referenced_security_group_id = aws_security_group.app.id | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: for my own understanding, what happens if we don't have this rule? i had thought that we allowed all outbound traffic from the app subnet and security group. like the block right before this is
resource "aws_security_group" "app" {
...
egress {
description = "Allow all outgoing traffic from application"
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
}
does that not already allow the app to access the AWS services without needing to go through VPC endpoints?
variable "aws_services_security_group_id" { | ||
type = string | ||
description = "Security group ID for VPC endpoints that access AWS Services" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional: we could potentially add a validation rule that says that this is required if container_secrets is non-empty
variable "healthcheck_matcher" { | ||
type = string | ||
description = "The response codes that indicate healthy to the ALB" | ||
default = "200-299" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: what are some other possible matchers we'd use? have you seen other examples in your experience with other apps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember going through a fair bit of this healthcheck customization for flowable (https://github.com/navapbc/pfml-starter-kit-app/commit/ea2f9064a6884f94434e03a4ff2ee63f675511a5), though I can't remember why we had that set to 200-401
there. I think probably because we couldn't disable the security on the endpoint, but at least it's not a 500 (or 404) error so something is working.
Update: @lorenyu Thank you for the feedback on this PR! I haven't had time to address it yet, but have it on my radar. |
This PR is probably too old to be able to merge easily as is, but I created a follow up ticket to review this PR for valuable things that we can pull into future PRs #772 |
Ticket
N/A
Changes
Context for reviewers
On a number of my recent projects, I've needed the ability to run non-custom docker images (e.g. flowable. This means I've needed the
service
module to be a little bit more flexible.Specifically, I've needed to be able to:
/health
doesn't exist, but/healthcheck
does or/homepage
does)Testing
I've created a matching test PR in the platform-test repo that demonstrates how to test these changes: navapbc/platform-test#65