influxdb/pkger/http_server_packages_deprec...

434 lines
10 KiB
Go

package pkger
import (
"fmt"
"net/http"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/influxdata/influxdb/v2"
pctx "github.com/influxdata/influxdb/v2/context"
kithttp "github.com/influxdata/influxdb/v2/kit/transport/http"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
)
const RoutePrefixPackages = "/api/v2/packages"
// HTTPServerPackages is a server that manages the packages HTTP transport. These
// endpoints are to be sunset and replaced by the templates and stacks endpoints.
type HTTPServerPackages struct {
chi.Router
api *kithttp.API
logger *zap.Logger
svc SVC
}
// NewHTTPServerPackages constructs a new http server.
func NewHTTPServerPackages(log *zap.Logger, svc SVC) *HTTPServerPackages {
svr := &HTTPServerPackages{
api: kithttp.NewAPI(kithttp.WithLog(log)),
logger: log,
svc: svc,
}
exportAllowContentTypes := middleware.AllowContentType("text/yml", "application/x-yaml", "application/json")
setJSONContentType := middleware.SetHeader("Content-Type", "application/json; charset=utf-8")
r := chi.NewRouter()
r.Use(
middleware.SetHeader("Sunset", "Thurs, 30 July 2020 17:00:00 UTC"),
)
{
r.With(exportAllowContentTypes).Post("/", svr.export)
r.With(setJSONContentType).Post("/apply", svr.apply)
r.Route("/stacks", func(r chi.Router) {
r.Post("/", svr.createStack)
r.Get("/", svr.listStacks)
r.Route("/{stack_id}", func(r chi.Router) {
r.Get("/", svr.readStack)
r.Delete("/", svr.deleteStack)
r.Patch("/", svr.updateStack)
r.With(exportAllowContentTypes).Get("/export", svr.exportStack)
})
})
}
svr.Router = r
return svr
}
// Prefix provides the prefix to this route tree.
func (s *HTTPServerPackages) Prefix() string {
return RoutePrefixPackages
}
func (s *HTTPServerPackages) listStacks(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
rawOrgID := q.Get("orgID")
orgID, err := influxdb.IDFromString(rawOrgID)
if err != nil {
s.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: fmt.Sprintf("organization id[%q] is invalid", rawOrgID),
Err: err,
})
return
}
if err := r.ParseForm(); err != nil {
s.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: "failed to parse form from encoded url",
Err: err,
})
return
}
filter := ListFilter{
Names: r.Form["name"],
}
for _, idRaw := range r.Form["stackID"] {
id, err := influxdb.IDFromString(idRaw)
if err != nil {
s.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: fmt.Sprintf("stack ID[%q] provided is invalid", idRaw),
Err: err,
})
return
}
filter.StackIDs = append(filter.StackIDs, *id)
}
stacks, err := s.svc.ListStacks(r.Context(), *orgID, filter)
if err != nil {
s.api.Err(w, r, err)
return
}
if stacks == nil {
stacks = []Stack{}
}
out := make([]RespStack, 0, len(stacks))
for _, st := range stacks {
out = append(out, convertStackToRespStack(st))
}
s.api.Respond(w, r, http.StatusOK, RespListStacks{
Stacks: out,
})
}
func (s *HTTPServerPackages) createStack(w http.ResponseWriter, r *http.Request) {
var reqBody ReqCreateStack
if err := s.api.DecodeJSON(r.Body, &reqBody); err != nil {
s.api.Err(w, r, err)
return
}
defer r.Body.Close()
auth, err := pctx.GetAuthorizer(r.Context())
if err != nil {
s.api.Err(w, r, err)
return
}
stack, err := s.svc.InitStack(r.Context(), auth.GetUserID(), StackCreate{
OrgID: reqBody.orgID(),
Name: reqBody.Name,
Description: reqBody.Description,
TemplateURLs: reqBody.URLs,
})
if err != nil {
s.api.Err(w, r, err)
return
}
s.api.Respond(w, r, http.StatusCreated, convertStackToRespStack(stack))
}
func (s *HTTPServerPackages) deleteStack(w http.ResponseWriter, r *http.Request) {
orgID, err := getRequiredOrgIDFromQuery(r.URL.Query())
if err != nil {
s.api.Err(w, r, err)
return
}
stackID, err := stackIDFromReq(r)
if err != nil {
s.api.Err(w, r, err)
return
}
auth, err := pctx.GetAuthorizer(r.Context())
if err != nil {
s.api.Err(w, r, err)
return
}
userID := auth.GetUserID()
err = s.svc.DeleteStack(r.Context(), struct{ OrgID, UserID, StackID influxdb.ID }{
OrgID: orgID,
UserID: userID,
StackID: stackID,
})
if err != nil {
s.api.Err(w, r, err)
return
}
s.api.Respond(w, r, http.StatusNoContent, nil)
}
func (s *HTTPServerPackages) exportStack(w http.ResponseWriter, r *http.Request) {
stackID, err := stackIDFromReq(r)
if err != nil {
s.api.Err(w, r, err)
return
}
pkg, err := s.svc.Export(r.Context(), ExportWithStackID(stackID))
if err != nil {
s.api.Err(w, r, err)
return
}
encoding := templateEncoding(r.Header.Get("Accept"))
b, err := pkg.Encode(encoding)
if err != nil {
s.api.Err(w, r, err)
return
}
switch encoding {
case EncodingYAML:
w.Header().Set("Content-Type", "application/x-yaml")
default:
w.Header().Set("Content-Type", "application/json; charset=utf-8")
}
s.api.Write(w, http.StatusOK, b)
}
func (s *HTTPServerPackages) readStack(w http.ResponseWriter, r *http.Request) {
stackID, err := stackIDFromReq(r)
if err != nil {
s.api.Err(w, r, err)
return
}
stack, err := s.svc.ReadStack(r.Context(), stackID)
if err != nil {
s.api.Err(w, r, err)
return
}
s.api.Respond(w, r, http.StatusOK, convertStackToRespStack(stack))
}
func (s *HTTPServerPackages) updateStack(w http.ResponseWriter, r *http.Request) {
var req ReqUpdateStack
if err := s.api.DecodeJSON(r.Body, &req); err != nil {
s.api.Err(w, r, err)
return
}
stackID, err := stackIDFromReq(r)
if err != nil {
s.api.Err(w, r, err)
return
}
update := StackUpdate{
ID: stackID,
Name: req.Name,
Description: req.Description,
TemplateURLs: append(req.TemplateURLs, req.URLs...),
}
for _, res := range req.AdditionalResources {
id, err := influxdb.IDFromString(res.ID)
if err != nil {
s.api.Err(w, r, influxErr(influxdb.EInvalid, err, fmt.Sprintf("stack resource id %q", res.ID)))
return
}
update.AdditionalResources = append(update.AdditionalResources, StackAdditionalResource{
APIVersion: APIVersion,
ID: *id,
Kind: res.Kind,
MetaName: res.MetaName,
})
}
stack, err := s.svc.UpdateStack(r.Context(), update)
if err != nil {
s.api.Err(w, r, err)
return
}
s.api.Respond(w, r, http.StatusOK, convertStackToRespStack(stack))
}
func (s *HTTPServerPackages) export(w http.ResponseWriter, r *http.Request) {
var reqBody ReqExport
if err := s.api.DecodeJSON(r.Body, &reqBody); err != nil {
s.api.Err(w, r, err)
return
}
defer r.Body.Close()
opts := []ExportOptFn{
ExportWithExistingResources(reqBody.Resources...),
}
for _, orgIDStr := range reqBody.OrgIDs {
orgID, err := influxdb.IDFromString(orgIDStr.OrgID)
if err != nil {
continue
}
opts = append(opts, ExportWithAllOrgResources(ExportByOrgIDOpt{
OrgID: *orgID,
LabelNames: orgIDStr.Filters.ByLabel,
ResourceKinds: orgIDStr.Filters.ByResourceKind,
}))
}
if reqBody.StackID != "" {
stackID, err := influxdb.IDFromString(reqBody.StackID)
if err != nil {
s.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: fmt.Sprintf("invalid stack ID provided: %q", reqBody.StackID),
})
return
}
opts = append(opts, ExportWithStackID(*stackID))
}
newPkg, err := s.svc.Export(r.Context(), opts...)
if err != nil {
s.api.Err(w, r, err)
return
}
resp := RespExport(newPkg.Objects)
if resp == nil {
resp = []Object{}
}
var enc encoder
switch templateEncoding(r.Header.Get("Accept")) {
case EncodingYAML:
enc = yaml.NewEncoder(w)
w.Header().Set("Content-Type", "application/x-yaml")
default:
enc = newJSONEnc(w)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
}
s.encResp(w, r, enc, http.StatusOK, resp)
}
func (s *HTTPServerPackages) apply(w http.ResponseWriter, r *http.Request) {
var reqBody ReqApply
encoding, err := decodeWithEncoding(r, &reqBody)
if err != nil {
s.api.Err(w, r, newDecodeErr(encoding.String(), err))
return
}
orgID, err := influxdb.IDFromString(reqBody.OrgID)
if err != nil {
s.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: fmt.Sprintf("invalid organization ID provided: %q", reqBody.OrgID),
})
return
}
var stackID influxdb.ID
if reqBody.StackID != nil {
if err := stackID.DecodeFromString(*reqBody.StackID); err != nil {
s.api.Err(w, r, &influxdb.Error{
Code: influxdb.EInvalid,
Msg: fmt.Sprintf("invalid stack ID provided: %q", *reqBody.StackID),
})
return
}
}
parsedPkg, err := reqBody.Templates(encoding)
if err != nil {
s.api.Err(w, r, &influxdb.Error{
Code: influxdb.EUnprocessableEntity,
Err: err,
})
return
}
actions, err := reqBody.validActions()
if err != nil {
s.api.Err(w, r, err)
return
}
applyOpts := []ApplyOptFn{
ApplyWithEnvRefs(reqBody.EnvRefs),
ApplyWithTemplate(parsedPkg),
ApplyWithStackID(stackID),
}
for _, a := range actions.SkipResources {
applyOpts = append(applyOpts, ApplyWithResourceSkip(a))
}
for _, a := range actions.SkipKinds {
applyOpts = append(applyOpts, ApplyWithKindSkip(a))
}
auth, err := pctx.GetAuthorizer(r.Context())
if err != nil {
s.api.Err(w, r, err)
return
}
userID := auth.GetUserID()
if reqBody.DryRun {
impact, err := s.svc.DryRun(r.Context(), *orgID, userID, applyOpts...)
if IsParseErr(err) {
s.api.Respond(w, r, http.StatusUnprocessableEntity, impactToRespApply(impact, err))
return
}
if err != nil {
s.api.Err(w, r, err)
return
}
s.api.Respond(w, r, http.StatusOK, impactToRespApply(impact, nil))
return
}
applyOpts = append(applyOpts, ApplyWithSecrets(reqBody.Secrets))
impact, err := s.svc.Apply(r.Context(), *orgID, userID, applyOpts...)
if err != nil && !IsParseErr(err) {
s.api.Err(w, r, err)
return
}
s.api.Respond(w, r, http.StatusCreated, impactToRespApply(impact, err))
}
func (s *HTTPServerPackages) encResp(w http.ResponseWriter, r *http.Request, enc encoder, code int, res interface{}) {
w.WriteHeader(code)
if err := enc.Encode(res); err != nil {
s.api.Err(w, r, &influxdb.Error{
Msg: fmt.Sprintf("unable to marshal; Err: %v", err),
Code: influxdb.EInternal,
Err: err,
})
}
}