forked from juju/charmstore
/
server.go
180 lines (156 loc) · 5.74 KB
/
server.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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
// This is the internal version of the charmstore package.
// It exposes details to the various API packages
// that we do not wish to expose to the world at large.
package charmstore // import "gopkg.in/juju/charmstore.v5-unstable/internal/charmstore"
import (
"net/http"
"strings"
"time"
"gopkg.in/errgo.v1"
"gopkg.in/macaroon-bakery.v1/bakery"
"gopkg.in/mgo.v2"
"gopkg.in/natefinch/lumberjack.v2"
"gopkg.in/juju/charmstore.v5-unstable/internal/router"
)
// NewAPIHandlerFunc is a function that returns a new API handler that uses
// the given Store. The absPath parameter holds the root path of the
// API handler.
type NewAPIHandlerFunc func(pool *Pool, p ServerParams, absPath string) HTTPCloseHandler
// HTTPCloseHandler represents a HTTP handler that
// must be closed after use.
type HTTPCloseHandler interface {
Close()
http.Handler
}
// ServerParams holds configuration for a new internal API server.
type ServerParams struct {
// AuthUsername and AuthPassword hold the credentials
// used for HTTP basic authentication.
AuthUsername string
AuthPassword string
// IdentityLocation holds the location of the third party authorization
// service to use when creating third party caveats,
// for example: http://api.jujucharms.com/identity/v1/discharger
// If it is empty, IdentityURL+"/v1/discharger" will be used.
IdentityLocation string
// TermsLocation holds the location of the third party
// terms service to use when creating third party caveats.
TermsLocation string
// PublicKeyLocator holds a public key store.
// It may be nil.
PublicKeyLocator bakery.PublicKeyLocator
// IdentityAPIURL holds the URL of the identity manager,
// for example http://api.jujucharms.com/identity
IdentityAPIURL string
// AgentUsername and AgentKey hold the credentials used for agent
// authentication.
AgentUsername string
AgentKey *bakery.KeyPair
// StatsCacheMaxAge is the maximum length of time between
// refreshes of entities in the stats cache.
StatsCacheMaxAge time.Duration
// SearchCacheMaxAge is the maximum length of time between
// refreshes of entities in the search cache.
SearchCacheMaxAge time.Duration
// MaxMgoSessions specifies a soft limit on the maximum
// number of mongo sessions used. Each concurrent
// HTTP request will use one session.
MaxMgoSessions int
// HTTPRequestWaitDuration holds the amount of time
// that an HTTP request will wait for a free connection
// when the MaxConcurrentHTTPRequests limit is reached.
HTTPRequestWaitDuration time.Duration
// AuditLogger optionally holds the logger which will be used to
// write audit log entries.
AuditLogger *lumberjack.Logger
}
// NewServer returns a handler that serves the given charm store API
// versions using db to store that charm store data.
// An optional elasticsearch configuration can be specified in si. If
// elasticsearch is not being used then si can be set to nil.
// The key of the versions map is the version name.
// The handler configuration is provided to all version handlers.
//
// The returned Server should be closed after use.
func NewServer(db *mgo.Database, si *SearchIndex, config ServerParams, versions map[string]NewAPIHandlerFunc) (*Server, error) {
if len(versions) == 0 {
return nil, errgo.Newf("charm store server must serve at least one version of the API")
}
config.IdentityLocation = strings.Trim(config.IdentityLocation, "/")
config.TermsLocation = strings.Trim(config.TermsLocation, "/")
config.IdentityAPIURL = strings.Trim(config.IdentityAPIURL, "/")
if config.IdentityLocation == "" && config.IdentityAPIURL != "" {
config.IdentityLocation = config.IdentityAPIURL + "/v1/discharger"
}
logger.Infof("identity discharge location: %s", config.IdentityLocation)
logger.Infof("identity API location: %s", config.IdentityAPIURL)
logger.Infof("terms discharge location: %s", config.TermsLocation)
bparams := bakery.NewServiceParams{
// TODO The location is attached to any macaroons that we
// mint. Currently we don't know the location of the current
// service. We potentially provide a way to configure this,
// but it probably doesn't matter, as nothing currently uses
// the macaroon location for anything.
Location: "charmstore",
Locator: config.PublicKeyLocator,
}
pool, err := NewPool(db, si, &bparams, config)
if err != nil {
return nil, errgo.Notef(err, "cannot make store")
}
store := pool.Store()
defer store.Close()
if err := migrate(store.DB); err != nil {
pool.Close()
return nil, errgo.Notef(err, "database migration failed")
}
store.Go(func(store *Store) {
if err := store.syncSearch(); err != nil {
logger.Errorf("Cannot populate elasticsearch: %v", err)
}
})
srv := &Server{
pool: pool,
mux: router.NewServeMux(),
}
// Version independent API.
handle(srv.mux, "/debug", newServiceDebugHandler(pool, config, srv.mux))
for vers, newAPI := range versions {
root := "/" + vers
h := newAPI(pool, config, root)
handle(srv.mux, root, h)
srv.handlers = append(srv.handlers, h)
}
return srv, nil
}
type Server struct {
pool *Pool
mux *router.ServeMux
handlers []HTTPCloseHandler
}
// ServeHTTP implements http.Handler.ServeHTTP.
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
s.mux.ServeHTTP(w, req)
}
// Close closes the server. It must be called when the server
// is finished with.
func (s *Server) Close() {
s.pool.Close()
for _, h := range s.handlers {
h.Close()
}
s.handlers = nil
}
// Pool returns the Pool used by the server.
func (s *Server) Pool() *Pool {
return s.pool
}
func handle(mux *router.ServeMux, path string, handler http.Handler) {
if path != "/" {
handler = http.StripPrefix(path, handler)
path += "/"
}
mux.Handle(path, handler)
}