chore: serve swagger.json in development builds
In a development build (i.e. does not have the assets build tag), the first request to GET /api/v2/swagger.json will produce log output like: INFLUXDB_VALID_SWAGGER_PATH not set; falling back to checking relative paths {"log_id": "0E4I7Dkl000", "service": "swagger-loader"} Successfully loaded swagger.yml {"log_id": "0E4I7Dkl000", "service": "swagger-loader", "path": "/.../influxdb/http/swagger.yml"} There is no such log line in production builds. But now both builds correctly serve swagger.json, instead of just production builds.pull/12480/head
parent
8e36f59f33
commit
9bbe321d23
|
@ -33,7 +33,7 @@ type APIHandler struct {
|
|||
DocumentHandler *DocumentHandler
|
||||
SetupHandler *SetupHandler
|
||||
SessionHandler *SessionHandler
|
||||
SwaggerHandler http.HandlerFunc
|
||||
SwaggerHandler http.Handler
|
||||
}
|
||||
|
||||
// APIBackend is all services and associated parameters required to construct
|
||||
|
@ -142,7 +142,7 @@ func NewAPIHandler(b *APIBackend) *APIHandler {
|
|||
|
||||
h.ProtoHandler = NewProtoHandler(NewProtoBackend(b))
|
||||
h.ChronografHandler = NewChronografHandler(b.ChronografService)
|
||||
h.SwaggerHandler = SwaggerHandler()
|
||||
h.SwaggerHandler = newSwaggerLoader(b.Logger.With(zap.String("service", "swagger-loader")))
|
||||
h.LabelHandler = NewLabelHandler(b.LabelService)
|
||||
|
||||
return h
|
||||
|
|
|
@ -4,32 +4,62 @@ package http
|
|||
//go:generate env GO111MODULE=on go run github.com/kevinburke/go-bindata/go-bindata -o swagger_gen.go -tags assets -nocompress -pkg http ./swagger.yml
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/influxdata/influxdb"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// SwaggerHandler servers the swagger.json file from bindata
|
||||
func SwaggerHandler() http.HandlerFunc {
|
||||
swagger, err := Asset("swagger.yml")
|
||||
var json []byte
|
||||
if err == nil {
|
||||
json, err = yaml.YAMLToJSON(swagger)
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
EncodeError(context.Background(), &influxdb.Error{
|
||||
Err: err,
|
||||
Msg: "this developer binary not built with assets",
|
||||
Code: influxdb.EInternal,
|
||||
}, w)
|
||||
return
|
||||
}
|
||||
var _ http.Handler = (*swaggerLoader)(nil)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(json)
|
||||
})
|
||||
// swaggerLoader manages loading the swagger asset and serving it as JSON.
|
||||
type swaggerLoader struct {
|
||||
logger *zap.Logger
|
||||
|
||||
// Ensure we only call initialize once.
|
||||
once sync.Once
|
||||
|
||||
// The swagger converted from YAML to JSON.
|
||||
json []byte
|
||||
|
||||
// The error loading the swagger asset.
|
||||
loadErr error
|
||||
}
|
||||
|
||||
func newSwaggerLoader(logger *zap.Logger) *swaggerLoader {
|
||||
return &swaggerLoader{logger: logger}
|
||||
}
|
||||
|
||||
func (s *swaggerLoader) initialize() {
|
||||
swagger, err := s.asset(Asset("swagger.yml"))
|
||||
if err != nil {
|
||||
s.loadErr = err
|
||||
return
|
||||
}
|
||||
|
||||
j, err := yaml.YAMLToJSON(swagger)
|
||||
if err == nil {
|
||||
s.json = j
|
||||
} else {
|
||||
s.loadErr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *swaggerLoader) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.once.Do(s.initialize)
|
||||
|
||||
if s.loadErr != nil {
|
||||
EncodeError(r.Context(), &influxdb.Error{
|
||||
Err: s.loadErr,
|
||||
Msg: "this developer binary not built with assets",
|
||||
Code: influxdb.EInternal,
|
||||
}, w)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(s.json)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
// +build assets
|
||||
|
||||
package http
|
||||
|
||||
// asset returns its input arguments.
|
||||
//
|
||||
// There is a separate definition of asset when not using the assets build tag.
|
||||
func (s *swaggerLoader) asset(swaggerData []byte, err error) ([]byte, error) {
|
||||
return swaggerData, err
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
// +build !assets
|
||||
|
||||
package http
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// asset returns swaggerData if present;
|
||||
// otherwise it looks in the following locations in this order:
|
||||
// the location specified in environment variable INFLUXDB_SWAGGER_YML_PATH;
|
||||
// <path to executable>/../../http/swagger.yml (binary built with make but without assets tag);
|
||||
// <path to executable>/http/swagger.yml (user ran go build ./cmd/influxd && ./influxd);
|
||||
// ./http/swagger.yml (user ran go run ./cmd/influxd).
|
||||
//
|
||||
// None of these lookups happen in production builds, which have the assets build tag.
|
||||
func (s *swaggerLoader) asset(swaggerData []byte, _ error) ([]byte, error) {
|
||||
if len(swaggerData) > 0 {
|
||||
return swaggerData, nil
|
||||
}
|
||||
|
||||
path := findSwaggerPath(s.logger)
|
||||
if path == "" {
|
||||
// Couldn't find it.
|
||||
return nil, errors.New("this developer binary not built with assets, and could not locate swagger.yml on disk")
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
s.logger.Info("Unable to load swagger.yml from disk", zap.String("path", path), zap.Error(err))
|
||||
return nil, errors.New("this developer binary not built with assets, and unable to load swagger.yml from disk")
|
||||
}
|
||||
|
||||
s.logger.Info("Successfully loaded swagger.yml", zap.String("path", path))
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// findSwaggerPath makes a best-effort to find the path of the swagger file on disk.
|
||||
// If it can't find the path, it returns the empty string.
|
||||
func findSwaggerPath(logger *zap.Logger) string {
|
||||
// First, look for environment variable pointing at swagger.
|
||||
path := os.Getenv("INFLUXDB_VALID_SWAGGER_PATH")
|
||||
if path != "" {
|
||||
// Environment variable set.
|
||||
return path
|
||||
}
|
||||
|
||||
logger.Info("INFLUXDB_VALID_SWAGGER_PATH not set; falling back to checking relative paths")
|
||||
|
||||
// Get the path to the executable so we can do a relative lookup.
|
||||
execPath, err := os.Executable()
|
||||
if err != nil {
|
||||
// Give up.
|
||||
logger.Info("Can't determine path of currently running executable", zap.Error(err))
|
||||
return ""
|
||||
}
|
||||
|
||||
execDir := filepath.Dir(execPath)
|
||||
|
||||
// Assume the executable is in bin/$OS/, i.e. the developer built with make, but somehow without assets.
|
||||
path = filepath.Join(execDir, "..", "..", "http", "swagger.yml")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
// Looks like we found it.
|
||||
return path
|
||||
}
|
||||
|
||||
// We didn't build from make... maybe the developer ran something like "go build ./cmd/influxd && ./influxd".
|
||||
path = filepath.Join(execDir, "http", "swagger.yml")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
// Looks like we found it.
|
||||
return path
|
||||
}
|
||||
|
||||
// Maybe they're in the influxdb root, and ran something like "go run ./cmd/influxd".
|
||||
wd, err := os.Getwd()
|
||||
if err == nil {
|
||||
path = filepath.Join(wd, "http", "swagger.yml")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
// Looks like we found it.
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("Couldn't guess path to swagger definition")
|
||||
return ""
|
||||
}
|
Loading…
Reference in New Issue