feat(pkger): extend stacks with ability to add resources without applying
references: #18646pull/18758/head
parent
0f7ab73199
commit
fdcf8000f6
|
@ -419,6 +419,15 @@ func TestLauncher_Pkger(t *testing.T) {
|
|||
assert.Equal(t, "2nd name", st.Name)
|
||||
assert.Equal(t, "2nd desc", st.Description)
|
||||
assert.Equal(t, []string{"http://example.com"}, st.URLs)
|
||||
resources := []pkger.StackResource{
|
||||
{
|
||||
APIVersion: pkger.APIVersion,
|
||||
ID: 1,
|
||||
Kind: pkger.KindBucket,
|
||||
MetaName: "bucket-meta",
|
||||
},
|
||||
}
|
||||
assert.Equal(t, resources, st.Resources)
|
||||
assert.True(t, st.UpdatedAt.After(stack.UpdatedAt))
|
||||
}
|
||||
|
||||
|
@ -427,6 +436,14 @@ func TestLauncher_Pkger(t *testing.T) {
|
|||
Name: strPtr("2nd name"),
|
||||
Description: strPtr("2nd desc"),
|
||||
URLs: []string{"http://example.com"},
|
||||
AdditionalResources: []pkger.StackAdditionalResource{
|
||||
{
|
||||
APIVersion: pkger.APIVersion,
|
||||
ID: 1,
|
||||
Kind: pkger.KindBucket,
|
||||
MetaName: "bucket-meta",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assertStack(t, updStack)
|
||||
|
|
|
@ -4818,10 +4818,22 @@ paths:
|
|||
type: string
|
||||
description:
|
||||
type: string
|
||||
urls:
|
||||
templateURLs:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
additionalResources:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
resourceID:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
templateMetaName:
|
||||
type: string
|
||||
required: ["kind","resourceID"]
|
||||
responses:
|
||||
"200":
|
||||
description: Influx stack updated
|
||||
|
|
|
@ -394,15 +394,19 @@ func (ex *resourceExporter) getEndpointRule(ctx context.Context, id influxdb.ID)
|
|||
}
|
||||
|
||||
func (ex *resourceExporter) uniqName() string {
|
||||
return uniqMetaName(ex.nameGen, ex.mPkgNames)
|
||||
}
|
||||
|
||||
func uniqMetaName(nameGen NameGenerator, existingNames map[string]bool) string {
|
||||
uuid := strings.ToLower(idGenerator.ID().String())
|
||||
name := uuid
|
||||
for i := 1; i < 250; i++ {
|
||||
name := fmt.Sprintf("%s-%s", ex.nameGen(), uuid[10:])
|
||||
if !ex.mPkgNames[name] {
|
||||
name = fmt.Sprintf("%s-%s", nameGen(), uuid[10:])
|
||||
if !existingNames[name] {
|
||||
return name
|
||||
}
|
||||
}
|
||||
// if all else fails, generate a UUID for the name
|
||||
return uuid
|
||||
return name
|
||||
}
|
||||
|
||||
func findDashboardByIDFull(ctx context.Context, dashSVC influxdb.DashboardService, id influxdb.ID) (*influxdb.Dashboard, error) {
|
||||
|
|
|
@ -87,9 +87,16 @@ func (s *HTTPRemoteService) ReadStack(ctx context.Context, id influxdb.ID) (Stac
|
|||
|
||||
func (s *HTTPRemoteService) UpdateStack(ctx context.Context, upd StackUpdate) (Stack, error) {
|
||||
reqBody := ReqUpdateStack{
|
||||
Name: upd.Name,
|
||||
Description: upd.Description,
|
||||
URLs: upd.URLs,
|
||||
Name: upd.Name,
|
||||
Description: upd.Description,
|
||||
TemplateURLs: upd.URLs,
|
||||
}
|
||||
for _, r := range upd.AdditionalResources {
|
||||
reqBody.AdditionalResources = append(reqBody.AdditionalResources, ReqUpdateStackResource{
|
||||
ID: r.ID.String(),
|
||||
MetaName: r.MetaName,
|
||||
Kind: r.Kind,
|
||||
})
|
||||
}
|
||||
|
||||
var respBody RespStack
|
||||
|
|
|
@ -320,12 +320,25 @@ func (s *HTTPServer) readStack(w http.ResponseWriter, r *http.Request) {
|
|||
s.api.Respond(w, r, http.StatusOK, convertStackToRespStack(stack))
|
||||
}
|
||||
|
||||
// ReqUpdateStack is the request body for updating a stack.
|
||||
type ReqUpdateStack struct {
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
URLs []string `json:"urls"`
|
||||
}
|
||||
type (
|
||||
// ReqUpdateStack is the request body for updating a stack.
|
||||
ReqUpdateStack struct {
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
TemplateURLs []string `json:"templateURLs"`
|
||||
AdditionalResources []ReqUpdateStackResource `json:"additionalResources"`
|
||||
|
||||
// Deprecating the urls field and replacing with templateURLs field.
|
||||
// This is remaining here for backwards compatibility.
|
||||
URLs []string `json:"urls"`
|
||||
}
|
||||
|
||||
ReqUpdateStackResource struct {
|
||||
ID string `json:"resourceID"`
|
||||
Kind Kind `json:"kind"`
|
||||
MetaName string `json:"templateMetaName"`
|
||||
}
|
||||
)
|
||||
|
||||
func (s *HTTPServer) updateStack(w http.ResponseWriter, r *http.Request) {
|
||||
var req ReqUpdateStack
|
||||
|
@ -340,12 +353,27 @@ func (s *HTTPServer) updateStack(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
stack, err := s.svc.UpdateStack(r.Context(), StackUpdate{
|
||||
update := StackUpdate{
|
||||
ID: stackID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
URLs: req.URLs,
|
||||
})
|
||||
URLs: 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
|
||||
|
|
|
@ -946,7 +946,7 @@ func TestPkgerHTTPServer(t *testing.T) {
|
|||
{
|
||||
name: "update urls field",
|
||||
input: pkger.ReqUpdateStack{
|
||||
URLs: []string{"http://example.com"},
|
||||
TemplateURLs: []string{"http://example.com"},
|
||||
},
|
||||
expectedStack: pkger.RespStack{
|
||||
ID: influxdb.ID(1).String(),
|
||||
|
@ -959,9 +959,9 @@ func TestPkgerHTTPServer(t *testing.T) {
|
|||
{
|
||||
name: "update all fields",
|
||||
input: pkger.ReqUpdateStack{
|
||||
Name: strPtr("name"),
|
||||
Description: strPtr("desc"),
|
||||
URLs: []string{"http://example.com"},
|
||||
Name: strPtr("name"),
|
||||
Description: strPtr("desc"),
|
||||
TemplateURLs: []string{"http://example.com"},
|
||||
},
|
||||
expectedStack: pkger.RespStack{
|
||||
ID: influxdb.ID(1).String(),
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
ierrors "github.com/influxdata/influxdb/v2/kit/errors"
|
||||
icheck "github.com/influxdata/influxdb/v2/notification/check"
|
||||
"github.com/influxdata/influxdb/v2/notification/rule"
|
||||
"github.com/influxdata/influxdb/v2/pkger/internal/wordplay"
|
||||
"github.com/influxdata/influxdb/v2/snowflake"
|
||||
"github.com/influxdata/influxdb/v2/task/options"
|
||||
"go.uber.org/zap"
|
||||
|
@ -59,10 +60,18 @@ type (
|
|||
|
||||
// StackUpdate provides a means to update an existing stack.
|
||||
StackUpdate struct {
|
||||
ID influxdb.ID
|
||||
Name *string
|
||||
Description *string
|
||||
URLs []string
|
||||
ID influxdb.ID
|
||||
Name *string
|
||||
Description *string
|
||||
URLs []string
|
||||
AdditionalResources []StackAdditionalResource
|
||||
}
|
||||
|
||||
StackAdditionalResource struct {
|
||||
APIVersion string
|
||||
ID influxdb.ID
|
||||
Kind Kind
|
||||
MetaName string
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -89,6 +98,7 @@ type serviceOpt struct {
|
|||
|
||||
applyReqLimit int
|
||||
idGen influxdb.IDGenerator
|
||||
nameGen NameGenerator
|
||||
timeGen influxdb.TimeGenerator
|
||||
store Store
|
||||
|
||||
|
@ -157,6 +167,19 @@ func WithDashboardSVC(dashSVC influxdb.DashboardService) ServiceSetterFn {
|
|||
}
|
||||
}
|
||||
|
||||
// WithLabelSVC sets the label service.
|
||||
func WithLabelSVC(labelSVC influxdb.LabelService) ServiceSetterFn {
|
||||
return func(opt *serviceOpt) {
|
||||
opt.labelSVC = labelSVC
|
||||
}
|
||||
}
|
||||
|
||||
func withNameGen(nameGen NameGenerator) ServiceSetterFn {
|
||||
return func(opt *serviceOpt) {
|
||||
opt.nameGen = nameGen
|
||||
}
|
||||
}
|
||||
|
||||
// WithNotificationEndpointSVC sets the endpoint notification service.
|
||||
func WithNotificationEndpointSVC(endpointSVC influxdb.NotificationEndpointService) ServiceSetterFn {
|
||||
return func(opt *serviceOpt) {
|
||||
|
@ -178,13 +201,6 @@ func WithOrganizationService(orgSVC influxdb.OrganizationService) ServiceSetterF
|
|||
}
|
||||
}
|
||||
|
||||
// WithLabelSVC sets the label service.
|
||||
func WithLabelSVC(labelSVC influxdb.LabelService) ServiceSetterFn {
|
||||
return func(opt *serviceOpt) {
|
||||
opt.labelSVC = labelSVC
|
||||
}
|
||||
}
|
||||
|
||||
// WithSecretSVC sets the secret service.
|
||||
func WithSecretSVC(secretSVC influxdb.SecretService) ServiceSetterFn {
|
||||
return func(opt *serviceOpt) {
|
||||
|
@ -230,6 +246,7 @@ type Service struct {
|
|||
// internal dependencies
|
||||
applyReqLimit int
|
||||
idGen influxdb.IDGenerator
|
||||
nameGen NameGenerator
|
||||
store Store
|
||||
timeGen influxdb.TimeGenerator
|
||||
|
||||
|
@ -255,6 +272,7 @@ func NewService(opts ...ServiceSetterFn) *Service {
|
|||
logger: zap.NewNop(),
|
||||
applyReqLimit: 5,
|
||||
idGen: snowflake.NewDefaultIDGenerator(),
|
||||
nameGen: wordplay.GetRandomName,
|
||||
timeGen: influxdb.RealTimeGenerator{},
|
||||
}
|
||||
for _, o := range opts {
|
||||
|
@ -266,6 +284,7 @@ func NewService(opts ...ServiceSetterFn) *Service {
|
|||
|
||||
applyReqLimit: opt.applyReqLimit,
|
||||
idGen: opt.idGen,
|
||||
nameGen: opt.nameGen,
|
||||
store: opt.store,
|
||||
timeGen: opt.timeGen,
|
||||
|
||||
|
@ -369,6 +388,15 @@ func (s *Service) UpdateStack(ctx context.Context, upd StackUpdate) (Stack, erro
|
|||
return Stack{}, err
|
||||
}
|
||||
|
||||
updatedStack := s.applyStackUpdate(existing, upd)
|
||||
if err := s.store.UpdateStack(ctx, updatedStack); err != nil {
|
||||
return Stack{}, err
|
||||
}
|
||||
|
||||
return updatedStack, nil
|
||||
}
|
||||
|
||||
func (s *Service) applyStackUpdate(existing Stack, upd StackUpdate) Stack {
|
||||
if upd.Name != nil {
|
||||
existing.Name = *upd.Name
|
||||
}
|
||||
|
@ -380,11 +408,38 @@ func (s *Service) UpdateStack(ctx context.Context, upd StackUpdate) (Stack, erro
|
|||
}
|
||||
existing.UpdatedAt = s.timeGen.Now()
|
||||
|
||||
if err := s.store.UpdateStack(ctx, existing); err != nil {
|
||||
return Stack{}, err
|
||||
type key struct {
|
||||
k Kind
|
||||
id influxdb.ID
|
||||
}
|
||||
mExistingResources := make(map[key]bool)
|
||||
mExistingNames := make(map[string]bool)
|
||||
for _, r := range existing.Resources {
|
||||
k := key{k: r.Kind, id: r.ID}
|
||||
mExistingResources[k] = true
|
||||
mExistingNames[r.MetaName] = true
|
||||
}
|
||||
for _, r := range upd.AdditionalResources {
|
||||
k := key{k: r.Kind, id: r.ID}
|
||||
if mExistingResources[k] {
|
||||
continue
|
||||
}
|
||||
|
||||
return existing, nil
|
||||
sr := StackResource{
|
||||
APIVersion: r.APIVersion,
|
||||
ID: r.ID,
|
||||
Kind: r.Kind,
|
||||
}
|
||||
|
||||
metaName := r.MetaName
|
||||
if metaName == "" || mExistingNames[metaName] {
|
||||
metaName = uniqMetaName(s.nameGen, mExistingNames)
|
||||
}
|
||||
mExistingNames[metaName] = true
|
||||
sr.MetaName = metaName
|
||||
existing.Resources = append(existing.Resources, sr)
|
||||
}
|
||||
return existing
|
||||
}
|
||||
|
||||
type (
|
||||
|
|
|
@ -3552,7 +3552,7 @@ func TestService(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "update all",
|
||||
name: "update first 3",
|
||||
input: StackUpdate{
|
||||
Name: strPtr("name"),
|
||||
Description: strPtr("desc"),
|
||||
|
@ -3564,10 +3564,86 @@ func TestService(t *testing.T) {
|
|||
URLs: []string{"http://example.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update with metaname collisions",
|
||||
input: StackUpdate{
|
||||
Name: strPtr("name"),
|
||||
Description: strPtr("desc"),
|
||||
URLs: []string{"http://example.com"},
|
||||
AdditionalResources: []StackAdditionalResource{
|
||||
{
|
||||
APIVersion: APIVersion,
|
||||
ID: 1,
|
||||
Kind: KindLabel,
|
||||
MetaName: "meta-label",
|
||||
},
|
||||
{
|
||||
APIVersion: APIVersion,
|
||||
ID: 2,
|
||||
Kind: KindLabel,
|
||||
MetaName: "meta-label",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: Stack{
|
||||
Name: "name",
|
||||
Description: "desc",
|
||||
URLs: []string{"http://example.com"},
|
||||
Resources: []StackResource{
|
||||
{
|
||||
APIVersion: APIVersion,
|
||||
ID: 1,
|
||||
Kind: KindLabel,
|
||||
MetaName: "meta-label",
|
||||
},
|
||||
{
|
||||
APIVersion: APIVersion,
|
||||
ID: 1,
|
||||
Kind: KindLabel,
|
||||
MetaName: "collision-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update all",
|
||||
input: StackUpdate{
|
||||
Name: strPtr("name"),
|
||||
Description: strPtr("desc"),
|
||||
URLs: []string{"http://example.com"},
|
||||
AdditionalResources: []StackAdditionalResource{
|
||||
{
|
||||
APIVersion: APIVersion,
|
||||
ID: 1,
|
||||
Kind: KindLabel,
|
||||
MetaName: "meta-label",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: Stack{
|
||||
Name: "name",
|
||||
Description: "desc",
|
||||
URLs: []string{"http://example.com"},
|
||||
Resources: []StackResource{
|
||||
{
|
||||
APIVersion: APIVersion,
|
||||
ID: 1,
|
||||
Kind: KindLabel,
|
||||
MetaName: "meta-label",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
fn := func(t *testing.T) {
|
||||
var collisions int
|
||||
nameGenFn := func() string {
|
||||
collisions++
|
||||
return "collision-" + strconv.Itoa(collisions)
|
||||
}
|
||||
|
||||
svc := newTestService(
|
||||
WithTimeGenerator(newTimeGen(now)),
|
||||
WithStore(&fakeStore{
|
||||
|
@ -3581,6 +3657,7 @@ func TestService(t *testing.T) {
|
|||
return nil
|
||||
},
|
||||
}),
|
||||
withNameGen(nameGenFn),
|
||||
)
|
||||
|
||||
tt.input.ID = 33
|
||||
|
|
Loading…
Reference in New Issue