Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
29 changes: 29 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: cd.yml
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Build with Gradle
run: ./gradlew clean build -x test

- name: Upload artifact to S3
uses: aws-actions/s3-sync@v2
with:
bucket: ${{ secrets.S3_BUCKET_NAME }}
source: build/libs/
destination: build/
region: ${{ secrets.AWS_REGION }}
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ src/main/resources/application-test.yml

getImageVersion.sh

/sql/
/sql/

### terraform ###
terraform/.terraform.lock.hcl
terraform/.terraform
29 changes: 29 additions & 0 deletions terraform/build_package.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -euo pipefail

PKG_DIR=lambda_package
ZIP_PATH=${PKG_DIR}/lambda.zip

rm -rf ${PKG_DIR}
mkdir -p ${PKG_DIR}/python

cat > Dockerfile.build <<'DOCKER'
FROM public.ecr.aws/lambda/python:3.12
WORKDIR /var/task
COPY handler.py ./
RUN pip install --target ./param_deps paramiko
DOCKER

docker build -t lambda-build -f Dockerfile.build .
container_id=$(docker create lambda-build)
docker cp ${container_id}:/var/task/param_deps ${PKG_DIR}/
docker rm ${container_id}

cp handler.py ${PKG_DIR}/
cd ${PKG_DIR}
(cd param_deps && cp -r . ../)
rm -rf param_deps
zip -r ${ZIP_PATH} .
cd ..

echo "โœ… Built ${ZIP_PATH}. Run: terraform init && terraform apply"
35 changes: 35 additions & 0 deletions terraform/lambda/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import boto3
import paramiko
import io
import os

s3 = boto3.client("s3")
ssm = boto3.client("ssm")

def lambda_handler(event, context):
try:
record = event["Records"][0]
bucket = record["s3"]["bucket"]["name"]
key = record["s3"]["object"]["key"]
except Exception as e:
return {"error": "Invalid event", "detail": str(e)}

param_name = os.environ.get("SSM_PARAM_NAME")
resp = ssm.get_parameter(Name=param_name, WithDecryption=True)
private_key = resp["Parameter"]["Value"]
pkey = paramiko.RSAKey.from_private_key(io.StringIO(private_key))

host = os.environ.get("HOME_HOST")
user = os.environ.get("HOME_USER")

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=host, username=user, pkey=pkey, timeout=10)

cmd = f"bash /home/{user}/deploy.sh {bucket} {key}"
stdin, stdout, stderr = ssh.exec_command(cmd)
out = stdout.read().decode()
err = stderr.read().decode()
ssh.close()

return {"stdout": out, "stderr": err}
97 changes: 97 additions & 0 deletions terraform/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
resource "aws_s3_bucket" "deploy_bucket" {
bucket = "${var.s3_bucket}-${random_id.bucket_suffix.hex}"
}

# Optionally create SSH key parameter in SSM
resource "aws_ssm_parameter" "ssh_key" {
count = length(trim(var.ssh_private_key)) > 0 ? 1 : 0
name = var.ssh_param_name
type = "SecureString"
value = var.ssh_private_key
overwrite = true
}

# IAM Role for Lambda
data "aws_iam_policy_document" "lambda_assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}

resource "aws_iam_role" "lambda_role" {
name = "lambda-cd-role-${random_id.bucket_suffix.hex}"
assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
}

resource "aws_iam_policy" "lambda_policy" {
name = "lambda-cd-policy-${random_id.bucket_suffix.hex}"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = ["ssm:GetParameter"],
Resource = ["arn:aws:ssm:${var.aws_region}:*:${var.ssh_param_name}"]
},
{
Effect = "Allow",
Action = ["s3:GetObject", "s3:GetObjectVersion"],
Resource = ["${aws_s3_bucket.deploy_bucket.arn}/*"]
},
{
Effect = "Allow",
Action = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
Resource = ["arn:aws:logs:*:*:*"]
}
]
})
}

resource "aws_iam_role_policy_attachment" "attach_lambda_policy" {
role = aws_iam_role.lambda_role.name
policy_arn = aws_iam_policy.lambda_policy.arn
}

# Lambda function
resource "aws_lambda_function" "deploy_lambda" {
filename = "${path.module}/lambda_package/lambda.zip"
function_name = "home-deploy-lambda-${random_id.bucket_suffix.hex}"
role = aws_iam_role.lambda_role.arn
handler = "handler.lambda_handler"
runtime = "python3.12"
source_code_hash = filebase64sha256("${path.module}/lambda_package/lambda.zip")
memory_size = var.lambda_memory
timeout = var.lambda_timeout

environment {
variables = {
SSM_PARAM_NAME = var.ssh_param_name
HOME_HOST = var.home_host
HOME_USER = var.home_user
}
}
}

resource "aws_lambda_permission" "s3_invoke" {
statement_id = "AllowS3Invoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.deploy_lambda.function_name
principal = "s3.amazonaws.com"
source_arn = aws_s3_bucket.deploy_bucket.arn
}

resource "aws_s3_bucket_notification" "bucket_notification" {
bucket = aws_s3_bucket.deploy_bucket.id

lambda_function {
lambda_function_arn = aws_lambda_function.deploy_lambda.arn
events = ["s3:ObjectCreated:Put"]
filter_prefix = "build/"
}

depends_on = [aws_lambda_permission.s3_invoke]
}
7 changes: 7 additions & 0 deletions terraform/output.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
output "s3_bucket" {
value = aws_s3_bucket.deploy_bucket.bucket
}

output "lambda_function_name" {
value = aws_lambda_function.deploy_lambda.function_name
}
13 changes: 13 additions & 0 deletions terraform/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.0"
}
}
}

provider "aws" {
region = var.aws_region
}
3 changes: 3 additions & 0 deletions terraform/random.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "random_id" "bucket_suffix" {
byte_length = 4
}
41 changes: 41 additions & 0 deletions terraform/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
variable "aws_region" {
type = string
default = "ap-northeast-2"
}

variable "s3_bucket" {
type = string
default = "home-deploy-bucket"
}

variable "lambda_memory" {
type = number
default = 256
}

variable "lambda_timeout" {
type = number
default = 30
}

variable "ssh_private_key" {
type = string
description = "SSH private key contents (recommended to use env var or Secrets Manager)."
sensitive = true
default = ""
}

variable "ssh_param_name" {
type = string
default = "/deploy/private-key"
}

variable "home_host" {
type = string
default = "myhome.example.com"
}

variable "home_user" {
type = string
default = "deploy"
}