-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy pathmain.go
165 lines (135 loc) · 5.5 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package main
import (
"math"
"net/http"
"os"
"time"
"github.com/hashicorp-demoapp/go-hckit"
"github.com/nicholasjackson/env"
"github.com/rs/cors"
"github.com/gorilla/mux"
"github.com/hashicorp-demoapp/product-api-go/config"
"github.com/hashicorp-demoapp/product-api-go/data"
"github.com/hashicorp-demoapp/product-api-go/handlers"
"github.com/hashicorp-demoapp/product-api-go/telemetry"
"github.com/hashicorp/go-hclog"
)
// Config format for application
type Config struct {
DBConnection string `json:"db_connection"`
BindAddress string `json:"bind_address"`
MetricsAddress string `json:"metrics_address"`
MaxRetries int `json:"max_retries"`
BackoffExponentialBase float64 `json:"backoff_exponential_base"`
}
var conf *Config
var logger hclog.Logger
var configFile = env.String("CONFIG_FILE", false, "./conf.json", "Path to JSON encoded config file")
var dbConnection = env.String("DB_CONNECTION", false, "", "db connection string")
var bindAddress = env.String("BIND_ADDRESS", false, "", "Bind address")
var metricsAddress = env.String("METRICS_ADDRESS", false, "", "Metrics address")
var maxRetries = env.Int("MAX_RETRIES", false, 60, "Maximum number of connection retries")
var backoffExponentialBase = env.Float64("BACKOFF_EXPONENTIAL_BASE", false, 1, "Exponential base number to calculate the backoff")
const jwtSecret = "test"
func main() {
logger = hclog.Default()
err := env.Parse()
if err != nil {
logger.Error("Error parsing flags", "error", err)
os.Exit(1)
}
closer, err := hckit.InitGlobalTracer("product-api")
if err != nil {
logger.Error("Unable to initialize Tracer", "error", err)
os.Exit(1)
}
defer closer.Close()
conf = &Config{
DBConnection: *dbConnection,
BindAddress: *bindAddress,
MetricsAddress: *metricsAddress,
MaxRetries: *maxRetries,
BackoffExponentialBase: *backoffExponentialBase,
}
// load the config, unless provided by env
if conf.DBConnection == "" || conf.BindAddress == "" {
c, err := config.New(*configFile, conf, configUpdated)
if err != nil {
logger.Error("Unable to load config file", "error", err)
os.Exit(1)
}
defer c.Close()
}
// configure the telemetry
t := telemetry.New(conf.MetricsAddress)
// load the db connection
db, err := retryDBUntilReady()
if err != nil {
logger.Error("Timeout waiting for database connection")
os.Exit(1)
}
r := mux.NewRouter()
r.Use(hckit.TracingMiddleware)
// Enable CORS for all hosts
r.Use(cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"POST", "GET", "OPTIONS", "PUT", "DELETE"},
AllowedHeaders: []string{"Accept", "content-type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization"},
}).Handler)
authMiddleware := handlers.NewAuthMiddleware(db, logger)
healthHandler := handlers.NewHealth(t, logger, db)
r.Handle("/health", healthHandler).Methods("GET")
r.HandleFunc("/health/livez", healthHandler.Liveness).Methods("GET")
r.HandleFunc("/health/readyz", healthHandler.Readiness).Methods("GET")
coffeeHandler := handlers.NewCoffee(db, logger)
r.Handle("/coffees", coffeeHandler).Methods("GET")
r.Handle("/coffees/{id:[0-9]+}", coffeeHandler).Methods("GET")
r.Handle("/coffees", authMiddleware.IsAuthorized(coffeeHandler.CreateCoffee)).Methods("POST")
ingredientsHandler := handlers.NewIngredients(db, logger)
r.Handle("/coffees/{id:[0-9]+}/ingredients", ingredientsHandler).Methods("GET")
r.Handle("/coffees/{id:[0-9]+}/ingredients", authMiddleware.IsAuthorized(ingredientsHandler.CreateCoffeeIngredient)).Methods("POST")
userHandler := handlers.NewUser(db, logger)
r.HandleFunc("/signup", userHandler.SignUp).Methods("POST")
r.HandleFunc("/signin", userHandler.SignIn).Methods("POST")
r.HandleFunc("/signout", userHandler.SignOut).Methods("POST")
orderHandler := handlers.NewOrder(db, logger)
r.Handle("/orders", authMiddleware.IsAuthorized(orderHandler.GetUserOrders)).Methods("GET")
r.Handle("/orders", authMiddleware.IsAuthorized(orderHandler.CreateOrder)).Methods("POST")
r.Handle("/orders/{id:[0-9]+}", authMiddleware.IsAuthorized(orderHandler.GetUserOrder)).Methods("GET")
r.Handle("/orders/{id:[0-9]+}", authMiddleware.IsAuthorized(orderHandler.UpdateOrder)).Methods("PUT")
r.Handle("/orders/{id:[0-9]+}", authMiddleware.IsAuthorized(orderHandler.DeleteOrder)).Methods("DELETE")
logger.Info("Starting service", "bind", conf.BindAddress, "metrics", conf.MetricsAddress)
err = http.ListenAndServe(conf.BindAddress, r)
if err != nil {
logger.Error("Unable to start server", "bind", conf.BindAddress, "error", err)
}
}
// retryDBUntilReady keeps retrying the database connection
// when running the application on a scheduler it is possible that the app will come up before
// the database, this can cause the app to go into a CrashLoopBackoff cycle
func retryDBUntilReady() (data.Connection, error) {
maxRetries := conf.MaxRetries
backoffExponentialBase := conf.BackoffExponentialBase
dt := 0
retries := 0
backoff := time.Duration(0) // backoff before attempting to conection
for {
db, err := data.New(conf.DBConnection)
if err == nil {
return db, nil
}
logger.Error("Unable to connect to database", "error", err)
// check if current retry reaches the max number of allowed retries
if retries > maxRetries {
return nil, err
}
// retry
retries++
dt = int(math.Pow(backoffExponentialBase, float64(retries)))
backoff = time.Duration(dt) * time.Second
time.Sleep(backoff)
}
}
func configUpdated() {
logger.Info("Config file changed")
}