Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4dd95ee
GE4-19 - Add Transaction # Refactor context handling in services and …
tuhin47 Aug 12, 2025
907d84e
GE4-21 - Circle CI + Github actions # Basic build
tuhin47 Aug 14, 2025
5febf23
GE4-21 - Circle CI + Github actions # Add k8 files
tuhin47 Aug 14, 2025
f2e93e9
GE4-21 - Circle CI + Github actions # update docker compose
tuhin47 Aug 14, 2025
2f7a381
GE4-21 - Circle CI + Github actions # update some codes
tuhin47 Aug 15, 2025
cfeebf7
GE4-21 - Circle CI + Github actions #Add ConfigMap and Secret for env…
tuhin47 Aug 15, 2025
a8bf812
GE4-21 # Add deploy script and update Kubernetes manifests for dynami…
tuhin47 Aug 16, 2025
640bf2d
Fix: Keycloak start-dev command path in k8/keycloak.yaml
tuhin47 Aug 16, 2025
9daeeca
feat: Add Kubernetes deployment for app and worker services
tuhin47 Aug 16, 2025
fa7ca73
Apply changes
tuhin47 Aug 16, 2025
3553ebf
Enhance Kubernetes deployment and update local configs
tuhin47 Aug 17, 2025
f0720f4
feat: enhance Kubernetes deployment with config management and enviro…
tuhin47 Aug 17, 2025
b45fd0a
feat: Implement NGINX Ingress for Kubernetes deployments
tuhin47 Aug 17, 2025
75cfe72
feat: Update port forwarding and API endpoints for health and metrics
tuhin47 Aug 17, 2025
8c1c3d2
feat: Update port forwarding, Prometheus configuration, and worker re…
tuhin47 Aug 17, 2025
7930de2
feat: Improve local dev setup and enhance observability
tuhin47 Aug 18, 2025
c6aa200
Switch Keycloak and Prometheus to hostPath volumes
tuhin47 Aug 18, 2025
3b9fbae
Refactor: Localize Kubernetes environment variables
tuhin47 Aug 18, 2025
084d3e7
refactor
tuhin47 Aug 18, 2025
22b2abe
feat: Convert config-server and prometheus to StatefulSets
tuhin47 Aug 18, 2025
899865e
Some refactor
tuhin47 Aug 19, 2025
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/go-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Go Build

on:
push:
branches:
- main
- develop
- feature/**
pull_request:
branches:
- main
- develop
- feature/**

jobs:
build:
runs-on: ubuntu-latest

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

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22' # Use the Go version appropriate for your project

- name: Build
run: go build -v ./...
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ localstack-data/
tmp/
temp/
localstack-data/
vendor/
aws/
sample/

keycloak/managed_context
keycloak/test_suite_analysis
keycloak/test_suite_analysis
.prompts/
app-config-prod.json
21 changes: 21 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
ARG GO_VERSION=1.24.2
FROM golang:${GO_VERSION}-alpine AS builder
RUN mkdir /user && \
echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd && \
echo 'nobody:x:65534:' > /user/group
RUN apk add --no-cache ca-certificates
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod vendor
COPY ./ ./
RUN CGO_ENABLED=0 GOFLAGS=-mod=vendor GOOS=linux go build -a -o /app .

FROM alpine:latest AS final
RUN apk add --no-cache ca-certificates curl
COPY --from=builder /user/group /user/passwd /etc/
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /app /app
USER nobody:nobody

EXPOSE 8080
ENTRYPOINT ["/app"]
6 changes: 2 additions & 4 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cmd

import (
"NotificationManagement/config"
"NotificationManagement/conn"
"NotificationManagement/logger"
"NotificationManagement/server"
"context"
Expand All @@ -23,16 +22,15 @@ var serveCmd = &cobra.Command{
Long: `Start the notification management server with the specified configuration`,
Run: func(cmd *cobra.Command, args []string) {
app := fx.New(
fx.Provide(conn.NewDB),
server.Module,
fx.Invoke(func(lc fx.Lifecycle, e *echo.Echo) {
e.GET("/health", func(c echo.Context) error {
e.GET("/api/health", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
})

// Add Prometheus middleware
e.Use(echoprometheus.NewMiddleware("notification_management"))
e.GET("/metrics", echoprometheus.NewHandler())
e.GET("/api/metrics", echoprometheus.NewHandler())

port := *config.App().Port
addr := fmt.Sprintf(":%d", port)
Expand Down
2 changes: 1 addition & 1 deletion cmd/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func registerHooks(telegramAPI domain.TelegramAPI) {
func NewAsynqServer() *asynq.Server {
return asynq.NewServer(
asynq.RedisClientOpt{
Addr: config.Asynq().RedisAddr,
Addr: config.GetRedisAddr(),
DB: *config.Asynq().DB,
Password: config.Asynq().Pass,
},
Expand Down
8 changes: 5 additions & 3 deletions config/aws_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ type AWSClient struct {
}

func NewAWSClient(cnf *AWSConfig) (*AWSClient, error) {

if os.Getenv(EnvAWSConfigServiceEnabled) == "false" {
return nil, fmt.Errorf("aws service not available")
}

var creds aws.CredentialsProvider
if cnf.AccessKeyID != "" && cnf.SecretAccessKey != "" {
creds = credentials.NewStaticCredentialsProvider(
Expand Down Expand Up @@ -94,9 +99,6 @@ func (c *AWSClient) GetConfigurationRecorderStatus(ctx context.Context) (*config
}

func (c *AWSClient) loadFromSsm() {
if os.Getenv(EnvConfigFromSSM) == "false" {
return
}
resp, err := c.ssm.GetParameter(context.TODO(), &ssm.GetParameterInput{
Name: &c.awsConfig.ConfigService.SSM,
WithDecryption: aws.Bool(true),
Expand Down
4 changes: 2 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type DatabaseConfig struct {
}

type AsynqConfig struct {
RedisAddr string `mapstructure:"redisaddr"`
//RedisAddr string `mapstructure:"redisaddr"`
DB *int `mapstructure:"db"`
Pass string `mapstructure:"pass"`
Concurrency *int `mapstructure:"concurrency"`
Expand Down Expand Up @@ -173,7 +173,7 @@ func loadDefaults() *Config {
SSLMode: "disable",
},
Asynq: AsynqConfig{
RedisAddr: "127.0.0.1:6379",
//RedisAddr: "127.0.0.1:6379",
DB: helper.ToInt("15"),
Pass: "*****",
Concurrency: helper.ToInt("10"),
Expand Down
4 changes: 2 additions & 2 deletions conn/asynq.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
func NewAsynq() *asynq.Client {
asynqConfig := config.Asynq()
return asynq.NewClient(asynq.RedisClientOpt{
Addr: asynqConfig.RedisAddr,
Addr: config.GetRedisAddr(),
DB: *asynqConfig.DB,
Password: asynqConfig.Pass,
})
Expand All @@ -17,7 +17,7 @@ func NewAsynq() *asynq.Client {
func NewAsynqInspector() *asynq.Inspector {
asynqConfig := config.Asynq()
return asynq.NewInspector(asynq.RedisClientOpt{
Addr: asynqConfig.RedisAddr,
Addr: config.GetRedisAddr(),
DB: *asynqConfig.DB,
Password: asynqConfig.Pass,
})
Expand Down
89 changes: 89 additions & 0 deletions deploy-kubernates.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/bin/bash

PROJECT_BASE_PATH=$(pwd)

# Check for --del argument
if [[ "$1" == "--del" ]]; then
echo "Deleting all Kubernetes resources..."
kubectl delete deployment,service,configmap,secret,ingress,statefulset --all -n default
echo "Kubernetes resources deleted."
exit 0
fi

SECRETS_SED_COMMANDS=""
CONFIGMAP_SED_COMMANDS=""

while IFS='=' read -r key value; do
if [[ -z "$key" ]]; then
continue
fi

if [[ "$key" == "GEMINI_KEY" || "$key" == "TELEGRAM_TOKEN" ]]; then
if [[ -n "${!key}" ]]; then
value="${!key}"
echo "Using environment variable for $key"
else
echo "Warning: $key not found in environment variables. Using value from .env file."
fi
fi

if [[ -z "$value" ]]; then
continue
fi

if grep -q " $key:" k8/secrets.yaml; then
ENCODED_VALUE=$(echo -n "$value" | base64)
SECRETS_SED_COMMANDS+=" -e \"s| $key: \".*\"| $key: \\\"$ENCODED_VALUE\\\"|g\""
SECRETS_SED_COMMANDS+=" -e \"s| $key: .*| $key: \\\"$ENCODED_VALUE\\\"|g\""
fi

if grep -q " $key:" k8/config-maps.yaml; then
CONFIGMAP_SED_COMMANDS+=" -e \"s|^[[:space:]]*$key:.*$| $key: \\\"$value\\\"|g\""
fi
done < .env

if [[ -n "$SECRETS_SED_COMMANDS" ]]; then
echo "Updating k8/secrets.yaml with values from .env..."
eval "cat k8/secrets.yaml | sed $SECRETS_SED_COMMANDS" | kubectl apply -f -
else
echo "No matching secrets found in .env to update k8/secrets.yaml."
kubectl apply -f k8/secrets.yaml
fi

if [[ -n "$CONFIGMAP_SED_COMMANDS" ]]; then
echo "Updating k8/config-maps.yaml with values from .env..."
eval "cat k8/config-maps.yaml | sed $CONFIGMAP_SED_COMMANDS" | kubectl apply -f -
else
echo "No matching config map values found in .env to update k8/config-maps.yaml."
kubectl apply -f k8/config-maps.yaml
fi

for file in k8/*.yaml; do
if [ "$file" == "k8/secrets.yaml" ] || [ "$file" == "k8/config-maps.yaml" ]; then
echo "Skipping $file as it's already processed."
continue
fi

if grep -q "__PROJECT_BASE_PATH__" "$file"; then
echo "Processing $file..."
sed "s|__PROJECT_BASE_PATH__|$PROJECT_BASE_PATH|g" "$file" | kubectl apply -f -
else
echo "Applying $file without path replacement..."
kubectl apply -f "$file"
fi
done
echo "All Kubernetes manifests in 'k8' directory processed with dynamic path: $PROJECT_BASE_PATH"

echo "Waiting for config-server statefulset to be ready..."
kubectl rollout status --watch --timeout=300s statefulset/config-server

CONFIG_SERVER_POD=$(kubectl get pods -l app=config-server -o jsonpath='{.items[0].metadata.name}')

if [ -z "$CONFIG_SERVER_POD" ]; then
echo "Error: config-server pod not found."
exit 1
fi

kubectl cp env/app-config-prod.json "$CONFIG_SERVER_POD":/tmp/app-config.json
kubectl cp env/aws_export.sh "$CONFIG_SERVER_POD":/tmp/aws_export.sh
kubectl exec "$CONFIG_SERVER_POD" -- bash /tmp/aws_export.sh;
72 changes: 67 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,22 @@ services:
networks:
- nms-network

keycloak:
container_name: keycloak
keycloak_svc:
container_name: gocloak
image: keycloak/keycloak:25.0.6
command:
- start-dev
- --import-realm
environment:
KC_HOSTNAME: localhost
KC_HOSTNAME: $KEYCLOAK_SERVER_URL
KC_HOSTNAME_STRICT: true
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: secret
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: secret
KC_HEALTH_ENABLED: "true"
KC_FEATURES: account-api,account3,authorization,client-policies,impersonation,docker,scripts,admin-fine-grained-authz
KC_HTTP_RELATIVE_PATH: /keycloak
ports:
- "8081:8080"
- "9000:9000"
Expand All @@ -76,12 +78,12 @@ services:
redis:
image: "bitnami/redis:6.0.9"
platform: linux/amd64 # Force x86 architecture for compatibility
container_name: nms-redis
container_name: redis
restart: unless-stopped
ports:
- "6379:6379"
environment:
# - REDIS_PASSWORD=
#- REDIS_PASSWORD=
- ALLOW_EMPTY_PASSWORD=yes
volumes:
- nms_redis_local_db:/bitnami/redis/data
Expand Down Expand Up @@ -116,6 +118,7 @@ services:
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/usr/share/prometheus/console_libraries'
- '--web.console.templates=/usr/share/prometheus/consoles'
# - '--web.external-url=/prometheus'
extra_hosts:
- "host.docker.internal:host-gateway" # Required for Linux to resolve host.docker.internal
networks:
Expand All @@ -129,13 +132,72 @@ services:
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
# - GF_SERVER_ROOT_URL=http://localhost:3000/grafana
# - GF_SERVER_SERVE_FROM_SUB_PATH=true
volumes:
- grafana-data:/var/lib/grafana
networks:
- nms-network
depends_on:
- prometheus

app:
build:
context: .
dockerfile: Dockerfile
container_name: go-nms
image: tuhin47/go-nms:lastest
command:
- serve
#restart: unless-stopped
ports:
- "8080:8080"
environment:
- LOG_LEVEL
- TELEGRAM_TOKEN
- GEMINI_KEY
- AWS_ENDPOINT
- AWS_CONFIG_SERVICE_ENABLED
- REDIS_HOST
- DB_HOST
- DB_PORT
- KEYCLOAK_SERVER_URL
volumes:
- ./app.log:/app.log
depends_on:
config-server:
condition: service_healthy
redis:
condition: service_started
networks:
- nms-network
extra_hosts:
- "keycloak:host-gateway"

worker:
container_name: go-nms-worker
image: tuhin47/go-nms:lastest
command:
- worker
restart: unless-stopped
environment:
- LOG_LEVEL
- TELEGRAM_TOKEN
- GEMINI_KEY
- AWS_ENDPOINT
- AWS_CONFIG_SERVICE_ENABLED
- REDIS_HOST
- DB_HOST
- DB_PORT
volumes:
- ./app.log:/app.log
networks:
- nms-network
depends_on:
app:
condition: service_started
redis:
condition: service_started
networks:
nms-network:
driver: bridge
Expand Down
2 changes: 2 additions & 0 deletions domain/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package domain

import (
"context"

"gorm.io/gorm"
)

type Repository[T any, ID comparable] interface {
GetDB(ctx context.Context) *gorm.DB
WithTx(tx *gorm.DB) Repository[T, ID]
Create(ctx context.Context, entity *T) error
GetByID(ctx context.Context, id ID, preloads *[]string) (*T, error)
GetByIDs(ctx context.Context, ids []uint, preloads *[]string) ([]T, error)
Expand Down
2 changes: 1 addition & 1 deletion domain/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ type CommonService[T any] interface {
UpdateModel(c context.Context, id uint, model *T) (*T, error)
DeleteModel(c context.Context, id uint) error
GetInstance() CommonService[T]
GetContext() context.Context
ProcessContext(context.Context) context.Context
}
Loading
Loading