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
Mark Rushakoff 2019-03-08 22:32:59 -08:00 committed by Mark Rushakoff
parent 8e36f59f33
commit 9bbe321d23
4 changed files with 155 additions and 23 deletions

View File

@ -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

View File

@ -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)
}

10
http/swagger_assets.go Normal file
View File

@ -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
}

92
http/swagger_noassets.go Normal file
View File

@ -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 ""
}