Skip to content

Commit

Permalink
Add GitHub workflows (#4)
Browse files Browse the repository at this point in the history
* Add GitHub workflow to build and upload
* Simplify terraform deployments
* Remove terraform workspaces
* Combine tf_backend and init stacks and deploy tf backend to the same AWS account
* Add terraform jobs to GitHub actions workflow
* Set bot webhook in GitHub workflow
  • Loading branch information
bugfloyd authored Apr 28, 2024
1 parent 8fa9425 commit 3dcebb6
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 168 deletions.
67 changes: 67 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Build, Upload and Deploy

on:
push:
branches:
- main

jobs:
build-upload-deploy:
runs-on: ubuntu-latest

permissions:
id-token: write
contents: read
steps:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'

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

- name: Build
run: |
cd bot
GOARCH=amd64 GOOS=linux go build -o bootstrap main.go
- name: Zip
run: |
cd bot
zip lambda_function.zip bootstrap
- name: Configure AWS credentials to upload bundle
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ${{ vars.AWS_REGION }}
role-to-assume: ${{ vars.PIPELINE_EXEC_ROLE_ARN }}
role-session-name: GitHubActions
role-duration-seconds: 3600

- name: Upload to S3
run: aws s3 cp bot/lambda_function.zip s3://${{ vars.S3_LAMBDA_BUCKET }}/lambda_function.zip

- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_wrapper: false

- name: Terraform init
run: |
cd infra
terraform init \
-backend-config="region=${{ vars.AWS_REGION }}" \
-backend-config="bucket=${{ vars.TERRAFORM_STATE_BUCKET }}"
- name: Terraform apply
run: |
cd infra
terraform apply -auto-approve -var aws_region=${{ vars.AWS_REGION }} -var lambda_bucket=${{ vars.S3_LAMBDA_BUCKET }} -var bot_token=${{ secrets.BOT_TOKEN }}
- name: Set webhook URL
shell: bash
run: |
cd infra
WEBHOOK_URL=$(terraform output -raw webhook_url)
curl https://api.telegram.org/bot${{ secrets.BOT_TOKEN }}/setWebhook -F "url=$WEBHOOK_URL"
87 changes: 21 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,27 @@
# Anonymous Telegram Chat Bot [UNDER DEVELOPMENT]
# Anonymous Telegram Chat Bot

## Deployment
### Terraform Backend
First we create a S3 bucket to store Terraform state, a DynamoDB table to persist Terraform state lock and a S3 bucket to deploy Lambda function code bundles. The Terraform state for this init stack is being kept locally.
```shell
cd infra/tf_backend
terraform init # Run once
```

Create a `terraform.tfvars` file in `infra/init`:
### Deploy Init Resources
Now we create and deploy the resources needed for the main stack including a S3 bucket to store Terraform state, a DynamoDB table to persist Terraform state lock and a S3 bucket to deploy Lambda function code bundles. Go to `infra/init` directory and create a file named `terraform.tfvars`:
```hcl
aws_region = <AWS_REGION>
aws_profile = <AWS_PROFILE>
terraform_state_bucket = <S3_TERRAFORM_STATE_BUCKET_NAME>
```
**Note:** Here `<AWS_PROFILE>` and `<AWS_REGION>` are the profile (and/or account) and the region that we use to store and manage terraform state and lock.

Now run the plan command:
```shell
terraform plan -out tf_backend.tfplan
```

After planning for the changes, apply the changeset:
```shell
terraform apply "tf_backend.tfplan"
init_aws_region = "<AWS_REGION>"
terraform_state_bucket = "<S3_TERRAFORM_STATE_BUCKET_NAME>"
init_lambda_bucket = "<S3_LAMBDA_CODE_BUCKET>"
github_repo = "<GITHUB_PROFILE/REPO_NAME>"
```

### Deploy Initial Resources
Now we create and deploy the resources needed for the main stack. Go to `infra/init` directory and create a file named `backend_config.hcl`:
```hcl
profile = <AWS_PROFILE>
bucket = <S3_TERRAFORM_STATE_BUCKET_NAME>
region = <AWS_REGION>
```
**Note:** Here `<AWS_PROFILE>` and `<AWS_REGION>` are the profile (and/or account) and the region that we use to store and manage terraform state and lock.

Create a file named `terraform.tfvars`:
```hcl
init_aws_region = <AWS_REGION>
init_aws_profile = {
development = <AWS_DEV_PROFILE>
production = <AWS_PROD_PROFILE>
}
init_lambda_bucket = <S3_LAMBDA_CODE_BUCKET>
github_repo = <GITHUB_PROFILE?REPO_NAME>
```
**Note:** Here `<AWS_DEV_PROFILE>`, `<AWS_PROD_PROFILE>` and `<AWS_REGION>` are the profile (and/or account) and the region that we use to deploy the main resources.
**Note:** The Terraform state for this init stack is being kept locally.

Now plan and apply the changeset:
```shell
terraform init -backend-config backend_config.hcl # Run once
terraform plan -out init.tfplan
terraform apply "init.tfplan"
terraform init # Run once
AWS_PROFILE=<AWS_PROFILE> terraform plan -out init.tfplan
AWS_PROFILE=<AWS_PROFILE> terraform apply "init.tfplan"
```

### Build and Bundle
```shell
cd bot

``
GOARCH=amd64 GOOS=linux go build -o bootstrap main.go
```
**Note:** In order to use the binary as the Lambda handler, it should be named `bootstrap`.
Expand All @@ -72,46 +36,37 @@ Upload the build zip bundle to S3:
```shell
aws s3 cp lambda_function.zip s3://<S3_LAMBDA_CODE_BUCKET>/lambda_function.zip --profile <AWS_PROFILE>
```
**Note:** Here `<AWS_PROFILE>` is the profile (and/or account) that we use to deploy the main resources.

### Deploy Main AWS Resources
#### Add Terraform Variables File
Create a file named `infra/terraform.tfvars`:
```hcl
aws_region = <AWS_REGION>
aws_profile = {
development = <AWS_DEV_PROFILE>
production = <AWS_PROD_PROFILE>
}
lambda_bucket = <S3_LAMBDA_CODE_BUCKET>
bot_token = <TELEGRAM_BOT_TOKEN>
aws_region = "<AWS_REGION>"
lambda_bucket = "<S3_LAMBDA_CODE_BUCKET>"
bot_token = "<TELEGRAM_BOT_TOKEN>"
```
**Note:** Here `<AWS_DEV_PROFILE>`, `<AWS_PROD_PROFILE>` and `<AWS_REGION>` are the profile (and/or account) and the region that we use to deploy the main resources.

#### Initialize Terraform
Create a Terraform backend configuration file named `infra/backend_config.hcl`:
```hcl
region = <AWS_REGION>
profile = <AWS_PROFILE>
bucket = <S3_TERRAFORM_STATE_BUCKET_NAME>
region = "<AWS_REGION>"
bucket = "<S3_TERRAFORM_STATE_BUCKET_NAME>"
```
**Note:** Here `<AWS_PROFILE>` and `<AWS_REGION>` are the profile (and/or account) and the region that we use to store and manage terraform state and lock.

Initialize the main Terraform stack:
```shell
cd infra
terraform init -backend-config backend_config.hcl # Run once
terraform workspace new production # Run once - Repeat it for other workspaces
terraform workspace select production

AWS_PROFILE=<AWS_PROFILE> init -backend-config backend_config.hcl # Run once
```

#### Deploy
Use `terraform plan` to create a changeset and run `terraform apply` command to deploy the changeset on AWS:
```shell
cd infra

terraform plan -out main.tfplan
terraform apply "main.tfplan"
AWS_PROFILE=<AWS_PROFILE> terraform plan -out main.tfplan
AWS_PROFILE=<AWS_PROFILE> terraform apply "main.tfplan"
```

### Register Bot Webhook
Expand Down
2 changes: 1 addition & 1 deletion bot/go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/bugfloyd/anonymous-telegram-bot

go 1.21.9
go 1.21

require (
github.com/PaulSonOfLars/gotgbot/v2 v2.0.0-rc.25
Expand Down
7 changes: 0 additions & 7 deletions infra/init/backend.tf

This file was deleted.

13 changes: 2 additions & 11 deletions infra/init/main.tf
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
locals {
workspace_prefixes = {
default = "dev"
development = "dev"
production = "prod"
}
}

provider "aws" {
region = var.init_aws_region
profile = var.init_aws_profile[terraform.workspace]
region = var.init_aws_region
}

# S3 bucket to store lambda function codes
resource "aws_s3_bucket" "lambda_bucket" {
bucket = "${local.workspace_prefixes[terraform.workspace]}.${var.init_lambda_bucket}"
bucket = var.init_lambda_bucket
}
7 changes: 5 additions & 2 deletions infra/init/pipeline_exec.tf
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@ resource "aws_iam_role" "github_actions" {
})
}

# Attach policies to the IAM role as needed
# Example: attaching a read-only policy
resource "aws_iam_role_policy_attachment" "administrator_access" {
role = aws_iam_role.github_actions.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

resource "aws_iam_role_policy_attachment" "terraform_backend_access" {
role = aws_iam_role.github_actions.name
policy_arn = aws_iam_policy.terraform_backend_access_policy.arn
}

output "pipeline_execution_role_arn" {
value = aws_iam_role.github_actions.arn
}
52 changes: 52 additions & 0 deletions infra/init/terraform_backend.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
resource "aws_s3_bucket" "terraform_state" {
bucket = var.terraform_state_bucket
}

resource "aws_dynamodb_table" "terraform_lock" {
name = var.terraform_state_lock_dynamodb_table
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"

attribute {
name = "LockID"
type = "S"
}

tags = {
Name = "TerraformStateLocking"
}
}

resource "aws_iam_policy" "terraform_backend_access_policy" {
name = "TerraformBackendAccessPolicy"
path = "/"
description = "Policy for allowing access to terraform backend S3 bucket and DynamoDB state lock table"

policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "${aws_s3_bucket.terraform_state.arn}"
},
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "${aws_s3_bucket.terraform_state.arn}/*"
},
{
"Effect": "Allow",
"Action": [
"dynamodb:DescribeTable",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem"
],
"Resource": "${aws_dynamodb_table.terraform_lock.arn}"
}
]
}
EOF
}
19 changes: 11 additions & 8 deletions infra/init/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ variable "init_aws_region" {
type = string
}

variable "init_aws_profile" {
description = "AWS profile to be used"
type = map(string)
default = {
development = "dev"
production = "prod"
}
variable "terraform_state_bucket" {
description = "The AWS S3 bucket used to store terraform state"
type = string
}

variable "terraform_state_lock_dynamodb_table" {
description = "The AWS DynamoDB table name to be used for terraform state lock"
type = string
default = "anonymous-chatbot-terraform-state-lock"
}

variable "init_lambda_bucket" {
Expand All @@ -20,4 +22,5 @@ variable "init_lambda_bucket" {
variable "github_repo" {
description = "Github repo to be used to allow github actions to have access to the AWS account via OIDC in this format: <GITHUB_OWNER>/<GITHUB_REPO>"
type = string
}
}

28 changes: 8 additions & 20 deletions infra/main.tf
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
locals {
workspace_prefixes = {
default = "dev"
development = "dev"
production = "prod"
}
}

provider "aws" {
region = var.aws_region
profile = var.aws_profile[terraform.workspace]
}

# Lambda
resource "aws_lambda_function" "anonymous_bot" {
function_name = "AnonymousBot"

s3_bucket = "${local.workspace_prefixes[terraform.workspace]}.${var.lambda_bucket}"
s3_key = "lambda_function.zip"

handler = "main"
runtime = "provided.al2023"

role = aws_iam_role.lambda_exec_role.arn

function_name = "AnonymousBot"
s3_bucket = var.lambda_bucket
s3_key = "lambda_function.zip"
handler = "main"
runtime = "provided.al2023"
role = aws_iam_role.lambda_exec_role.arn
source_code_hash = filebase64sha256(var.zip_bundle_path)

environment {
Expand Down Expand Up @@ -93,7 +80,8 @@ resource "aws_apigatewayv2_stage" "default_stage" {
}

output "webhook_url" {
value = "${aws_apigatewayv2_stage.default_stage.invoke_url}anonymous-bot"
value = "${aws_apigatewayv2_stage.default_stage.invoke_url}anonymous-bot"
sensitive = true
}

resource "aws_lambda_permission" "api_gw_lambda" {
Expand Down
Loading

0 comments on commit 3dcebb6

Please sign in to comment.