/
service.go
153 lines (122 loc) · 4.7 KB
/
service.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
package service
import (
"context"
"github.com/ONSdigital/dp-authorisation/v2/authorisation"
"github.com/ONSdigital/dp-identity-api/api"
cognitoClient "github.com/ONSdigital/dp-identity-api/cognito"
"github.com/ONSdigital/dp-identity-api/config"
"github.com/ONSdigital/dp-identity-api/jwks"
health "github.com/ONSdigital/dp-identity-api/service/healthcheck"
"github.com/ONSdigital/log.go/v2/log"
"github.com/gorilla/mux"
"github.com/pkg/errors"
)
// Service contains all the configs, server and clients to run the dp-identity-api API
type Service struct {
Config *config.Config
Server HTTPServer
Router *mux.Router
Api *api.API
ServiceList *ExternalServiceList
HealthCheck HealthChecker
authorisationMiddleware authorisation.Middleware
}
// Run the service
func Run(ctx context.Context, cfg *config.Config, serviceList *ExternalServiceList, jwksHandler jwks.JWKSInt, buildTime, gitCommit, version string, svcErrors chan error) (*Service, error) {
log.Info(ctx, "running service")
log.Info(ctx, "using service configuration", log.Data{"config": cfg})
r := mux.NewRouter()
s := serviceList.GetHTTPServer(cfg.BindAddr, r)
cognitoclient := serviceList.GetCognitoClient(cfg.AWSRegion)
authorisationMiddleware, err := serviceList.GetAuthorisationMiddleware(ctx, cfg.AuthorisationConfig)
if err != nil {
log.Fatal(ctx, "could not instantiate authorisation middleware", err)
return nil, err
}
a, err := api.Setup(ctx, r, cognitoclient, cfg.AWSCognitoUserPoolID, cfg.AWSCognitoClientId, cfg.AWSCognitoClientSecret, cfg.AWSRegion, cfg.AWSAuthFlow, cfg.AllowedEmailDomains, authorisationMiddleware, jwksHandler)
if err != nil {
log.Fatal(ctx, "error returned from api setup", err)
return nil, err
}
hc, err := serviceList.GetHealthCheck(cfg, buildTime, gitCommit, version)
if err != nil {
log.Fatal(ctx, "could not instantiate healthcheck", err)
return nil, err
}
if err := registerCheckers(ctx, hc, cognitoclient, &cfg.AWSCognitoUserPoolID, authorisationMiddleware); err != nil {
return nil, errors.Wrap(err, "unable to register checkers")
}
r.StrictSlash(true).Path("/health").HandlerFunc(hc.Handler)
hc.Start(ctx)
// Run the http server in a new go-routine
go func() {
if err := s.ListenAndServe(); err != nil {
svcErrors <- errors.Wrap(err, "failure in http listen and serve")
}
}()
return &Service{
Config: cfg,
Router: r,
Api: a,
HealthCheck: hc,
ServiceList: serviceList,
Server: s,
authorisationMiddleware: authorisationMiddleware,
}, nil
}
// Close gracefully shuts the service down in the required order, with timeout
func (svc *Service) Close(ctx context.Context) error {
timeout := svc.Config.GracefulShutdownTimeout
log.Info(ctx, "commencing graceful shutdown", log.Data{"graceful_shutdown_timeout": timeout})
ctx, cancel := context.WithTimeout(ctx, timeout)
// track shutown gracefully closes up
var hasShutdownError bool
go func() {
defer cancel()
// stop healthcheck, as it depends on everything else
if svc.ServiceList.HealthCheck {
svc.HealthCheck.Stop()
}
// stop any incoming requests before closing any outbound connections
if err := svc.Server.Shutdown(ctx); err != nil {
log.Error(ctx, "failed to shutdown http server", err)
hasShutdownError = true
}
if svc.ServiceList.AuthMiddleware {
if err := svc.authorisationMiddleware.Close(ctx); err != nil {
log.Error(ctx, "failed to close authorisation middleware", err)
hasShutdownError = true
}
}
}()
// wait for shutdown success (via cancel) or failure (timeout)
<-ctx.Done()
// timeout expired
if ctx.Err() == context.DeadlineExceeded {
log.Error(ctx, "shutdown timed out", ctx.Err())
return ctx.Err()
}
// other error
if hasShutdownError {
err := errors.New("failed to shutdown gracefully")
log.Error(ctx, "failed to shutdown gracefully ", err)
return err
}
log.Info(ctx, "graceful shutdown was successful")
return nil
}
func registerCheckers(ctx context.Context, hc HealthChecker, client cognitoClient.Client, userPoolID *string, authorisationMiddleware authorisation.Middleware) (err error) {
hasErrors := false
if err := hc.AddCheck("Cognito", health.CognitoHealthCheck(ctx, client, userPoolID)); err != nil {
hasErrors = true
log.Error(ctx, "error adding health checker for Cognito", err)
}
if err := hc.AddCheck("Permissions API", authorisationMiddleware.HealthCheck); err != nil {
hasErrors = true
log.Error(ctx, "error adding health checker for Permissions API", err)
}
if hasErrors {
return errors.New("Error(s) registering checkers for healthcheck")
}
return nil
}