Skip to content

Commit 0186619

Browse files
committed
Merge branch 'dev' into keycloak
# Conflicts: # code/grpc-gateway/main.go # code/kubernetes/scripts/deploy.sh
2 parents e5412dd + c2c497a commit 0186619

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+2250
-1718
lines changed

.github/workflows/dev.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Build and Test
2+
3+
on:
4+
push:
5+
branches:
6+
- dev
7+
pull_request:
8+
branches:
9+
- dev
10+
11+
jobs:
12+
test:
13+
name: Run Unit Tests
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v4
19+
20+
- name: Set up Go
21+
uses: actions/setup-go@v5
22+
with:
23+
go-version: '1.23'
24+
25+
- name: Run unit tests for thread-service
26+
working-directory: code/services/thread-service
27+
run: go test ./test/...
28+
29+
- name: Run unit tests for vote-service
30+
working-directory: code/services/vote-service
31+
run: go test ./test/...
32+
33+
- name: Run unit tests for search-service
34+
working-directory: code/services/search-service
35+
run: go test ./test/...
36+
37+
- name: Run unit tests for popular-service
38+
working-directory: code/services/popular-service
39+
run: go test ./test/...
40+
41+
- name: Run unit tests for community-service
42+
working-directory: code/services/community-service
43+
run: go test ./test/...
44+
45+
- name: Run unit tests for comment-service
46+
working-directory: code/services/comment-service
47+
run: go test ./test/...

.github/workflows/main.yml

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
name: Build and Deploy to GKE
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
env:
10+
PROJECT_ID: threadit-api
11+
CLUSTER_NAME: threadit-cluster
12+
ZONE: europe-west1-b
13+
GCS_KEY: gcs-key
14+
SERVICES: db community thread comment vote search popular
15+
16+
jobs:
17+
check-cluster:
18+
name: Check if GKE cluster exists
19+
runs-on: ubuntu-latest
20+
outputs:
21+
exists: ${{ steps.set-exists.outputs.exists }}
22+
23+
permissions:
24+
contents: read
25+
id-token: write
26+
27+
steps:
28+
- name: Authenticate to Google Cloud
29+
uses: google-github-actions/auth@v2
30+
with:
31+
credentials_json: ${{ secrets.GCP_SA_KEY }}
32+
33+
- name: Set up Google Cloud SDK
34+
uses: google-github-actions/setup-gcloud@v2
35+
with:
36+
project_id: ${{ env.PROJECT_ID }}
37+
38+
- id: set-exists
39+
run: |
40+
if gcloud container clusters describe $CLUSTER_NAME --zone $ZONE --project $PROJECT_ID; then
41+
echo "exists=true" >> $GITHUB_OUTPUT
42+
else
43+
echo "exists=false" >> $GITHUB_OUTPUT
44+
fi
45+
46+
build-publish-deploy:
47+
name: Build, Publish, and Deploy
48+
needs: check-cluster
49+
if: needs.check-cluster.outputs.exists == 'true'
50+
runs-on: ubuntu-latest
51+
environment: production
52+
53+
permissions:
54+
contents: read
55+
id-token: write
56+
57+
steps:
58+
- name: Checkout
59+
uses: actions/checkout@v4
60+
61+
- name: Authenticate to Google Cloud
62+
uses: google-github-actions/auth@v2
63+
with:
64+
credentials_json: ${{ secrets.GCP_SA_KEY }}
65+
66+
- name: Set up Google Cloud SDK
67+
uses: google-github-actions/setup-gcloud@v2
68+
with:
69+
project_id: ${{ env.PROJECT_ID }}
70+
71+
- name: Set up GKE credentials
72+
uses: google-github-actions/get-gke-credentials@v2
73+
with:
74+
project_id: ${{ env.PROJECT_ID }}
75+
cluster_name: ${{ env.CLUSTER_NAME }}
76+
location: ${{ env.ZONE }}
77+
78+
- name: Configure Docker for GCR
79+
run: |
80+
gcloud auth configure-docker --quiet
81+
82+
- name: Build and push images to GCR
83+
working-directory: code
84+
run: |
85+
for SERVICE in $SERVICES; do
86+
docker build -t gcr.io/$PROJECT_ID/${SERVICE}-service:latest -f services/${SERVICE}-service/Dockerfile .
87+
docker push gcr.io/$PROJECT_ID/${SERVICE}-service:latest
88+
done
89+
90+
docker build -t gcr.io/$PROJECT_ID/grpc-gateway:latest -f grpc-gateway/Dockerfile .
91+
docker push gcr.io/$PROJECT_ID/grpc-gateway:latest
92+
93+
- name: Deploy Traefik
94+
working-directory: code/kubernetes
95+
run: |
96+
helm repo add traefik https://traefik.github.io/charts
97+
helm repo update
98+
99+
helm upgrade --install traefik traefik/traefik -n $CLUSTER_NAME -f traefik/resources.yaml
100+
kubectl apply -n $CLUSTER_NAME -f traefik/ingress.yaml
101+
kubectl apply -n $CLUSTER_NAME -f traefik/hpa-config.yaml
102+
103+
- name: Create Kubernetes secrets
104+
run: |
105+
BUCKET_SECRET=$(gcloud secrets versions access latest --secret=$GCS_KEY)
106+
MONGO_USER=$(gcloud secrets versions access latest --secret="mongo-user")
107+
MONGO_PASS=$(gcloud secrets versions access latest --secret="mongo-pass")
108+
109+
kubectl create secret generic "bucket-secret" \
110+
--from-literal="$GCS_KEY.json=$BUCKET_SECRET" \
111+
-n $CLUSTER_NAME --dry-run=client -o yaml | kubectl apply -f -
112+
113+
kubectl create secret generic "mongo-secret" \
114+
--from-literal="MONGO_INITDB_ROOT_USERNAME=$MONGO_USER" \
115+
--from-literal="MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASS" \
116+
-n $CLUSTER_NAME --dry-run=client -o yaml | kubectl apply -f -
117+
118+
- name: Deploy configuration and Mongo
119+
working-directory: code/kubernetes
120+
run: |
121+
kubectl apply -n $CLUSTER_NAME -f config.yaml
122+
kubectl apply -n $CLUSTER_NAME -f mongo/
123+
124+
- name: Deploy services
125+
working-directory: code/kubernetes
126+
run: |
127+
for SERVICE in $SERVICES; do
128+
kubectl apply -n $CLUSTER_NAME -f services/${SERVICE}-service/
129+
done
130+
131+
kubectl apply -n $CLUSTER_NAME -f grpc-gateway/

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,16 @@ Reddit Clone REST API with Microservices Architecture Using gRPC
2424
## 🚀 Overview
2525

2626
*Threadit* is cloud native application that offers a set of services that provide users the ability to connect, share and engage in discussions within communities through a REST API.
27-
Its architecture will follow a microservices model with gRPC communication and be deployed on Google Cloud Platform (GCP) with Kubernetes.
27+
Its architecture follows a microservices model with gRPC communication and can be deployed on Google Cloud Platform (GCP) with Kubernetes.
28+
29+
## 🌐 API Description
30+
31+
The OpenAPI specification of all microservices can be found [here](./docs/openapi).
32+
We also made available a [Postman collection](https://grupo-8-0813.postman.co/workspace/f8d9d9ba-0d5a-42e6-851c-c4c77649f095/collection/34079154-e136d7b1-05c1-4e8f-bcf9-00d6b3f6b65c) for testing the REST API.
33+
34+
## 📦 Application Architecture
35+
36+
![application architecture](./docs/images/application-architecture.png)
2837

2938
## 🔍 Development Phases
3039

@@ -38,7 +47,3 @@ Its architecture will follow a microservices model with gRPC communication and b
3847
- [Phase 8 - Automation with CI/CD](./docs/phases/phase8.md)
3948
- [Phase 9 - System Testing](./docs/phases/phase9.md)
4049
- [Phase 10 - Final Report](./docs/phases/phase10.md)
41-
42-
## 📦 Application Architecture
43-
44-
![application architecture](./docs/images/architecture.png)

code/docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ services:
109109
DB_SERVICE_PORT: ${DB_SERVICE_PORT}
110110
COMMUNITY_SERVICE_HOST: community-service
111111
COMMUNITY_SERVICE_PORT: ${COMMUNITY_SERVICE_PORT}
112+
COMMENT_SERVICE_HOST: comment-service
113+
COMMENT_SERVICE_PORT: ${COMMENT_SERVICE_PORT}
112114
ports:
113115
- "${THREAD_SERVICE_PORT}:${THREAD_SERVICE_PORT}"
114116
networks:

code/gen/go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ require (
1212
)
1313

1414
require (
15-
golang.org/x/net v0.35.0 // indirect
16-
golang.org/x/sys v0.30.0 // indirect
17-
golang.org/x/text v0.22.0 // indirect
15+
golang.org/x/net v0.38.0 // indirect
16+
golang.org/x/sys v0.31.0 // indirect
17+
golang.org/x/text v0.23.0 // indirect
1818
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
1919
)

code/gen/go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,14 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC
2424
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
2525
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
2626
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
27+
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
28+
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
2729
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
2830
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
31+
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
2932
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
3033
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
34+
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
3135
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
3236
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
3337
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=

code/generate-openapi.sh

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ generate_openapi_for_service() {
1919
PROTO_DIR="$SCRIPT_DIR/proto"
2020
GOOGLE_API_DIR="$PROTO_DIR/google/api"
2121
PROTO_FILE="$PROTO_DIR/${SERVICE_NAME}.proto"
22-
OUT_DIR="$SCRIPT_DIR/../docs/openapi/gen"
22+
OUT_DIR="$SCRIPT_DIR/../docs/openapi"
2323

2424
# check if the .proto file exists
2525
if [ ! -f "$PROTO_FILE" ]; then
@@ -32,9 +32,6 @@ generate_openapi_for_service() {
3232
# create output directory if it doesn't exist
3333
mkdir -p "$OUT_DIR"
3434

35-
# clean up existing generated OpenAPI file
36-
rm -f "$OUT_DIR/${SERVICE_NAME}.swagger.json"
37-
3835
# run protoc with grpc-gateway's OpenAPI plugin (Swagger 2.0)
3936
protoc \
4037
--openapiv2_out="$OUT_DIR" \
@@ -46,8 +43,8 @@ generate_openapi_for_service() {
4643
# Convert Swagger 2.0 JSON to OpenAPI 3.1.0 YAML using swagger2openapi
4744
swagger2openapi -o "$OUT_DIR/${SERVICE_NAME}.yaml" "$OUT_DIR/${SERVICE_NAME}.swagger.json"
4845

49-
# Optionally, remove the JSON file if it's no longer needed
50-
rm "$OUT_DIR/${SERVICE_NAME}.swagger.json"
46+
# Remove the JSON file
47+
rm -f "$OUT_DIR/${SERVICE_NAME}.swagger.json"
5148

5249
echo "✅ OpenAPI spec generated at docs/openapi/gen/${SERVICE_NAME}.yaml"
5350
}

code/grpc-gateway/go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ require (
88
gen v0.0.0-00010101000000-000000000000
99
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3
1010
google.golang.org/grpc v1.71.0
11+
google.golang.org/protobuf v1.36.6
1112
)
1213

1314
require (
14-
golang.org/x/net v0.35.0 // indirect
15-
golang.org/x/sys v0.30.0 // indirect
16-
golang.org/x/text v0.22.0 // indirect
15+
golang.org/x/net v0.38.0 // indirect
16+
golang.org/x/sys v0.31.0 // indirect
17+
golang.org/x/text v0.23.0 // indirect
1718
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
1819
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
19-
google.golang.org/protobuf v1.36.6 // indirect
2020
)
2121

2222
replace gen => ../gen

code/grpc-gateway/go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce
2222
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
2323
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
2424
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
25-
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
26-
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
27-
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
28-
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
29-
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
30-
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
25+
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
26+
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
27+
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
28+
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
29+
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
30+
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
3131
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
3232
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
3333
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4=

code/grpc-gateway/main.go

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"log"
1414
"net/http"
1515
"os"
16+
gorun "runtime"
1617

1718
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
1819
"google.golang.org/grpc"
@@ -109,8 +110,20 @@ func handleHealthCheck(w http.ResponseWriter, r *http.Request) {
109110
}
110111

111112
func main() {
113+
// Set maximum number of CPUs to use
114+
gorun.GOMAXPROCS(gorun.NumCPU())
115+
116+
gwmux := runtime.NewServeMux()
112117
ctx := context.Background()
113-
mux := runtime.NewServeMux()
118+
119+
// gRPC dial options with message size configurations
120+
opts := []grpc.DialOption{
121+
grpc.WithTransportCredentials(insecure.NewCredentials()),
122+
grpc.WithDefaultCallOptions(
123+
grpc.MaxCallRecvMsgSize(1024*1024*500), // 500MB
124+
grpc.MaxCallSendMsgSize(1024*1024*500), // 500MB
125+
),
126+
}
114127

115128
// Initialize auth handler
116129
authHandler := middleware.NewAuthHandler(
@@ -126,15 +139,6 @@ func main() {
126139
// Register auth routes
127140
authHandler.RegisterRoutes(httpMux)
128141

129-
// gRPC dial options with message size configurations
130-
opts := []grpc.DialOption{
131-
grpc.WithTransportCredentials(insecure.NewCredentials()),
132-
grpc.WithDefaultCallOptions(
133-
grpc.MaxCallRecvMsgSize(1024*1024*500), // 500MB
134-
grpc.MaxCallSendMsgSize(1024*1024*500), // 500MB
135-
),
136-
}
137-
138142
// Register gRPC-Gateway routes with auth middleware
139143
httpMux.Handle("/api", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
140144
// Auth middleware for API routes
@@ -145,11 +149,11 @@ func main() {
145149
KeycloakURL: os.Getenv("KEYCLOAK_URL"),
146150
})
147151

148-
authMiddleware.Handler(mux).ServeHTTP(w, r)
152+
authMiddleware.Handler(gwmux).ServeHTTP(w, r)
149153
}))
150154

151155
// Register service handlers
152-
if err := registerServices(ctx, mux, opts); err != nil {
156+
if err := registerServices(ctx, gwmux, opts); err != nil {
153157
log.Fatalf("Failed to register services: %v", err)
154158
}
155159

@@ -193,9 +197,6 @@ func registerServices(ctx context.Context, mux *runtime.ServeMux, opts []grpc.Di
193197
return fmt.Errorf("failed to register vote service: %v", err)
194198
}
195199

196-
http.HandleFunc("/health", handleHealthCheck)
197-
http.Handle("/", mux)
198-
199200
// Register Search Service
200201
if err := searchpb.RegisterSearchServiceHandlerFromEndpoint(
201202
ctx, mux, getGrpcServerAddress("SEARCH_SERVICE_HOST", "SEARCH_SERVICE_PORT"), opts,
@@ -210,5 +211,8 @@ func registerServices(ctx context.Context, mux *runtime.ServeMux, opts []grpc.Di
210211
return fmt.Errorf("failed to register popular service: %v", err)
211212
}
212213

214+
http.HandleFunc("/health", handleHealthCheck)
215+
http.Handle("/", mux)
216+
213217
return nil
214218
}

0 commit comments

Comments
 (0)