diff --git a/cmd/influxd/launcher/pkger_test.go b/cmd/influxd/launcher/pkger_test.go index cf7aa09e58..fc75649a96 100644 --- a/cmd/influxd/launcher/pkger_test.go +++ b/cmd/influxd/launcher/pkger_test.go @@ -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) diff --git a/http/swagger.yml b/http/swagger.yml index f0c8803200..ec638806ad 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -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 diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index ea68f862c6..444acf6ab6 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -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) { diff --git a/pkger/http_remote_service.go b/pkger/http_remote_service.go index f10014235c..8544cde8b0 100644 --- a/pkger/http_remote_service.go +++ b/pkger/http_remote_service.go @@ -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 diff --git a/pkger/http_server.go b/pkger/http_server.go index fa013709e9..d698d9b0bc 100644 --- a/pkger/http_server.go +++ b/pkger/http_server.go @@ -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 diff --git a/pkger/http_server_test.go b/pkger/http_server_test.go index 84abefcba3..613fcf0863 100644 --- a/pkger/http_server_test.go +++ b/pkger/http_server_test.go @@ -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(), diff --git a/pkger/service.go b/pkger/service.go index 74f46a7976..bdc956b9bc 100644 --- a/pkger/service.go +++ b/pkger/service.go @@ -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 ( diff --git a/pkger/service_test.go b/pkger/service_test.go index adaf5729f2..074a2e8d87 100644 --- a/pkger/service_test.go +++ b/pkger/service_test.go @@ -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