feat(pkger): extend stacks with ability to add resources without applying

references: #18646
pull/18758/head
Johnny Steenbergen 2020-06-26 13:58:22 -07:00 committed by Johnny Steenbergen
parent 0f7ab73199
commit fdcf8000f6
8 changed files with 236 additions and 36 deletions

View File

@ -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)

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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(),

View File

@ -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 (

View File

@ -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