Skip to content
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

Support new GitHub bot deployment #15

Merged
merged 24 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ override.tf.json
*_override.tf
*_override.tf.json
.terraformrc
terraform.rc
terraform.rc
.DS_Store
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ Terraform configuration used to create the required AWS resources for integratin

| Name | Version |
| ----------- | ----------- |
| [aws](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) | >= 4.37.0 |
| [aws](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) | >= 5.26.0 |

## Inputs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some missing fields in the documentation. check out this tool https://terraform-docs.io/ it can help you to manage it
lambda_source_code_path
frontend_lambda_source_code_path
backend_lambda_source_code_path
lambda_logs_retention_in_days
lambda_enable_logs
resource_name_common_part
secrets_names
gateway_api_integration_timeout_milliseconds


| Name | Description | Type | Default | Required |
| ----------- | ----------- | ----------- | ----------- | ----------- |
| `environment` | The target environment name for deployment | `string` | `prod` | No |
| `integration_type` | Spectral integration type (A unique phrase describing the integration) - Available values: `terraform`, `jira` and `gitlab` | `string` | N/A | Yes |
| `integration_type` | Spectral integration type (A unique phrase describing the integration) - Available values: `github`, `terraform`, `jira` and `gitlab` | `string` | N/A | Yes |
| [`env_vars`](#env_vars) | Extendable object contains all required environment variables required for the integration. | `map(string)` | N/A | No |
| [`global_tags`](#global_tags) | Tags to be applied on every newly created resource. | `map(string)` | ```{ BusinessUnit = "Spectral" }``` | No |
| [`tags`](#tags) | Tags to be applied on concrete resources | `map(map(string))` | ```{ iam = { } lambda = { } api_gateway = { } }``` | No |
Expand All @@ -30,7 +30,7 @@ Terraform configuration used to create the required AWS resources for integratin
| `lambda_function_timeout` | Amount of time your Lambda Function has to run in seconds. | `number` | 300 | No |
| `lambda_function_memory_size` | Amount of memory in MB your Lambda Function can use at runtime. | `number` | 1024 | No |
| `lambda_publish` | Whether to publish creation/change as new Lambda Function Version. | `bool` | `false` | No |
| `store_secret_in_secrets_manager` | Whether to store secrets values on a vault (currently supporting AWS secrets manager on GitLab integration). | `bool` | `false` | No |
| `store_secret_in_secrets_manager` | Whether to store secrets values on a vault (currently supporting AWS secrets manager on GitHub / GitLab integration). | `bool` | `false` | No |

### env_vars
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is partially relevant to the bots.
i think we can remove this section and redirect to our guide docs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The env vars are not detailed in this section, this just shows how to set variables and redirects the users to the docs.
The only env var mentioned here is SPECTRAL_DSN which is always mandatory.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the var that is mandatory, in a different but, there is more mandatory env var. i just say to refer to our docs and remote this section


Expand Down Expand Up @@ -152,3 +152,7 @@ module "spectral_lambda_integration" {
8. `lambda_iam_role_arn` - Amazon Resource Name (ARN) specifying the role.
9. `lambda_iam_role_name` - Name of the role.
10. `secrets_arns` - Arns of created secrets in secrets manager.

## Support
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a changelog file that lists versions, describes the changes, and breaks changes per version is essential, and can avoid a lot of questions.


For GitHub deployment - only bot version 2.x is supported
32 changes: 32 additions & 0 deletions examples/basic-github-integration.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module "spectral_lambda_integration" {
source = "github.com/SpectralOps/spectral-terraform-lambda-integration"

integration_type = "github"
lambda_enable_logs = true

# Use this attributes to deploy specific version of the bot
frontend_lambda_source_code_path = "./source-code/github/github-frontend.zip"
backend_lambda_source_code_path = "./source-code/github/github-backend.zip"

env_vars = {
# Required environment variables
SPECTRAL_DSN = "MySpectralDSN"
CHECK_POLICY = "Fail on any issue" # (Fail on any issue / Fail on warnings and above / Fail on errors only / Always Pass)
GITHUB_APP_ID = "MyGitHubAppId"
GITHUB_WEBHOOK_SECRET = "MyGitHubWebhookSecret"
GITHUB_PRIVATE_KEY = "MyGitHubPrivateKey"
# Optional environment variables
SECRETS_VAULT = "aws_secrets_manager"
VAULT_KEY_SPECTRAL_DSN = "Spectral_Dsn-..."
VAULT_KEY_GITHUB_WEBHOOK_SECRET = "Spectral_GithubBot_WebhookSecret-..."
VAULT_KEY_GITHUB_PRIVATE_KEY = "Spectral_GithubBot_PrivateKey-..."
GITHUB_SHOULD_POST_REVIEW_COMMENTS = false
GITHUB_SHOULD_SKIP_CHECK = false
S3_BLACK_LIST_OBJECT_KEY = "The S3 object key of your blacklist flie"
S3_BLACK_LIST_BUCKET_NAME = "The S3 bucket name that holds your blacklist file"
SHOULD_SKIP_INGEST = false
STRICT_MODE = false
SPECTRAL_TAGS = "iac,base,audit"
HOME = "/tmp"
}
}
12 changes: 10 additions & 2 deletions locals.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
locals {
resource_name_pattern = "spectral-${var.integration_type}-integration-${var.environment}"
resource_name_pattern = coalesce(var.resource_name_common_part, "spectral-${var.integration_type}-integration-${var.environment}-${random_string.random_resource_name_suffix.id}")
single_lambda_integration = contains(["jira", "terraform"], var.integration_type) ? true : false
multiple_lambda_integration = contains(["gitlab"], var.integration_type) ? true : false
multiple_lambda_integration = contains(["gitlab", "github"], var.integration_type) ? true : false
api_triggered_function_arn = local.single_lambda_integration ? module.lambda_function[0].lambda_function_arn : module.frontend_lambda_function[0].lambda_function_arn
frontend_lambda_handler = contains(["github"], var.integration_type) ? "index.handler" : "frontend.app"
backend_lambda_handler = contains(["github"], var.integration_type) ? "index.handler" : "backend.app"
default_secrets_names = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about SPECTRAL_DSN?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct

"github" = coalesce(var.secrets_names, ["Spectral_GithubBot_PrivateKey", "Spectral_GithubBot_WebhookSecret"]),
"gitlab" = coalesce(var.secrets_names, ["Spectral_GitlabBot_GitlabToken", "Spectral_GitlabBot_WebhookSecret"])
}
# Please do not change or replace the 'frontend' suffix since there a logic in the bot based in it
function_name = local.single_lambda_integration ? local.resource_name_pattern : "${local.resource_name_pattern}-frontend"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function_name can be confusing since it implies working with both the backend and the front end. I recommend the following changes:

Rename options:

  • receive_hook_lambda_name
  • provider_request_treigger_lambda_name

The idea is to understand when you read this variable what he does.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to api_integrated_function_name

}
3 changes: 2 additions & 1 deletion modules/api_gateway/rest_api.tf
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ resource "aws_api_gateway_integration" "api_proxy_integration" {
integration_http_method = "POST"
type = "AWS_PROXY"
uri = var.lambda_function_arn
timeout_milliseconds = var.gateway_api_integration_timeout_milliseconds
}

resource "aws_api_gateway_method_response" "response_200" {
Expand All @@ -43,7 +44,7 @@ resource "aws_api_gateway_method_response" "response_200" {
resource "aws_lambda_permission" "lambda_permission" {
statement_id = "AllowExecutionFromAPIGateway"
action = "lambda:InvokeFunction"
function_name = var.resource_name_pattern
function_name = var.function_name
principal = "apigateway.amazonaws.com"
source_arn = local.rest_api_execution_arn
}
Expand Down
11 changes: 11 additions & 0 deletions modules/api_gateway/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,15 @@ variable "tags" {
variable "integration_type" {
type = string
description = "Spectral integration type (A unique phrase describing the integration) - Available values: `terraform`."
}

variable "function_name" {
type = string
description = "The name of the function the API would trigger upon request"
}

variable "gateway_api_integration_timeout_milliseconds" {
description = "Timeout for the API Gateway to wait for lambda response"
type = number
default = 29000
}
4 changes: 2 additions & 2 deletions modules/lambda/lambda.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
locals {
runtime = "nodejs14.x"
lambda_source_code_zip_path = "${path.module}/source_code/${var.integration_type}/${var.lambda_source_code_filename}"
runtime = "nodejs20.x"
lambda_source_code_zip_path = coalesce(var.lambda_source_code_path, "${path.module}/source_code/${var.integration_type}/${var.lambda_source_code_filename}")
}

resource "aws_lambda_function" "spectral_scanner_lambda" {
Expand Down
Binary file added modules/lambda/source_code/github/backend.zip
Binary file not shown.
Binary file added modules/lambda/source_code/github/frontend.zip
Binary file not shown.
Binary file modified modules/lambda/source_code/gitlab/backend.zip
Binary file not shown.
Binary file modified modules/lambda/source_code/gitlab/frontend.zip
Binary file not shown.
Binary file modified modules/lambda/source_code/jira/app.zip
Binary file not shown.
Binary file modified modules/lambda/source_code/terraform/app.zip
Binary file not shown.
10 changes: 5 additions & 5 deletions modules/lambda/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,16 @@ variable "secrets_arns" {
default = []
}

variable "store_secret_in_secrets_manager" {
description = "Whether to store your secrets in secrets manager, default is false"
type = bool
}

variable "lambda_source_code_filename" {
type = string
description = "The lambda source code filename"
}

variable "lambda_source_code_path" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use terraform-docs to generate the readme file for the lambda module.
this is relevant for all the modules

type = string
description = "The lambda source code path"
}

variable "role_arn" {
type = string
description = "The lambda source code filename"
Expand Down
2 changes: 1 addition & 1 deletion modules/role/role.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data "aws_iam_policy_document" "assume_role_policy" {
}

resource "aws_iam_role" "lambda_execution_role" {
name = var.resource_name_pattern
name = var.role_name
assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json

tags = merge(
Expand Down
4 changes: 2 additions & 2 deletions modules/role/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ variable "tags" {
}
}

variable "resource_name_pattern" {
variable "role_name" {
type = string
description = "A common resource name created by pattern."
description = "The name of the role"
}

variable "multiple_lambda_integration" {
Expand Down
7 changes: 0 additions & 7 deletions modules/secrets_manager/gitlab/main.tf

This file was deleted.

6 changes: 0 additions & 6 deletions modules/secrets_manager/gitlab/outputs.tf

This file was deleted.

8 changes: 4 additions & 4 deletions modules/secrets_manager/secrets.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
locals {
secrets_arns = concat(
try(module.gitlab[0].secrets_arns, []),
[for secret in aws_secretsmanager_secret.general_secret : secret.arn],
[aws_secretsmanager_secret.spectral_dsn.arn]
)
}
Expand All @@ -9,7 +9,7 @@ resource "aws_secretsmanager_secret" "spectral_dsn" {
name = "Spectral_Dsn"
}

module "gitlab" {
count = var.integration_type == "gitlab" ? 1 : 0
source = "./gitlab"
resource "aws_secretsmanager_secret" "general_secret" {
count = length(var.secrets_names)
name = var.secrets_names[count.index]
}
5 changes: 5 additions & 0 deletions modules/secrets_manager/variables.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
variable "integration_type" {
description = "Integration type to create secrets for"
type = string
}

variable "secrets_names" {
description = "Names of secrets to create"
type = list(string)
}
74 changes: 38 additions & 36 deletions multiple-lambdas-integration.tf
Original file line number Diff line number Diff line change
@@ -1,43 +1,45 @@
module "frontend_lambda_function" {
count = local.multiple_lambda_integration ? 1 : 0
source = "./modules/lambda"
global_tags = var.global_tags
tags = var.tags
environment = var.environment
integration_type = var.integration_type
resource_name_pattern = "${local.resource_name_pattern}-frontend"
env_vars = var.env_vars
logs_retention_in_days = var.lambda_logs_retention_in_days
should_write_logs = var.lambda_enable_logs
lambda_handler = "frontend.app"
timeout = var.lambda_function_timeout
memory_size = var.lambda_function_memory_size
publish = var.lambda_publish
secrets_arns = var.store_secret_in_secrets_manager ? module.secrets_manager[0].secrets_arns : []
store_secret_in_secrets_manager = var.store_secret_in_secrets_manager
lambda_source_code_filename = "frontend.zip"
role_arn = module.lambda_role.lambda_role_arn
count = local.multiple_lambda_integration ? 1 : 0
source = "./modules/lambda"
global_tags = var.global_tags
tags = var.tags
environment = var.environment
integration_type = var.integration_type
# Please do not change or replace the 'frontend' suffix since there a logic in the bot based in it
resource_name_pattern = "${local.resource_name_pattern}-frontend"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We currently have ${local.resource_name_pattern}-frontend in the locals.

Can we use the same variable to reduce the string concatenations?

env_vars = var.env_vars
logs_retention_in_days = var.lambda_logs_retention_in_days
should_write_logs = var.lambda_enable_logs
lambda_handler = local.frontend_lambda_handler
timeout = var.lambda_function_timeout
memory_size = var.lambda_function_memory_size
publish = var.lambda_publish
secrets_arns = var.store_secret_in_secrets_manager ? module.secrets_manager[0].secrets_arns : []
lambda_source_code_filename = "frontend.zip"
lambda_source_code_path = var.frontend_lambda_source_code_path
role_arn = module.lambda_role.lambda_role_arn
}

module "backend_lambda_function" {
count = local.multiple_lambda_integration ? 1 : 0
source = "./modules/lambda"
global_tags = var.global_tags
tags = var.tags
environment = var.environment
integration_type = var.integration_type
resource_name_pattern = "${local.resource_name_pattern}-backend"
env_vars = var.env_vars
logs_retention_in_days = var.lambda_logs_retention_in_days
should_write_logs = var.lambda_enable_logs
lambda_handler = "backend.app"
timeout = var.lambda_function_timeout
memory_size = var.lambda_function_memory_size
publish = var.lambda_publish
secrets_arns = var.store_secret_in_secrets_manager ? module.secrets_manager[0].secrets_arns : []
store_secret_in_secrets_manager = var.store_secret_in_secrets_manager
lambda_source_code_filename = "backend.zip"
role_arn = module.lambda_role.lambda_role_arn
count = local.multiple_lambda_integration ? 1 : 0
source = "./modules/lambda"
global_tags = var.global_tags
tags = var.tags
environment = var.environment
integration_type = var.integration_type
# Please do not change or replace the 'backend' suffix since there a logic in the bot based in it
resource_name_pattern = "${local.resource_name_pattern}-backend"
env_vars = var.env_vars
logs_retention_in_days = var.lambda_logs_retention_in_days
should_write_logs = var.lambda_enable_logs
lambda_handler = local.backend_lambda_handler
timeout = var.lambda_function_timeout
memory_size = var.lambda_function_memory_size
publish = var.lambda_publish
secrets_arns = var.store_secret_in_secrets_manager ? module.secrets_manager[0].secrets_arns : []
lambda_source_code_filename = "backend.zip"
lambda_source_code_path = var.backend_lambda_source_code_path
role_arn = module.lambda_role.lambda_role_arn
}

data "aws_iam_policy_document" "lambda_invoke_policy_document" {
Expand Down
12 changes: 10 additions & 2 deletions shared.tf
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
resource "random_string" "random_resource_name_suffix" {
length = 10
special = false
upper = false
}

module "api_gateway" {
source = "./modules/api_gateway"
global_tags = var.global_tags
tags = var.tags
environment = var.environment
integration_type = var.integration_type
resource_name_pattern = local.single_lambda_integration ? local.resource_name_pattern : "${local.resource_name_pattern}-frontend"
resource_name_pattern = local.resource_name_pattern
function_name = local.function_name
lambda_function_arn = local.api_triggered_function_arn
}

module "secrets_manager" {
count = var.store_secret_in_secrets_manager ? 1 : 0
integration_type = var.integration_type
source = "./modules/secrets_manager"
secrets_names = local.default_secrets_names[var.integration_type]
}

module "lambda_role" {
source = "./modules/role"
resource_name_pattern = local.single_lambda_integration ? local.resource_name_pattern : "${local.resource_name_pattern}-frontend"
role_name = local.function_name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the role name getting the function name?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this role and its policies were created explicitly for this lambda function, and with this function name there will be no doubt about why this role was created and who should use it

store_secret_in_secrets_manager = var.store_secret_in_secrets_manager
secrets_arns = var.store_secret_in_secrets_manager ? module.secrets_manager[0].secrets_arns : []
tags = var.tags
Expand Down
34 changes: 17 additions & 17 deletions single-lambda-integration.tf
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
module "lambda_function" {
count = local.single_lambda_integration ? 1 : 0
source = "./modules/lambda"
global_tags = var.global_tags
tags = var.tags
environment = var.environment
integration_type = var.integration_type
resource_name_pattern = local.resource_name_pattern
env_vars = var.env_vars
logs_retention_in_days = var.lambda_logs_retention_in_days
should_write_logs = var.lambda_enable_logs
timeout = var.lambda_function_timeout
memory_size = var.lambda_function_memory_size
publish = var.lambda_publish
secrets_arns = var.store_secret_in_secrets_manager ? module.secrets_manager[0].secrets_arns : []
store_secret_in_secrets_manager = var.store_secret_in_secrets_manager
lambda_source_code_filename = "app.zip"
role_arn = module.lambda_role.lambda_role_arn
count = local.single_lambda_integration ? 1 : 0
source = "./modules/lambda"
global_tags = var.global_tags
tags = var.tags
environment = var.environment
integration_type = var.integration_type
resource_name_pattern = local.resource_name_pattern
env_vars = var.env_vars
logs_retention_in_days = var.lambda_logs_retention_in_days
should_write_logs = var.lambda_enable_logs
timeout = var.lambda_function_timeout
memory_size = var.lambda_function_memory_size
publish = var.lambda_publish
secrets_arns = var.store_secret_in_secrets_manager ? module.secrets_manager[0].secrets_arns : []
lambda_source_code_filename = "app.zip"
lambda_source_code_path = var.lambda_source_code_path
role_arn = module.lambda_role.lambda_role_arn
}
Loading
Loading