influxdb/pkger/http_remote_service.go

347 lines
9.1 KiB
Go

package pkger
import (
"context"
"encoding/json"
"net/http"
ihttp "github.com/influxdata/influxdb/v2/http"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
"github.com/influxdata/influxdb/v2/pkg/httpc"
)
// HTTPRemoteService provides an http client that is fluent in all things template.
type HTTPRemoteService struct {
Client *httpc.Client
}
var _ SVC = (*HTTPRemoteService)(nil)
func (s *HTTPRemoteService) InitStack(ctx context.Context, userID platform.ID, stack StackCreate) (Stack, error) {
reqBody := ReqCreateStack{
OrgID: stack.OrgID.String(),
Name: stack.Name,
Description: stack.Description,
URLs: stack.TemplateURLs,
}
var respBody RespStack
err := s.Client.
PostJSON(reqBody, RoutePrefixStacks).
DecodeJSON(&respBody).
Do(ctx)
if err != nil {
return Stack{}, err
}
return convertRespStackToStack(respBody)
}
func (s *HTTPRemoteService) UninstallStack(ctx context.Context, identifiers struct{ OrgID, UserID, StackID platform.ID }) (Stack, error) {
var respBody RespStack
err := s.Client.
Post(httpc.BodyEmpty, RoutePrefixStacks, identifiers.StackID.String(), "/uninstall").
QueryParams([2]string{"orgID", identifiers.OrgID.String()}).
DecodeJSON(&respBody).
Do(ctx)
if err != nil {
return Stack{}, err
}
return convertRespStackToStack(respBody)
}
func (s *HTTPRemoteService) DeleteStack(ctx context.Context, identifiers struct{ OrgID, UserID, StackID platform.ID }) error {
return s.Client.
Delete(RoutePrefixStacks, identifiers.StackID.String()).
QueryParams([2]string{"orgID", identifiers.OrgID.String()}).
Do(ctx)
}
func (s *HTTPRemoteService) ListStacks(ctx context.Context, orgID platform.ID, f ListFilter) ([]Stack, error) {
queryParams := [][2]string{{"orgID", orgID.String()}}
for _, name := range f.Names {
queryParams = append(queryParams, [2]string{"name", name})
}
for _, stackID := range f.StackIDs {
queryParams = append(queryParams, [2]string{"stackID", stackID.String()})
}
var resp RespListStacks
err := s.Client.
Get(RoutePrefixStacks).
QueryParams(queryParams...).
DecodeJSON(&resp).
Do(ctx)
if err != nil {
return nil, err
}
out := make([]Stack, 0, len(resp.Stacks))
for _, st := range resp.Stacks {
stack, err := convertRespStackToStack(st)
if err != nil {
continue
}
out = append(out, stack)
}
return out, nil
}
func (s *HTTPRemoteService) ReadStack(ctx context.Context, id platform.ID) (Stack, error) {
var respBody RespStack
err := s.Client.
Get(RoutePrefixStacks, id.String()).
DecodeJSON(&respBody).
Do(ctx)
if err != nil {
return Stack{}, err
}
return convertRespStackToStack(respBody)
}
func (s *HTTPRemoteService) UpdateStack(ctx context.Context, upd StackUpdate) (Stack, error) {
reqBody := ReqUpdateStack{
Name: upd.Name,
Description: upd.Description,
TemplateURLs: upd.TemplateURLs,
}
for _, r := range upd.AdditionalResources {
reqBody.AdditionalResources = append(reqBody.AdditionalResources, ReqUpdateStackResource{
ID: r.ID.String(),
MetaName: r.MetaName,
Kind: r.Kind,
})
}
var respBody RespStack
err := s.Client.
PatchJSON(reqBody, RoutePrefixStacks, upd.ID.String()).
DecodeJSON(&respBody).
Do(ctx)
if err != nil {
return Stack{}, err
}
return convertRespStackToStack(respBody)
}
// Export will produce a template from the parameters provided.
func (s *HTTPRemoteService) Export(ctx context.Context, opts ...ExportOptFn) (*Template, error) {
opt, err := exportOptFromOptFns(opts)
if err != nil {
return nil, err
}
var orgIDs []ReqExportOrgIDOpt
for _, org := range opt.OrgIDs {
orgIDs = append(orgIDs, ReqExportOrgIDOpt{
OrgID: org.OrgID.String(),
Filters: struct {
ByLabel []string `json:"byLabel"`
ByResourceKind []Kind `json:"byResourceKind"`
}{
ByLabel: org.LabelNames,
ByResourceKind: org.ResourceKinds,
},
})
}
reqBody := ReqExport{
StackID: opt.StackID.String(),
OrgIDs: orgIDs,
Resources: opt.Resources,
}
var newTemplate *Template
err = s.Client.
PostJSON(reqBody, RoutePrefixTemplates, "/export").
Decode(func(resp *http.Response) error {
t, err := Parse(EncodingJSON, FromReader(resp.Body, "export"))
newTemplate = t
return err
}).
Do(ctx)
if err != nil {
return nil, err
}
if err := newTemplate.Validate(ValidWithoutResources()); err != nil {
return nil, err
}
return newTemplate, nil
}
// DryRun provides a dry run of the template application. The template will be marked verified
// for later calls to Apply. This func will be run on an Apply if it has not been run
// already.
func (s *HTTPRemoteService) DryRun(ctx context.Context, orgID, userID platform.ID, opts ...ApplyOptFn) (ImpactSummary, error) {
return s.apply(ctx, orgID, true, opts...)
}
// Apply will apply all the resources identified in the provided template. The entire template will be applied
// in its entirety. If a failure happens midway then the entire template will be rolled back to the state
// from before the template was applied.
func (s *HTTPRemoteService) Apply(ctx context.Context, orgID, userID platform.ID, opts ...ApplyOptFn) (ImpactSummary, error) {
return s.apply(ctx, orgID, false, opts...)
}
func (s *HTTPRemoteService) apply(ctx context.Context, orgID platform.ID, dryRun bool, opts ...ApplyOptFn) (ImpactSummary, error) {
opt := applyOptFromOptFns(opts...)
var rawTemplate ReqRawTemplate
for _, t := range opt.Templates {
b, err := t.Encode(EncodingJSON)
if err != nil {
return ImpactSummary{}, err
}
rawTemplate.Template = b
rawTemplate.Sources = t.sources
rawTemplate.ContentType = EncodingJSON.String()
}
reqBody := ReqApply{
OrgID: orgID.String(),
DryRun: dryRun,
EnvRefs: opt.EnvRefs,
Secrets: opt.MissingSecrets,
RawTemplate: rawTemplate,
}
if opt.StackID != 0 {
stackID := opt.StackID.String()
reqBody.StackID = &stackID
}
for act := range opt.ResourcesToSkip {
b, err := json.Marshal(act)
if err != nil {
return ImpactSummary{}, influxErr(errors.EInvalid, err)
}
reqBody.RawActions = append(reqBody.RawActions, ReqRawAction{
Action: string(ActionTypeSkipResource),
Properties: b,
})
}
for kind := range opt.KindsToSkip {
b, err := json.Marshal(ActionSkipKind{Kind: kind})
if err != nil {
return ImpactSummary{}, influxErr(errors.EInvalid, err)
}
reqBody.RawActions = append(reqBody.RawActions, ReqRawAction{
Action: string(ActionTypeSkipKind),
Properties: b,
})
}
var resp RespApply
err := s.Client.
PostJSON(reqBody, RoutePrefixTemplates, "/apply").
DecodeJSON(&resp).
StatusFn(func(resp *http.Response) error {
// valid response code when the template itself has parser errors.
// we short circuit on that and allow that response to pass through
// but consume the initial implementation if that does not hold.
if resp.StatusCode == http.StatusUnprocessableEntity {
return nil
}
return ihttp.CheckError(resp)
}).
Do(ctx)
if err != nil {
return ImpactSummary{}, err
}
impact := ImpactSummary{
Sources: resp.Sources,
Diff: resp.Diff,
Summary: resp.Summary,
}
if stackID, err := platform.IDFromString(resp.StackID); err == nil {
impact.StackID = *stackID
}
return impact, NewParseError(resp.Errors...)
}
func convertRespStackToStack(respStack RespStack) (Stack, error) {
newStack := Stack{
CreatedAt: respStack.CreatedAt,
}
id, err := platform.IDFromString(respStack.ID)
if err != nil {
return Stack{}, err
}
newStack.ID = *id
orgID, err := platform.IDFromString(respStack.OrgID)
if err != nil {
return Stack{}, err
}
newStack.OrgID = *orgID
events := respStack.Events
if len(events) == 0 && !respStack.UpdatedAt.IsZero() {
events = append(events, respStack.RespStackEvent)
}
for _, respEv := range events {
ev, err := convertRespStackEvent(respEv)
if err != nil {
return Stack{}, err
}
newStack.Events = append(newStack.Events, ev)
}
return newStack, nil
}
func convertRespStackEvent(ev RespStackEvent) (StackEvent, error) {
res, err := convertRespStackResources(ev.Resources)
if err != nil {
return StackEvent{}, err
}
eventType := StackEventCreate
switch ev.EventType {
case "uninstall", "delete": // delete is included to maintain backwards compatibility
eventType = StackEventUninstalled
case "update":
eventType = StackEventUpdate
}
return StackEvent{
EventType: eventType,
Name: ev.Name,
Description: ev.Description,
Resources: res,
Sources: ev.Sources,
TemplateURLs: ev.URLs,
UpdatedAt: ev.UpdatedAt,
}, nil
}
func convertRespStackResources(resources []RespStackResource) ([]StackResource, error) {
out := make([]StackResource, 0, len(resources))
for _, r := range resources {
sr := StackResource{
APIVersion: r.APIVersion,
MetaName: r.MetaName,
Kind: r.Kind,
}
for _, a := range r.Associations {
sr.Associations = append(sr.Associations, StackResourceAssociation(a))
}
resID, err := platform.IDFromString(r.ID)
if err != nil {
return nil, influxErr(errors.EInternal, err)
}
sr.ID = *resID
out = append(out, sr)
}
return out, nil
}