From 9ca8a5ad6d13e411fe47e0707c2204b7f9cb1ba2 Mon Sep 17 00:00:00 2001 From: David Chang Date: Tue, 27 Feb 2024 15:53:43 -0500 Subject: [PATCH] Add a simple way to run both app and database using `docker compose` --- .env.example | 2 +- Dockerfile | 2 +- README.md | 22 ++++++++++++++++++---- docker-compose.yml | 33 +++++++++++++++++++++++++++++++++ main.go | 26 ++++++++++++++++++++------ 5 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example index 74ee532..6f016b4 100644 --- a/.env.example +++ b/.env.example @@ -3,5 +3,5 @@ MYSQL_PORT=[3306] DB_USER=[root] DB_PASSWORD=[password] DB_NAME=[local_db] -DB_CONNECTION_NAME=localhost:$MYSQL_PORT +DB_CONNECTION_NAME=[mysql:$MYSQL_PORT] DB_CONNECTION_STRING=$DB_USER:$DB_PASSWORD@tcp($DB_CONNECTION_NAME)/$DB_NAME?charset=utf8&parseTime=True&loc=Local diff --git a/Dockerfile b/Dockerfile index 1dd9783..2ce3740 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21 +FROM golang:latest WORKDIR /usr/src/app diff --git a/README.md b/README.md index 5577221..fe9a707 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,21 @@ RESTful API for interacting with David Chang programmatically ## Build & run locally (http://localhost:80) -### Configure + +### Configure, build, and run using `docker compose` +`$ cp .env.example .env` +- Fill out all the details for your local database (replace examples in brackets): + - ENVIRONMENT=[development] + - MYSQL_PORT=[3306] + - DB_USER=[root] + - DB_PASSWORD=[password] + - DB_NAME=[local_db] + - DB_CONNECTION_NAME=[mysql:$MYSQL_PORT] + - DB_CONNECTION_STRING=$DB_USER:$DB_PASSWORD@tcp($DB_CONNECTION_NAME)/$DB_NAME?charset=utf8&parseTime=True&loc=Local +- Populate your local environment +`$ source .env` + +### Configure, build, and run manually `$ cp .env.example .env` - Fill out all the details for your local database (replace examples in brackets): - ENVIRONMENT=[development] @@ -10,7 +24,7 @@ RESTful API for interacting with David Chang programmatically - DB_USER=[root] - DB_PASSWORD=[password] - DB_NAME=[local_db] - - DB_CONNECTION_NAME=localhost:$MYSQL_PORT + - DB_CONNECTION_NAME=[localhost:$MYSQL_PORT] - DB_CONNECTION_STRING=$DB_USER:$DB_PASSWORD@tcp($DB_CONNECTION_NAME)/$DB_NAME?charset=utf8&parseTime=True&loc=Local - Populate your local environment `$ source .env` @@ -20,9 +34,9 @@ RESTful API for interacting with David Chang programmatically `$ docker exec -it local_mysql mysql -u $DB_USER -p` (enter password when prompted $DB_PASSWORD) `CREATE DATABASE [$DB_NAME]` (replace [$DB_NAME] with password you set up) -### Build +#### Build `$ docker build -t dev-davidchang-api .` -### Run +#### Run `$ docker run -p 80:8080 -it --rm --name dev-dc-api dev-davidchang-api` ## API Design (https://api.davidchang.dev) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2d1ddb5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +version: '3' + +services: + api: + build: + context: . + ports: + - "80:8080" # Map host's port 80 to container's port 8080 + networks: + - api-network + depends_on: + mysql: + condition: service_healthy + environment: + DB_CONNECTION_STRING: "$DB_USER:$DB_PASSWORD@tcp($DB_CONNECTION_NAME)/$DB_NAME?charset=utf8&parseTime=True&loc=UTC" + + mysql: + image: mysql:latest + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} + MYSQL_DATABASE: ${DB_NAME} + ports: + - "3306:3306" + networks: + - api-network + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + timeout: 10s + retries: 10 + +networks: + api-network: + driver: bridge diff --git a/main.go b/main.go index 6503b20..4fd6403 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "net/http" "os" "strings" + "time" "github.com/gin-gonic/gin" "github.com/joho/godotenv" @@ -71,20 +72,33 @@ func setupDB() error { connectionString = fmt.Sprintf("%s:%s@unix(/cloudsql/%s)/%s?charset=utf8&parseTime=True&loc=Local", dbUser, dbPassword, dbConnectionName, dbName) } + // Open a database connection - database, err := gorm.Open(mysql.Open(connectionString), &gorm.Config{}) + var err error + for i := 0; i < 10; i++ { // Attempt to connect 10 times with a delay between attempts + db, err = gorm.Open(mysql.Open(connectionString), &gorm.Config{ + // Set the time zone + NowFunc: func() time.Time { + return time.Now().UTC() + }, + }) + if err == nil { + break + } + + log.Printf("Error connecting to the database: %v. Retrying in 5 seconds...", err) + time.Sleep(5 * time.Second) + } + if err != nil { - return fmt.Errorf("failed to connect to database: %v", err) + return fmt.Errorf("failed to connect to the database after multiple attempts: %v", err) } // Auto Migrate the resumé model, Resume - if err := database.AutoMigrate(&Resume{}); err != nil { + if err := db.AutoMigrate(&Resume{}); err != nil { return fmt.Errorf("failed to migrate database: %v", err) } - // Assign the database instance to the global variable - db = database - return nil }