2018-09-12 19:24:17 +00:00
|
|
|
package http
|
|
|
|
|
|
|
|
import (
|
2018-10-02 17:21:36 +00:00
|
|
|
"bytes"
|
2018-09-12 19:24:17 +00:00
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2019-07-09 15:16:26 +00:00
|
|
|
"fmt"
|
2018-09-12 19:24:17 +00:00
|
|
|
"net/http"
|
|
|
|
|
2019-01-08 00:37:16 +00:00
|
|
|
platform "github.com/influxdata/influxdb"
|
2018-09-12 19:24:17 +00:00
|
|
|
"github.com/julienschmidt/httprouter"
|
2018-12-20 16:07:46 +00:00
|
|
|
"go.uber.org/zap"
|
2018-09-12 19:24:17 +00:00
|
|
|
)
|
|
|
|
|
2019-01-16 14:59:34 +00:00
|
|
|
// SetupBackend is all services and associated parameters required to construct
|
|
|
|
// the SetupHandler.
|
|
|
|
type SetupBackend struct {
|
2019-06-27 01:33:20 +00:00
|
|
|
platform.HTTPErrorHandler
|
2019-01-16 14:59:34 +00:00
|
|
|
Logger *zap.Logger
|
|
|
|
OnboardingService platform.OnboardingService
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewSetupBackend returns a new instance of SetupBackend.
|
|
|
|
func NewSetupBackend(b *APIBackend) *SetupBackend {
|
|
|
|
return &SetupBackend{
|
2019-06-27 01:33:20 +00:00
|
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
2019-01-16 14:59:34 +00:00
|
|
|
Logger: b.Logger.With(zap.String("handler", "setup")),
|
|
|
|
OnboardingService: b.OnboardingService,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-12 19:24:17 +00:00
|
|
|
// SetupHandler represents an HTTP API handler for onboarding setup.
|
|
|
|
type SetupHandler struct {
|
|
|
|
*httprouter.Router
|
2019-06-27 01:33:20 +00:00
|
|
|
platform.HTTPErrorHandler
|
2018-12-20 16:07:46 +00:00
|
|
|
Logger *zap.Logger
|
|
|
|
|
2018-09-12 19:24:17 +00:00
|
|
|
OnboardingService platform.OnboardingService
|
|
|
|
}
|
|
|
|
|
2018-10-02 17:21:36 +00:00
|
|
|
const (
|
|
|
|
setupPath = "/api/v2/setup"
|
|
|
|
)
|
|
|
|
|
2018-09-12 19:24:17 +00:00
|
|
|
// NewSetupHandler returns a new instance of SetupHandler.
|
2019-01-16 14:59:34 +00:00
|
|
|
func NewSetupHandler(b *SetupBackend) *SetupHandler {
|
2018-09-12 19:24:17 +00:00
|
|
|
h := &SetupHandler{
|
2019-06-27 01:33:20 +00:00
|
|
|
Router: NewRouter(b.HTTPErrorHandler),
|
|
|
|
HTTPErrorHandler: b.HTTPErrorHandler,
|
2019-01-16 14:59:34 +00:00
|
|
|
Logger: b.Logger,
|
|
|
|
OnboardingService: b.OnboardingService,
|
2018-09-12 19:24:17 +00:00
|
|
|
}
|
2018-10-02 17:21:36 +00:00
|
|
|
h.HandlerFunc("POST", setupPath, h.handlePostSetup)
|
|
|
|
h.HandlerFunc("GET", setupPath, h.isOnboarding)
|
2018-09-12 19:24:17 +00:00
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
2018-10-02 17:21:36 +00:00
|
|
|
type isOnboardingResponse struct {
|
|
|
|
Allowed bool `json:"allowed"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// isOnboarding is the HTTP handler for the GET /api/v2/setup route.
|
2018-09-12 19:24:17 +00:00
|
|
|
func (h *SetupHandler) isOnboarding(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
result, err := h.OnboardingService.IsOnboarding(ctx)
|
|
|
|
if err != nil {
|
2019-06-27 01:33:20 +00:00
|
|
|
h.HandleHTTPError(ctx, err, w)
|
2018-09-12 19:24:17 +00:00
|
|
|
return
|
|
|
|
}
|
2019-07-09 15:16:26 +00:00
|
|
|
h.Logger.Debug("onboarding eligibility check finished", zap.String("result", fmt.Sprint(result)))
|
|
|
|
|
2018-10-02 17:21:36 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusOK, isOnboardingResponse{result}); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.Logger, r, err)
|
2018-10-02 17:21:36 +00:00
|
|
|
return
|
|
|
|
}
|
2018-09-12 19:24:17 +00:00
|
|
|
}
|
|
|
|
|
2018-10-02 17:21:36 +00:00
|
|
|
// isOnboarding is the HTTP handler for the POST /api/v2/setup route.
|
2018-09-12 19:24:17 +00:00
|
|
|
func (h *SetupHandler) handlePostSetup(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
req, err := decodePostSetupRequest(ctx, r)
|
|
|
|
if err != nil {
|
2019-06-27 01:33:20 +00:00
|
|
|
h.HandleHTTPError(ctx, err, w)
|
2018-09-12 19:24:17 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
results, err := h.OnboardingService.Generate(ctx, req)
|
|
|
|
if err != nil {
|
2019-06-27 01:33:20 +00:00
|
|
|
h.HandleHTTPError(ctx, err, w)
|
2018-09-12 19:24:17 +00:00
|
|
|
return
|
|
|
|
}
|
2019-07-09 15:16:26 +00:00
|
|
|
h.Logger.Debug("onboarding setup completed", zap.String("results", fmt.Sprint(results)))
|
|
|
|
|
2018-09-12 19:24:17 +00:00
|
|
|
if err := encodeResponse(ctx, w, http.StatusCreated, newOnboardingResponse(results)); err != nil {
|
2018-12-20 16:07:46 +00:00
|
|
|
logEncodingError(h.Logger, r, err)
|
2018-09-12 19:24:17 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type onboardingResponse struct {
|
2019-10-09 21:01:23 +00:00
|
|
|
User *UserResponse `json:"user"`
|
2018-09-12 19:24:17 +00:00
|
|
|
Bucket *bucketResponse `json:"bucket"`
|
|
|
|
Organization *orgResponse `json:"org"`
|
|
|
|
Auth *authResponse `json:"auth"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func newOnboardingResponse(results *platform.OnboardingResults) *onboardingResponse {
|
2018-12-28 23:02:19 +00:00
|
|
|
// when onboarding the permissions are for all resources and no
|
|
|
|
// specifically named resources. Therefore, there is no need to
|
|
|
|
// lookup the name.
|
|
|
|
ps := make([]permissionResponse, len(results.Auth.Permissions))
|
|
|
|
for i, p := range results.Auth.Permissions {
|
|
|
|
ps[i] = permissionResponse{
|
2019-01-15 16:09:58 +00:00
|
|
|
Action: p.Action,
|
|
|
|
Resource: resourceResponse{
|
|
|
|
Resource: p.Resource,
|
|
|
|
},
|
2018-12-28 23:02:19 +00:00
|
|
|
}
|
|
|
|
}
|
2018-09-12 19:24:17 +00:00
|
|
|
return &onboardingResponse{
|
|
|
|
User: newUserResponse(results.User),
|
2019-01-03 23:56:52 +00:00
|
|
|
Bucket: newBucketResponse(results.Bucket, []*platform.Label{}),
|
2018-09-12 19:24:17 +00:00
|
|
|
Organization: newOrgResponse(results.Org),
|
2018-12-28 23:02:19 +00:00
|
|
|
Auth: newAuthResponse(results.Auth, results.Org, results.User, ps),
|
2018-09-12 19:24:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodePostSetupRequest(ctx context.Context, r *http.Request) (*platform.OnboardingRequest, error) {
|
|
|
|
req := &platform.OnboardingRequest{}
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return req, nil
|
|
|
|
}
|
2018-10-02 17:21:36 +00:00
|
|
|
|
|
|
|
// SetupService connects to Influx via HTTP to perform onboarding operations
|
|
|
|
type SetupService struct {
|
|
|
|
Addr string
|
|
|
|
InsecureSkipVerify bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsOnboarding determine if onboarding request is allowed.
|
|
|
|
func (s *SetupService) IsOnboarding(ctx context.Context) (bool, error) {
|
2019-05-09 17:41:14 +00:00
|
|
|
u, err := NewURL(s.Addr, setupPath)
|
2018-10-02 17:21:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2019-05-09 17:41:14 +00:00
|
|
|
hc := NewClient(u.Scheme, s.InsecureSkipVerify)
|
2018-10-02 17:21:36 +00:00
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2019-01-18 22:32:53 +00:00
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2018-10-02 17:21:36 +00:00
|
|
|
if err := CheckError(resp); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
var ir isOnboardingResponse
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&ir); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return ir.Allowed, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate OnboardingResults.
|
|
|
|
func (s *SetupService) Generate(ctx context.Context, or *platform.OnboardingRequest) (*platform.OnboardingResults, error) {
|
2019-05-09 17:41:14 +00:00
|
|
|
u, err := NewURL(s.Addr, setupPath)
|
2018-10-02 17:21:36 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
octets, err := json.Marshal(or)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", u.String(), bytes.NewReader(octets))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
2019-05-09 17:41:14 +00:00
|
|
|
hc := NewClient(u.Scheme, s.InsecureSkipVerify)
|
2018-10-02 17:21:36 +00:00
|
|
|
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-03 14:51:14 +00:00
|
|
|
defer resp.Body.Close()
|
2018-10-02 17:21:36 +00:00
|
|
|
// TODO(jsternberg): Should this check for a 201 explicitly?
|
2019-01-24 01:02:37 +00:00
|
|
|
if err := CheckError(resp); err != nil {
|
2018-10-02 17:21:36 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var oResp onboardingResponse
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&oResp); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-02-07 23:19:30 +00:00
|
|
|
bkt, err := oResp.Bucket.toInfluxDB()
|
2018-10-03 14:51:14 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-02 17:21:36 +00:00
|
|
|
return &platform.OnboardingResults{
|
2018-10-03 14:51:14 +00:00
|
|
|
Org: &oResp.Organization.Organization,
|
2018-12-28 23:02:19 +00:00
|
|
|
User: &oResp.User.User,
|
|
|
|
Auth: oResp.Auth.toPlatform(),
|
2018-10-03 14:51:14 +00:00
|
|
|
Bucket: bkt,
|
2018-10-02 17:21:36 +00:00
|
|
|
}, nil
|
|
|
|
}
|