Skip to content
Open
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
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
DB_URL=jdbc:mysql://<rds-endpoint>:3306/test
DB_USERNAME=root
DB_PASSWORD=example
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_REGION=us-east-1
ECR_REGISTRY=123456789012.dkr.ecr.us-east-1.amazonaws.com

25 changes: 25 additions & 0 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# GitHub Workflows

This repository provides three GitHub Actions workflows:

- **build.yml** – Builds the project and pushes a Docker image to Amazon ECR whenever changes are pushed to the `main` branch.
- **deploy.yml** – Manually triggered deployment of the Kubernetes manifests using a provided kubeconfig.
- **infra.yml** – Manually triggered workflow that creates or removes AWS infrastructure via CloudFormation.

## Required Secrets

Configure the following secrets in your repository settings so the workflows can access AWS and your Kubernetes cluster:

| Secret Name | Used By | Description |
|-------------|---------|-------------|
| `AWS_ACCESS_KEY_ID` | build.yml, infra.yml | IAM user access key for AWS operations |
| `AWS_SECRET_ACCESS_KEY` | build.yml, infra.yml | Secret key associated with `AWS_ACCESS_KEY_ID` |
| `AWS_REGION` | build.yml, infra.yml | AWS region for ECR and CloudFormation |
| `ECR_REGISTRY` | build.yml | ECR registry URL (e.g. `123456789012.dkr.ecr.us-east-1.amazonaws.com`) |
| `KUBECONFIG` | deploy.yml | Base64‑encoded kubeconfig for your Kubernetes cluster |
| `DB_PASSWORD` | infra.yml | Database master password used when creating the RDS instance |
| `HOSTED_ZONE` | infra.yml (optional) | Domain name for Route53 records (defaults to `example.com.`) |

Create these secrets in **Settings → Secrets and variables → Actions**.

The `infra.yml` workflow reads `deploy/cloudformation/config.json` if present in the repository to determine whether to create or delete the stack and whether termination protection should be enabled.
27 changes: 27 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Build and Push
on:
push:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 17
- name: Build jar
run: ./mvnw -B package -DskipTests
- name: Log in to ECR
uses: aws-actions/amazon-ecr-login@v1
with:
region: ${{ secrets.AWS_REGION }}
- name: Build Docker image
run: |
docker build -t ${{ secrets.ECR_REGISTRY }}/spring-oauth-example:latest .
docker push ${{ secrets.ECR_REGISTRY }}/spring-oauth-example:latest


23 changes: 23 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Deploy to Kubernetes
on:
workflow_dispatch:

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up kubectl
uses: azure/setup-kubectl@v3
with:
version: 'v1.27.1'
- name: Configure kubeconfig
run: echo "${{ secrets.KUBECONFIG }}" > kubeconfig && chmod 600 kubeconfig
- name: Deploy
env:
KUBECONFIG: kubeconfig
run: |
cd deploy/kubernetes
./deploy.sh


22 changes: 22 additions & 0 deletions .github/workflows/infra.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Manage Infrastructure
on:
workflow_dispatch:

jobs:
deploy-infra:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Deploy CloudFormation
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
HOSTED_ZONE: ${{ secrets.HOSTED_ZONE }}
run: |
cd deploy/cloudformation
./deploy.sh "$DB_PASSWORD" "${HOSTED_ZONE:-example.com.}"
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM maven:3.8.6-eclipse-temurin-17 AS build
WORKDIR /workspace/app
COPY pom.xml .
COPY src src
RUN mvn package -DskipTests

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /workspace/app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]
68 changes: 66 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
## #. Run using maven wrapper/executable or via your preferred IDE

- __Note__:
- To run: Change datasource properties @See application.yml -> ```spring: datasource: ``` (create database "test")
- Configure datasource using environment variables `DB_URL`, `DB_USERNAME` and `DB_PASSWORD`. Example:
```bash
export DB_URL=jdbc:mysql://<rds-endpoint>:3306/test
export DB_USERNAME=root
export DB_PASSWORD=example
```
- To view flow: Change logging level as needed @See application.yml -> ```logging:```

```cmd
Expand Down Expand Up @@ -163,4 +168,63 @@ For all OAuth2 providers, On Successful OAuth2User Authentication Request proces
- For Multiple properties usage from `AppProperties appProperties`, values are initialized in `@PostConstruct` to avoid code clutter
- Uses Lombok `@Getter @Setter` for getter/setter generation
- By Default, config classes uses Field Injection `@Autowired`, other class uses constructor injection
- Exception thrown are handled from `@ControllerAdvice`
- Exception thrown are handled from `@ControllerAdvice`

## Deployment

- CloudFormation template for a minimal RDS MySQL instance is available under `deploy/cloudformation/rds.yml`.
- Kubernetes manifests for running the application are located in `deploy/kubernetes` and reference secrets containing the datasource credentials and AWS access keys.

### Preparing the environment

1. Copy `.env.example` to `.env` and adjust credentials. The same file is used for Kubernetes secret creation and docker-compose.
2. Edit `deploy/cloudformation/config.json` to adjust the action, protection and service settings as needed.
3. If using GitHub Actions, define the secrets listed in `.github/workflows/README.md` (AWS credentials, `ECR_REGISTRY`, `KUBECONFIG`, etc.). The workflow in `.github/workflows/build.yml` builds and pushes the container image automatically.
To run locally, build the Docker image and push it to your ECR registry:

```bash
aws ecr get-login-password --region $AWS_REGION | \
docker login --username AWS --password-stdin $ECR_REGISTRY
docker build -t $ECR_REGISTRY/spring-oauth-example:latest .
docker push $ECR_REGISTRY/spring-oauth-example:latest
```

### Local development

A docker-compose setup is available under `deploy/docker-compose` which starts MySQL, LocalStack and the application container. LocalStack emulates AWS services so the application can be tested without a real AWS account. Ensure you have the `.env` file in the project root then run:

```bash
cd deploy/docker-compose
docker compose up --build
```

The application will be available on `http://localhost:8080` with MySQL exposed on port `3306` and LocalStack on `4566`.

### Deploying to AWS

1. Provision infrastructure using the helper script which applies `deploy/cloudformation/infra.yml`. The `config.json` file controls whether stacks are created or removed and which services are enabled:

```bash
cd deploy/cloudformation
./deploy.sh yourdbpassword example.com.
```

You can also trigger the `Manage Infrastructure` workflow in GitHub Actions (`infra.yml`) which runs the same script in CI using the secrets you configured.

Note the database endpoint from the stack outputs and update `DB_URL` in your `.env` file.

2. Create the Kubernetes secret with the environment variables:

```bash
cd deploy/kubernetes
./create-secret.sh
```

3. Deploy the application:

```bash
cd deploy/kubernetes
./deploy.sh
```

Alternatively trigger the workflows defined in `.github/workflows`. Use `deploy.yml` for Kubernetes deployments and `infra.yml` for managing AWS resources. Redeploy by rebuilding the Docker image and running the workflows again as needed.
11 changes: 11 additions & 0 deletions deploy/cloudformation/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"action": "start",
"protect": true,
"stack_name": "spring-oauth-example",
"services": {
"route53": {"enabled": true, "hosted_zone": "example.com."},
"rds": {"enabled": true, "db_instance_class": "db.t3.micro"},
"cache": {"enabled": true, "node_type": "cache.t2.micro"},
"s3": {"enabled": true}
}
}
86 changes: 86 additions & 0 deletions deploy/cloudformation/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/bin/sh
# Deploy or remove the CloudFormation stack that provides AWS resources.
# Behaviour is controlled via `config.json`. Requires AWS CLI credentials.
set -e

command -v jq >/dev/null 2>&1 || { echo >&2 "jq is required but not installed."; exit 1; }

deploy_stack() {
echo "Deploying stack $STACK_NAME"
PARAMS="DBPassword=$DB_PASSWORD HostedZoneName=$HOSTED_ZONE"
PARAMS="$PARAMS EnableS3=$S3_ENABLED EnableRDS=$RDS_ENABLED EnableCache=$CACHE_ENABLED EnableDNS=$DNS_ENABLED"
PARAMS="$PARAMS DBInstanceClass=$DB_CLASS CacheNodeType=$CACHE_NODE"
aws cloudformation deploy \
--stack-name "$STACK_NAME" \
--template-file "$TEMPLATE" \
--capabilities CAPABILITY_NAMED_IAM \
--parameter-overrides $PARAMS
if [ "$PROTECT" = "true" ]; then
aws cloudformation update-termination-protection \
--stack-name "$STACK_NAME" --enable-termination-protection
fi
aws cloudformation describe-stacks --stack-name "$STACK_NAME" --query 'Stacks[0].Outputs'
}

remove_stack() {
if [ "$PROTECT" = "true" ]; then
echo "Stack is protected. Set protect=false in $CONFIG_FILE to allow deletion." >&2
return
fi
aws cloudformation update-termination-protection \
--stack-name "$STACK_NAME" --no-enable-termination-protection || true
aws cloudformation delete-stack --stack-name "$STACK_NAME"
echo "Waiting for stack deletion..."
aws cloudformation wait stack-delete-complete --stack-name "$STACK_NAME"
}

CONFIG_FILE="config.json"
STACK_NAME="spring-oauth-example"
TEMPLATE=infra.yml

ACTION="start"
PROTECT=true
HOSTED_ZONE="example.com."
S3_ENABLED="true"
RDS_ENABLED="true"
CACHE_ENABLED="true"
DNS_ENABLED="true"
DB_CLASS="db.t3.micro"
CACHE_NODE="cache.t2.micro"

# Read configuration from JSON if present
if [ -f "$CONFIG_FILE" ]; then
ACTION="$(jq -r '.action // "start"' "$CONFIG_FILE")"
PROTECT="$(jq -r '.protect // true' "$CONFIG_FILE")"
STACK_NAME="$(jq -r '.stack_name // "spring-oauth-example"' "$CONFIG_FILE")"
HOSTED_ZONE="$(jq -r '.services.route53.hosted_zone // "example.com."' "$CONFIG_FILE")"
DNS_ENABLED="$(jq -r '.services.route53.enabled // true' "$CONFIG_FILE")"
RDS_ENABLED="$(jq -r '.services.rds.enabled // true' "$CONFIG_FILE")"
S3_ENABLED="$(jq -r '.services.s3.enabled // true' "$CONFIG_FILE")"
CACHE_ENABLED="$(jq -r '.services.cache.enabled // true' "$CONFIG_FILE")"
DB_CLASS="$(jq -r '.services.rds.db_instance_class // "db.t3.micro"' "$CONFIG_FILE")"
CACHE_NODE="$(jq -r '.services.cache.node_type // "cache.t2.micro"' "$CONFIG_FILE")"
fi

if [ -z "$1" ] && [ -z "$DB_PASSWORD" ]; then
echo "Usage: $0 <db-password> [hosted-zone]" >&2
exit 1
fi

DB_PASSWORD="${1:-$DB_PASSWORD}"
HOSTED_ZONE="${2:-$HOSTED_ZONE}"

case "$ACTION" in
start)
deploy_stack
;;
stop)
remove_stack
;;
*)
echo "Unknown ACTION: $ACTION" >&2
exit 1
;;
esac


Loading