Merge pull request #69 from HideyoshiSolutions/chore/better-ci

feat: adds deploy job to project
This commit is contained in:
2025-11-07 21:32:52 -03:00
committed by GitHub
10 changed files with 352 additions and 2 deletions

View File

@@ -38,7 +38,7 @@ jobs:
docker:
needs: [ build, run-tests ]
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
if: github.ref_name == 'main' || github.ref_name == 'develop'
runs-on: ubuntu-latest
permissions:
contents: read
@@ -90,3 +90,76 @@ jobs:
${{ env.IMAGE_SHA }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: [docker]
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref_name == 'main')
environment:
name: ${{ github.ref_name == 'main' && 'production' || 'dev' }}
url: https://${{ vars.KUBE_DOMAIN }}
env:
# Kubernetes Specific
KUBE_NAMESPACE: ${{ vars.KUBE_NAMESPACE }}
KUBE_API_DOMAIN: ${{ vars.KUBE_API_DOMAIN }}
WORKER_NODE_LABEL: ${{ vars.WORKER_NODE_LABEL }}
# Application Specific
FRONTEND_PATH: ${{ vars.FRONTEND_PATH }}
steps:
- uses: actions/checkout@v4
- uses: azure/setup-kubectl@v4
- name: Set Up Kubeconfig
uses: azure/k8s-set-context@v4
with:
kubeconfig: ${{ secrets.PORTFOLIO_KUBECONFIG }}
- name: Prepare Image Tag
run: |
OWNER=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]')
REPO=$(echo "${GITHUB_REPOSITORY#*/}" | tr '[:upper:]' '[:lower:]')
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
IMAGE_BASE="ghcr.io/${OWNER}/${REPO}"
IMAGE_TAG="sha-${SHORT_SHA}"
echo "IMAGE_BASE=${IMAGE_BASE}" >> $GITHUB_ENV
echo "IMAGE_TAG=${IMAGE_TAG}" >> $GITHUB_ENV
- name: Import SOPS GPG Key
run: |
echo "${{ secrets.PORTFOLIO_GPG_PRIVATE_KEY }}" | gpg --import
- name: Install SOPS
run: |
curl -L https://github.com/mozilla/sops/releases/download/v3.9.1/sops-v3.9.1.linux.amd64 -o /usr/local/bin/sops
chmod +x /usr/local/bin/sops
- name: Decrypt SOPS Secrets Test
run: |
cd .k8s
sops -d secrets.enc.yml secrets.yml
- name: Apply Kubernetes Manifests - Configuration
run: cat .k8s/config.template.yml | envsubst | kubectl apply -f -
- name: Apply Kubernetes Manifests - Secrets
run: cat .k8s/secrets.yml | envsubst | kubectl apply -f -
- name: Apply Kubernetes Manifests - Postgres Cluster
run: cat .k8s/postgres-cluster.template.yml | envsubst | kubectl apply -f -
- name: Apply Kubernetes Manifests - Redis Cluster
run: cat .k8s/redis.template.yml | envsubst | kubectl apply -f -
- name: Apply Kubernetes Manifests - Deployment
run: |
cat .k8s/deployment.template.yml | envsubst | kubectl apply -f -
cat .k8s/deployment.yaml | envsubst | kubectl rollout status deployment/frontend-deployment -n ${KUBE_NAMESPACE} --timeout=120s
- name: Apply Kubernetes Manifests - Service
run: cat .k8s/service.template.yml | envsubst | kubectl apply -f -
- name: Apply Kubernetes Manifests - Ingress
run: cat .k8s/ingress.template.yml | envsubst | kubectl apply -f -

13
.gitignore vendored
View File

@@ -47,4 +47,15 @@ dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
.mvn/wrapper/maven-wrapper.jar
### Certs ###
*.pem
### Secrets and Envs ###
.env*
*.secret
.k8s/*.yml
!.k8s/*.template.yml
!.k8s/*.enc.yml

5
.k8s/.sops.yaml Normal file
View File

@@ -0,0 +1,5 @@
creation_rules:
- path_regex: ^secrets(\.enc)?\.yml$
encrypted_regex: '^(data|stringData)$'
pgp: >-
8C8D94A7639C87559B0F2F64B7E1F62F69798EB6

9
.k8s/config.template.yml Normal file
View File

@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: backend-config
namespace: ${KUBE_NAMESPACE}
data:
FRONTEND_PATH: ${FRONTEND_PATH}
STORAGE_SERVICE_INTERNAL_URL: storage-service
STORAGE_SERVICE_PORT: "8000"

View File

@@ -0,0 +1,99 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-deployment
namespace: ${KUBE_NAMESPACE}
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
nodeSelector:
${WORKER_NODE_LABEL}
imagePullSecrets:
- name: ghcr-secret
containers:
- name: backend
image: ${IMAGE_BASE}:${IMAGE_TAG}
imagePullPolicy: Always
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "1000m"
ports:
- containerPort: 8070
readinessProbe:
httpGet:
path: /health
port: 8070
initialDelaySeconds: 60
livenessProbe:
httpGet:
path: /health
port: 8070
initialDelaySeconds: 60
envFrom:
- configMapRef:
name: backend-config
- secretRef:
name: backend-secret
env:
- name: PORT
value: "8070"
- name: REDIS_URL
value: backend-redis-service
- name: REDIS_PORT
value: "6379"
- name: POSTGRES_URL
valueFrom:
secretKeyRef:
name: backend-postgres-cluster-app
key: host
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: backend-postgres-cluster-app
key: dbname
- name: DATABASE_URL
value: "postgresql://$(POSTGRES_URL):5432/$(POSTGRES_DB)"
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: backend-postgres-cluster-app
key: user
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: backend-postgres-cluster-app
key: password
- name: STORAGE_SERVICE_URL
valueFrom:
configMapKeyRef:
name: backend-config
key: STORAGE_SERVICE_INTERNAL_URL
- name: STORAGE_SERVICE_PORT
valueFrom:
configMapKeyRef:
name: backend-config
key: STORAGE_SERVICE_PORT
- name: STORAGE_SERVICE_PATH
value: "http://$(STORAGE_SERVICE_URL):$(STORAGE_SERVICE_PORT)"

25
.k8s/ingress.template.yml Normal file
View File

@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: backend-ingress
namespace: ${KUBE_NAMESPACE}
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- ${KUBE_API_DOMAIN}
secretName: letsencrypt-cluster-certificate-tls
rules:
- host: ${KUBE_API_DOMAIN}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend-service
port:
number: 8070

View File

@@ -0,0 +1,22 @@
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: backend-postgres-cluster
namespace: ${KUBE_NAMESPACE}
spec:
instances: 3
primaryUpdateStrategy: unsupervised
imageName: ghcr.io/cloudnative-pg/postgresql:14.10-18
storage:
size: 5Gi
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
affinity: {}

47
.k8s/redis.template.yml Normal file
View File

@@ -0,0 +1,47 @@
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: ${KUBE_NAMESPACE}
name: backend-redis-deployment
spec:
replicas: 1
selector:
matchLabels:
app: backend-redis
template:
metadata:
labels:
app: backend-redis
spec:
containers:
- name: redis
image: valkey/valkey:8.0.6-alpine
imagePullPolicy: "IfNotPresent"
resources:
requests:
memory: "256Mi"
cpu: "75m"
limits:
memory: "256Mi"
cpu: "256m"
ports:
- containerPort: 6379
env:
- name: VALKEY_PASSWORD
valueFrom:
secretKeyRef:
name: backend-secret
key: REDIS_PASSWORD
---
apiVersion: v1
kind: Service
metadata:
namespace: ${KUBE_NAMESPACE}
name: backend-redis-service
spec:
selector:
app: backend-redis
ports:
- port: 6379
type: ClusterIP

46
.k8s/secrets.enc.yml Normal file
View File

@@ -0,0 +1,46 @@
apiVersion: v1
kind: Secret
metadata:
name: backend-secret
namespace: ${KUBE_NAMESPACE}
data:
ACCESS_TOKEN_DURATION: ENC[AES256_GCM,data:UID13ASjuH9hrFfx,iv:D9cTYN60Q8KL1ZdEMPAQ/RP+uqsMbA95cOqngMtxyF8=,tag:Q4cCzqzzRiuf2wyTW86kXg==,type:str]
DEFAULT_USER_EMAIL: ENC[AES256_GCM,data:DJ/3gHT47OidWhevnCHBdzwrFhmCDQeYzjL3siAi+e/Op3Ge,iv:AqUg21UnGl5tJP6TIewcL9wdpAJBGS7Af6olHRm+auU=,tag:+PX5bjMslR0FVqd9fSXnBg==,type:str]
DEFAULT_USER_FULLNAME: ENC[AES256_GCM,data:WGQEQ1/5NhDYudrW8nBnQUXmcC0=,iv:BvysE7IGiXzFUqse0AtzrW1eO4Y52TgD1RX1hxXjqnQ=,tag:nqHCPt7Aa53OTBOTkQGSDA==,type:str]
DEFAULT_USER_PASSWORD: ENC[AES256_GCM,data:N+q5z+cYXPPf7peJdGKx+A6ou5A=,iv:kmOdcNStwIJOnO1RQ+KOcpW3wxrzR13xtdBwSTvC834=,tag:esM/CE+Y4oCFKs7w573qLw==,type:str]
DEFAULT_USER_USERNAME: ENC[AES256_GCM,data:IXWTfhIKBgs+/zUCRBN5SHcn3Ig=,iv:0RegOxvd7WzoSW8u6sjJLdn6kPoSJzstj+OyStS5zNA=,tag:Lv5XR//V9FD0Aa1kLEmOUA==,type:str]
GITHUB_CLIENT_ID: ENC[AES256_GCM,data:Wg8YoSkLie3HgAMUi2Y25GV/0Di+gs0i6KznVg==,iv:4tsj0GvTa21O/CUZ/54B0VnX9Ebi0pX8Y0cBlwQ9uGQ=,tag:uS9WRW9SEeP+PXERUAUDqg==,type:str]
GITHUB_CLIENT_SECRET: ENC[AES256_GCM,data:xA1jb1YcQzLYOtWQWnY1t6DP701Q4P7JXKVCA9m2t9npettwy4FR2Gt1WqrYkyuMrp8CYYdVnlY=,iv:ThCZid05MBY/6W4/H9Xi85bsT6AzOpdhGVWBEc4zNYE=,tag:hP3CO+ElBug0n3f4P708BA==,type:str]
GITHUB_REDIRECT_URL: ENC[AES256_GCM,data:YI1CWaPAtTilqq7ZAmKKGtn9fQs4/urH874s2Nc51Rnqc5/xjYbTpbd/2MMg0vmyrFK8Twcg2RtnfLFa,iv:viH3P9FPCUsd9KoEwVdNTYNl+v24gcGCnVMJzB4AvxM=,tag:b7oSX1Vn5SiFiMhWBsLEDQ==,type:str]
GOOGLE_CLIENT_ID: ENC[AES256_GCM,data:GaKQG4al/kl1PyVGLyQ3gKI0y93IvyPWoxlWd1Iu8YUkcP6vQ80S4fQ1LCGGS8BA0f83SnwTZT37vrFz7h6V3+po6a7CjXE/gSRsu/HIuOG+O4gyAw07KWHorLpcD8pL,iv:RGskspn8CEq3rwinaOe4T/KajAUMHVBJLnGOgBT8L78=,tag:YuSQgDuowd8LLhf5Rdcj2w==,type:str]
GOOGLE_CLIENT_SECRET: ENC[AES256_GCM,data:6sQAluZFxc6mEOZffPJV5Al64APVHJyHHZzneofu17nflE6eslzH0SfL1Uo/ngu6,iv:Za9MHjCSWsGCik9OgJXYmLfFLmcVPQ7V4bLeeflVVOw=,tag:cZfo/pdl42n+eiKPrwFGMg==,type:str]
GOOGLE_REDIRECT_URL: ENC[AES256_GCM,data:3smhd+Hp3/q2uRVHoASTnm7j1F90TAaauCVuWsCUG1aKb++eXkdLEqP5j77szzFmWmBpgeW3hjP7dipL,iv:CPUxKja1YLSnKQpVeEHGkxxmQg1qJnQxRJ48Mw/k75Q=,tag:YJ/c5N1QLLRbCXsi2D2X4g==,type:str]
REDIS_PASSWORD: ENC[AES256_GCM,data:9kexm1c0M7A=,iv:MUSnfOdUbbsJReQtuzyVrJcsc3NqptE96w3Kh1jbqjo=,tag:SvSHTW8Sm3UMIKismYubng==,type:str]
REFRESH_TOKEN_DURATION: ENC[AES256_GCM,data:MCz6j6RI9hKcD8Zzqvt1Iw==,iv:nsw3cTtVJo+/1foTp/M78ByF5p8K6uw720GY8sAJypU=,tag:Usq/7VvLn7MGpoYbZgXAtg==,type:str]
TOKEN_SECRET: ENC[AES256_GCM,data:ESHASgGJZYspUVua,iv:52aZ1Ds984u8rZR48lNjPhBM07vnWGTpEOE6c7cItUU=,tag:Xc/6w/PqDDspl3r/krgdTg==,type:str]
sops:
lastmodified: "2025-11-07T23:43:53Z"
mac: ENC[AES256_GCM,data:38dHNBEQuExxNeouXp7LotuV5aYTUcrhovYEB0v9SPK8q5ViwXSiU730BTyqF3iya6AuugT3xuFZZG0BFxAOv57FXpiX32pVOb9OQEf/vo1I6+lKjYCg0NiP6qvtpH59z4m35SG6zUXICf5zJucOr+n+UeRMsLO4tbjg9s5A+DU=,iv:vlXW79iMy1qBY+hzqkX2McB3746oJZI/6vqeSi9HNNw=,tag:kGhNHSh/aAXDv3Qz3k3gOg==,type:str]
pgp:
- created_at: "2025-11-07T23:43:53Z"
enc: |-
-----BEGIN PGP MESSAGE-----
hQIMAwzdivR1H/BQAQ//ffQ2BHDvlwBU7Ck4BfM3sN5XYFCMVY1Nd1WxNB+Mso7+
Rx4WD1UfNufJhzXIDwGICyNghrfy16UEZZxq2uM/vE/PeQjOwTmkBAB+izzOP8cM
219UV0RG+qh5/n8v5szcaOvpwI2rU5OvRJs/M7N2563rTy+GtXxM3F8zdMFX6ilQ
PNwo0Ah2ag45PEuu/RH9BQ7egdPx4niKESbBX8Ixp95BndcIUqqDmg7mq7oIeg5K
5nu6D0AAf41D5vqTDNpT5P+KiY5adStW22vwTehfBklVXK65scDbEp7BQjMlfWMX
a2DVv0axxOvqPuXaABoVtbHFH9UMnUCe3rcq1An4id/DHU7WrRt0SkHcvHPYxbsO
IWxtl8Au9NH7hjpYO5uTP1HfUYw/MkZ0kC27ZoIg/QxCM1HkYQJU85J9VrK+GhJT
/HbXGb9PE6XIH/Nh0PNJ7cHdpM0tXSE/AyTjdyN5DSnub6sT28vORbsHQkV4oykq
k03gDtT50qT5t0x9oYydJPBCcTd8vhNfNqxYA0ppuGJo3iv+81LDPRSTvGRSU8UJ
bbX56ryJMO/942oBp4u5dsL2q7u0/5cPBJ7UN8v9dJkSAuSHTVNYhhFzKnLsGbr1
ZpBzfg1Mp0zySv2CZpY5xDu1SFs+kaDVPRrOlPz8jFsyt1WNjRNun0521Gfc/CbS
XgEpMev76yzSjkbNSwRS/2U13w7fh76F2MO6ftOwcWS89Do+drdyWJq15ou0LJZE
cqV6ojX5hhtFsH2YTS/+kGDTGf0mNEHtglSzwUT4M0bHBO2vld7p7SNkzMKrwkE=
=vkjm
-----END PGP MESSAGE-----
fp: 8C8D94A7639C87559B0F2F64B7E1F62F69798EB6
encrypted_regex: ^(data|stringData)$
version: 3.11.0

13
.k8s/service.template.yml Normal file
View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
namespace: ${KUBE_NAMESPACE}
name: backend-service
spec:
selector:
app: backend
ports:
- port: 8070
protocol: TCP
targetPort: 8070
type: ClusterIP