Skip to content

Commit

Permalink
Automate EC2 instance start/stop
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Tai committed Oct 20, 2023
1 parent 4ee3e5f commit d84864e
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
.terraform/
*.tfstate
*.tfstate*
*.lock.hcl
secret.tfvars
91 changes: 91 additions & 0 deletions data.tf
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,94 @@ data "aws_ami" "this" {

most_recent = true
}

data "aws_iam_policy_document" "lambda" {
version = "2012-10-17"

statement {
effect = "Allow"

principals {
type = "Service"

identifiers = [
"lambda.amazonaws.com",
]
}

actions = [
"sts:AssumeRole",
]
}
}

data "aws_iam_policy_document" "instance" {
version = "2012-10-17"

statement {
effect = "Allow"

resources = [
"*",
]

actions = [
"ec2:Start*",
"ec2:Stop*",
]
}
}

data "aws_iam_policy_document" "cloudwatch" {
version = "2012-10-17"

statement {
effect = "Allow"

resources = [
"arn:aws:logs:*:*:*",
]

actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
]
}
}

data "archive_file" "start_instance" {
type = "zip"
output_path = "${path.module}/start_instance.zip"
source_content_filename = "${path.module}/start_instance.js"
source_content = <<EOF
const AWS = require('aws-sdk');
const ec2 = new AWS.EC2({
region: '${var.aws_region}',
});
exports.handler = async (event, context, callback) => await ec2.startInstances({
InstanceIds: [
'${aws_instance.this.id}',
],
}).promise();
EOF
}

data "archive_file" "stop_instance" {
type = "zip"
output_path = "${path.module}/stop_instance.zip"
source_content_filename = "${path.module}/stop_instance.js"
source_content = <<EOF
const AWS = require('aws-sdk');
const ec2 = new AWS.EC2({
region: '${var.aws_region}',
});
exports.handler = async (event, context, callback) => await ec2.stopInstances({
InstanceIds: [
'${aws_instance.this.id}',
],
}).promise();
EOF
}
130 changes: 130 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,136 @@ resource "aws_instance" "this" {
}
}

resource "aws_iam_role" "lambda" {
name = "tailscale"
assume_role_policy = data.aws_iam_policy_document.lambda.json
}

resource "aws_iam_policy" "lambda" {
name = "tailscale-lambda"
path = "/"
policy = data.aws_iam_policy_document.instance.json
}

resource "aws_iam_policy" "cloudwatch" {
name = "tailscale-cloudwatch"
path = "/"
policy = data.aws_iam_policy_document.cloudwatch.json
}

resource "aws_iam_role_policy_attachment" "lambda" {
role = aws_iam_role.lambda.name
policy_arn = aws_iam_policy.lambda.arn
}

resource "aws_iam_role_policy_attachment" "cloudwatch" {
role = aws_iam_role.lambda.name
policy_arn = aws_iam_policy.cloudwatch.arn
}

resource "aws_cloudwatch_log_group" "start_instance" {
name = "/aws/lambda/${var.server_hostname}-${var.aws_region}-start-instance"
retention_in_days = var.log_retention
}

resource "aws_cloudwatch_log_group" "stop_instance" {
name = "/aws/lambda/${var.server_hostname}-${var.aws_region}-stop-instance"
retention_in_days = var.log_retention
}

resource "aws_cloudwatch_event_rule" "start_instance" {
name = "${var.server_hostname}-${var.aws_region}-start-instance"
schedule_expression = var.server_start_expression
}

resource "aws_cloudwatch_event_rule" "stop_instance" {
name = "${var.server_hostname}-${var.aws_region}-stop-instance"
schedule_expression = var.server_stop_expression
}

resource "aws_s3_bucket" "this" {
bucket = "${var.server_hostname}-${var.aws_region}"
force_destroy = true
}

resource "aws_s3_object" "start_instance" {
bucket = aws_s3_bucket.this.id
key = "start_instance.zip"
source = data.archive_file.start_instance.output_path
}

resource "aws_s3_object" "stop_instance" {
bucket = aws_s3_bucket.this.id
key = "stop_instance.zip"
source = data.archive_file.stop_instance.output_path
}

resource "aws_lambda_function" "start_instance" {
function_name = "${var.server_hostname}-${var.aws_region}-start-instance"
role = aws_iam_role.lambda.arn
s3_bucket = aws_s3_bucket.this.id
s3_key = aws_s3_object.start_instance.id
handler = "start_instance.handler"
runtime = "nodejs16.x"
memory_size = 128
timeout = 60

architectures = [
"arm64",
]

depends_on = [
aws_cloudwatch_log_group.start_instance,
]
}

resource "aws_lambda_function" "stop_instance" {
function_name = "${var.server_hostname}-${var.aws_region}-stop-instance"
role = aws_iam_role.lambda.arn
s3_bucket = aws_s3_bucket.this.id
s3_key = aws_s3_object.stop_instance.id
handler = "stop_instance.handler"
runtime = "nodejs16.x"
memory_size = 128
timeout = 60

architectures = [
"arm64",
]

depends_on = [
aws_cloudwatch_log_group.stop_instance,
]
}

resource "aws_cloudwatch_event_target" "start_instance" {
rule = aws_cloudwatch_event_rule.start_instance.name
target_id = aws_cloudwatch_event_rule.start_instance.name
arn = aws_lambda_function.start_instance.arn
}

resource "aws_cloudwatch_event_target" "stop_instance" {
rule = aws_cloudwatch_event_rule.stop_instance.name
target_id = aws_cloudwatch_event_rule.stop_instance.name
arn = aws_lambda_function.stop_instance.arn
}

resource "aws_lambda_permission" "start_instance" {
statement_id = "AllowExecutionFromCloudWatch"
principal = "events.amazonaws.com"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.start_instance.function_name
source_arn = aws_cloudwatch_event_rule.start_instance.arn
}

resource "aws_lambda_permission" "stop_instance" {
statement_id = "AllowExecutionFromCloudWatch"
principal = "events.amazonaws.com"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.stop_instance.function_name
source_arn = aws_cloudwatch_event_rule.stop_instance.arn
}

resource "tailscale_tailnet_key" "this" {
reusable = true
preauthorized = true
Expand Down
18 changes: 18 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ variable "server_hostname" {
default = "vpn"
}

variable "server_start_expression" {
description = "Server start schedule expression"
type = string
default = "cron(0 10 * * ? *)"
}

variable "server_stop_expression" {
description = "Server stop schedule expression"
type = string
default = "cron(0 1 * * ? *)"
}

variable "log_retention" {
description = "CloudWatch log retention"
type = number
default = 14
}

variable "tailscale_api_key" {
description = "Tailscale API access token"
type = string
Expand Down

0 comments on commit d84864e

Please sign in to comment.