From 5eb29e9ed9bf4792bd19bff7dd5ae364a51e138c Mon Sep 17 00:00:00 2001 From: Johnny Steenbergen <jonathansteenbergen@gmail.com> Date: Wed, 6 Nov 2019 16:45:00 -0800 Subject: [PATCH] feat(pkger): add label associations to variables --- cmd/influx/pkg.go | 121 ++++++++- cmd/influxd/launcher/launcher.go | 9 +- http/swagger.yml | 40 ++- pkger/models.go | 175 ++++++++---- pkger/models_test.go | 14 +- pkger/parser.go | 11 +- pkger/parser_test.go | 59 ++++- pkger/service.go | 250 ++++++++++++++++-- pkger/service_test.go | 240 ++++++++++++++--- pkger/testdata/variables_associates_label.yml | 18 ++ 10 files changed, 809 insertions(+), 128 deletions(-) create mode 100644 pkger/testdata/variables_associates_label.yml diff --git a/cmd/influx/pkg.go b/cmd/influx/pkg.go index 71476937aa..eeebd00508 100644 --- a/cmd/influx/pkg.go +++ b/cmd/influx/pkg.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "errors" "fmt" "os" @@ -17,7 +18,6 @@ import ( "github.com/olekukonko/tablewriter" "github.com/spf13/cobra" input "github.com/tcnksm/go-input" - "go.uber.org/zap" ) func pkgCmd() *cobra.Command { @@ -107,7 +107,17 @@ func newPkgerSVC(f Flags) (*pkger.Service, error) { return nil, err } - return pkger.NewService(zap.NewNop(), bucketSVC, labelSVC, dashSVC), nil + varSVC, err := newVariableService(f) + if err != nil { + return nil, err + } + + return pkger.NewService( + pkger.WithBucketSVC(bucketSVC), + pkger.WithDashboardSVC(dashSVC), + pkger.WithLabelSVC(labelSVC), + pkger.WithVariableSVC(varSVC), + ), nil } func newDashboardService(f Flags) (influxdb.DashboardService, error) { @@ -130,6 +140,16 @@ func newLabelService(f Flags) (influxdb.LabelService, error) { }, nil } +func newVariableService(f Flags) (influxdb.VariableService, error) { + if f.local { + return newLocalKVService() + } + return &http.VariableService{ + Addr: f.host, + Token: f.token, + }, nil +} + func pkgFromFile(path string) (*pkger.Pkg, error) { var enc pkger.Encoding switch ext := filepath.Ext(path); ext { @@ -184,9 +204,10 @@ func printPkgDiff(hasColor, hasTableBorders bool, diff pkger.Diff) { return fmt.Sprintf("%s\n%s", red(o), green(n)) } + tablePrintFn := tablePrinterGen(hasColor, hasTableBorders) if labels := diff.Labels; len(labels) > 0 { headers := []string{"New", "ID", "Name", "Color", "Description"} - tablePrinter("LABELS", headers, len(labels), hasColor, hasTableBorders, func(w *tablewriter.Table) { + tablePrintFn("LABELS", headers, len(labels), func(w *tablewriter.Table) { for _, l := range labels { w.Append([]string{ boolDiff(l.IsNew()), @@ -201,7 +222,7 @@ func printPkgDiff(hasColor, hasTableBorders bool, diff pkger.Diff) { if bkts := diff.Buckets; len(bkts) > 0 { headers := []string{"New", "ID", "Name", "Retention Period", "Description"} - tablePrinter("BUCKETS", headers, len(bkts), hasColor, hasTableBorders, func(w *tablewriter.Table) { + tablePrintFn("BUCKETS", headers, len(bkts), func(w *tablewriter.Table) { for _, b := range bkts { w.Append([]string{ boolDiff(b.IsNew()), @@ -216,7 +237,7 @@ func printPkgDiff(hasColor, hasTableBorders bool, diff pkger.Diff) { if dashes := diff.Dashboards; len(dashes) > 0 { headers := []string{"New", "Name", "Description", "Num Charts"} - tablePrinter("DASHBOARDS", headers, len(dashes), hasColor, hasTableBorders, func(w *tablewriter.Table) { + tablePrintFn("DASHBOARDS", headers, len(dashes), func(w *tablewriter.Table) { for _, d := range dashes { w.Append([]string{ boolDiff(true), @@ -228,9 +249,33 @@ func printPkgDiff(hasColor, hasTableBorders bool, diff pkger.Diff) { }) } + if vars := diff.Variables; len(vars) > 0 { + headers := []string{"New", "ID", "Name", "Description", "Arg Type", "Arg Values"} + tablePrintFn("VARIABLES", headers, len(vars), func(w *tablewriter.Table) { + for _, v := range vars { + var oldArgType string + if v.OldArgs != nil { + oldArgType = v.OldArgs.Type + } + var newArgType string + if v.NewArgs != nil { + newArgType = v.NewArgs.Type + } + w.Append([]string{ + boolDiff(v.IsNew()), + v.ID.String(), + v.Name, + strDiff(v.IsNew(), v.OldDesc, v.NewDesc), + strDiff(v.IsNew(), oldArgType, newArgType), + strDiff(v.IsNew(), printVarArgs(v.OldArgs), printVarArgs(v.NewArgs)), + }) + } + }) + } + if len(diff.LabelMappings) > 0 { headers := []string{"New", "Resource Type", "Resource Name", "Resource ID", "Label Name", "Label ID"} - tablePrinter("LABEL MAPPINGS", headers, len(diff.LabelMappings), hasColor, hasTableBorders, func(w *tablewriter.Table) { + tablePrintFn("LABEL MAPPINGS", headers, len(diff.LabelMappings), func(w *tablewriter.Table) { for _, m := range diff.LabelMappings { w.Append([]string{ boolDiff(m.IsNew), @@ -245,10 +290,43 @@ func printPkgDiff(hasColor, hasTableBorders bool, diff pkger.Diff) { } } +func printVarArgs(a *influxdb.VariableArguments) string { + if a == nil { + return "<nil>" + } + if a.Type == "map" { + b, err := json.Marshal(a.Values) + if err != nil { + return "{}" + } + return string(b) + } + if a.Type == "constant" { + vals, ok := a.Values.(influxdb.VariableConstantValues) + if !ok { + return "[]" + } + var out []string + for _, s := range vals { + out = append(out, fmt.Sprintf("%q", s)) + } + return fmt.Sprintf("[%s]", strings.Join(out, " ")) + } + if a.Type == "query" { + qVal, ok := a.Values.(influxdb.VariableQueryValues) + if !ok { + return "" + } + return fmt.Sprintf("language=%q query=%q", qVal.Language, qVal.Query) + } + return "unknown variable argument" +} + func printPkgSummary(hasColor, hasTableBorders bool, sum pkger.Summary) { + tablePrintFn := tablePrinterGen(hasColor, hasTableBorders) if labels := sum.Labels; len(labels) > 0 { headers := []string{"ID", "Name", "Description", "Color"} - tablePrinter("LABELS", headers, len(labels), hasColor, hasTableBorders, func(w *tablewriter.Table) { + tablePrintFn("LABELS", headers, len(labels), func(w *tablewriter.Table) { for _, l := range labels { w.Append([]string{ l.ID.String(), @@ -262,7 +340,7 @@ func printPkgSummary(hasColor, hasTableBorders bool, sum pkger.Summary) { if buckets := sum.Buckets; len(buckets) > 0 { headers := []string{"ID", "Name", "Retention", "Description"} - tablePrinter("BUCKETS", headers, len(buckets), hasColor, hasTableBorders, func(w *tablewriter.Table) { + tablePrintFn("BUCKETS", headers, len(buckets), func(w *tablewriter.Table) { for _, bucket := range buckets { w.Append([]string{ bucket.ID.String(), @@ -276,7 +354,7 @@ func printPkgSummary(hasColor, hasTableBorders bool, sum pkger.Summary) { if dashes := sum.Dashboards; len(dashes) > 0 { headers := []string{"ID", "Name", "Description"} - tablePrinter("DASHBOARDS", headers, len(dashes), hasColor, hasTableBorders, func(w *tablewriter.Table) { + tablePrintFn("DASHBOARDS", headers, len(dashes), func(w *tablewriter.Table) { for _, d := range dashes { w.Append([]string{ d.ID.String(), @@ -287,9 +365,25 @@ func printPkgSummary(hasColor, hasTableBorders bool, sum pkger.Summary) { }) } + if vars := sum.Variables; len(vars) > 0 { + headers := []string{"ID", "Name", "Description", "Arg Type", "Arg Values"} + tablePrintFn("VARIABLES", headers, len(vars), func(w *tablewriter.Table) { + for _, v := range vars { + args := v.Arguments + w.Append([]string{ + v.ID.String(), + v.Name, + v.Description, + args.Type, + printVarArgs(args), + }) + } + }) + } + if mappings := sum.LabelMappings; len(mappings) > 0 { headers := []string{"Resource Type", "Resource Name", "Resource ID", "Label Name", "Label ID"} - tablePrinter("LABEL MAPPINGS", headers, len(mappings), hasColor, hasTableBorders, func(w *tablewriter.Table) { + tablePrintFn("LABEL MAPPINGS", headers, len(mappings), func(w *tablewriter.Table) { for _, m := range mappings { w.Append([]string{ string(m.ResourceType), @@ -303,6 +397,12 @@ func printPkgSummary(hasColor, hasTableBorders bool, sum pkger.Summary) { } } +func tablePrinterGen(hasColor, hasTableBorder bool) func(table string, headers []string, count int, appendFn func(w *tablewriter.Table)) { + return func(table string, headers []string, count int, appendFn func(w *tablewriter.Table)) { + tablePrinter(table, headers, count, hasColor, hasTableBorder, appendFn) + } +} + func tablePrinter(table string, headers []string, count int, hasColor, hasTableBorders bool, appendFn func(w *tablewriter.Table)) { descrCol := -1 for i, h := range headers { @@ -321,7 +421,6 @@ func tablePrinter(table string, headers []string, count int, hasColor, hasTableB alignments = append(alignments, tablewriter.ALIGN_CENTER) } if descrCol != -1 { - w.SetAutoWrapText(false) w.SetColMinWidth(descrCol, 30) alignments[descrCol] = tablewriter.ALIGN_LEFT } diff --git a/cmd/influxd/launcher/launcher.go b/cmd/influxd/launcher/launcher.go index 66e2e56cab..03b1b0aee9 100644 --- a/cmd/influxd/launcher/launcher.go +++ b/cmd/influxd/launcher/launcher.go @@ -839,9 +839,14 @@ func (m *Launcher) run(ctx context.Context) (err error) { var pkgSVC pkger.SVC { - pkgLogger := m.logger.With(zap.String("service", "pkger")) b := m.apibackend - pkgSVC = pkger.NewService(pkgLogger, b.BucketService, b.LabelService, b.DashboardService) + pkgSVC = pkger.NewService( + pkger.WithLogger(m.logger.With(zap.String("service", "pkger"))), + pkger.WithBucketSVC(b.BucketService), + pkger.WithDashboardSVC(b.DashboardService), + pkger.WithLabelSVC(b.LabelService), + pkger.WithVariableSVC(b.VariableService), + ) } var pkgHTTPServer *http.HandlerPkg diff --git a/http/swagger.yml b/http/swagger.yml index 80928cb33c..ff55dfd3b6 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -7186,6 +7186,17 @@ components: type: string labelID: type: string + variables: + type: array + items: + allOf: + - $ref: "#/components/schemas/Variable" + - type: object + properties: + labelAssociations: + type: array + items: + $ref: "#/components/schemas/Label" diff: type: object properties: @@ -7253,6 +7264,23 @@ components: type: string labelName: type: string + variables: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + oldDescription: + type: string + newDescription: + type: string + oldArgs: + $ref: "#/components/schemas/VariableProperties" + newArgs: + $ref: "#/components/schemas/VariableProperties" PkgChart: type: object properties: @@ -8461,11 +8489,7 @@ components: labels: $ref: "#/components/schemas/Labels" arguments: - type: object - oneOf: - - $ref: "#/components/schemas/QueryVariableProperties" - - $ref: "#/components/schemas/ConstantVariableProperties" - - $ref: "#/components/schemas/MapVariableProperties" + $ref: "#/components/schemas/VariableProperties" createdAt: type: string format: date-time @@ -8511,6 +8535,12 @@ components: type: array items: $ref: "#/components/schemas/Variable" + VariableProperties: + type: object + oneOf: + - $ref: "#/components/schemas/QueryVariableProperties" + - $ref: "#/components/schemas/ConstantVariableProperties" + - $ref: "#/components/schemas/MapVariableProperties" ViewProperties: oneOf: - $ref: "#/components/schemas/LinePlusSingleStatProperties" diff --git a/pkger/models.go b/pkger/models.go index 6644e4ae90..eb9e6529be 100644 --- a/pkger/models.go +++ b/pkger/models.go @@ -67,6 +67,7 @@ type Diff struct { Dashboards []DiffDashboard `json:"dashboards"` Labels []DiffLabel `json:"labels"` LabelMappings []DiffLabelMapping `json:"labelMappings"` + Variables []DiffVariable `json:"variables"` } // DiffBucket is a diff of an individual bucket. @@ -163,6 +164,33 @@ type DiffLabelMapping struct { LabelName string `json:"labelName"` } +// DiffVariable is a diff of an individual variable. +type DiffVariable struct { + ID SafeID `json:"id"` + Name string `json:"name"` + OldDesc string `json:"oldDescription"` + NewDesc string `json:"newDescription"` + + OldArgs *influxdb.VariableArguments `json:"oldArgs"` + NewArgs *influxdb.VariableArguments `json:"newArgs"` +} + +func newDiffVariable(v *variable, iv influxdb.Variable) DiffVariable { + return DiffVariable{ + ID: SafeID(iv.ID), + Name: v.Name, + OldDesc: iv.Description, + NewDesc: v.Description, + OldArgs: iv.Arguments, + NewArgs: v.influxVarArgs(), + } +} + +// IsNew indicates whether a pkg variable is going to be new to the platform. +func (d DiffVariable) IsNew() bool { + return d.ID == SafeID(0) +} + // Summary is a definition of all the resources that have or // will be created from a pkg. type Summary struct { @@ -240,6 +268,7 @@ type SummaryLabelMapping struct { // SummaryVariable provides a summary of a pkg variable. type SummaryVariable struct { influxdb.Variable + LabelAssociations []influxdb.Label `json:"labelAssociations"` } type bucket struct { @@ -317,14 +346,68 @@ func (l assocMapVal) dashboard() (*dashboard, bool) { return d, ok } +func (l assocMapVal) variable() (*variable, bool) { + if l.v == nil { + return nil, false + } + v, ok := l.v.(*variable) + return v, ok +} + +type associationMapping struct { + mappings map[assocMapKey]assocMapVal +} + +func (l *associationMapping) setMapping(k assocMapKey, v assocMapVal) { + if l == nil { + return + } + if l.mappings == nil { + l.mappings = make(map[assocMapKey]assocMapVal) + } + l.mappings[k] = v +} + +func (l *associationMapping) setBucketMapping(b *bucket, exists bool) { + key := assocMapKey{ + resType: b.ResourceType(), + name: b.Name, + } + val := assocMapVal{ + exists: exists, + v: b, + } + l.setMapping(key, val) +} + +func (l *associationMapping) setDashboardMapping(d *dashboard) { + key := assocMapKey{ + resType: d.ResourceType(), + name: d.Name, + } + val := assocMapVal{v: d} + l.setMapping(key, val) +} + +func (l *associationMapping) setVariableMapping(v *variable, exists bool) { + key := assocMapKey{ + resType: v.ResourceType(), + name: v.Name, + } + val := assocMapVal{ + exists: exists, + v: v, + } + l.setMapping(key, val) +} + type label struct { id influxdb.ID OrgID influxdb.ID Name string Color string Description string - - mappings map[assocMapKey]assocMapVal + associationMapping // exists provides context for a resource that already // exists in the platform. If a resource already exists(exists=true) @@ -387,43 +470,15 @@ func (l *label) getMappedResourceID(k assocMapKey) influxdb.ID { if ok { return d.ID() } + case influxdb.VariablesResourceType: + v, ok := l.mappings[k].variable() + if ok { + return v.ID() + } } return 0 } -func (l *label) setBucketMapping(b *bucket, exists bool) { - if l == nil { - return - } - if l.mappings == nil { - l.mappings = make(map[assocMapKey]assocMapVal) - } - - key := assocMapKey{ - resType: influxdb.BucketsResourceType, - name: b.Name, - } - l.mappings[key] = assocMapVal{ - exists: exists, - v: b, - } -} - -func (l *label) setDashboardMapping(d *dashboard) { - if l == nil { - return - } - if l.mappings == nil { - l.mappings = make(map[assocMapKey]assocMapVal) - } - - key := assocMapKey{ - resType: d.ResourceType(), - name: d.Name, - } - l.mappings[key] = assocMapVal{v: d} -} - func (l *label) properties() map[string]string { return map[string]string{ "color": l.Color, @@ -455,10 +510,47 @@ type variable struct { ConstValues []string MapValues map[string]string - mappings map[assocMapKey]assocMapVal + labels []*label + + existing *influxdb.Variable +} + +func (v *variable) ID() influxdb.ID { + if v.existing != nil { + return v.existing.ID + } + return v.id +} + +func (v *variable) Exists() bool { + return v.existing != nil +} + +func (v *variable) ResourceType() influxdb.ResourceType { + return influxdb.VariablesResourceType +} + +func (v *variable) shouldApply() bool { + return v.existing == nil || + v.existing.Description != v.Description || + v.existing.Arguments == nil || + v.existing.Arguments.Type != v.Type } func (v *variable) summarize() SummaryVariable { + return SummaryVariable{ + Variable: influxdb.Variable{ + ID: v.ID(), + OrganizationID: v.OrgID, + Name: v.Name, + Description: v.Description, + Arguments: v.influxVarArgs(), + }, + LabelAssociations: toInfluxLabels(v.labels...), + } +} + +func (v *variable) influxVarArgs() *influxdb.VariableArguments { args := &influxdb.VariableArguments{ Type: v.Type, } @@ -473,16 +565,7 @@ func (v *variable) summarize() SummaryVariable { case "map": args.Values = influxdb.VariableMapValues(v.MapValues) } - - return SummaryVariable{ - Variable: influxdb.Variable{ - ID: v.id, - OrganizationID: v.OrgID, - Name: v.Name, - Description: v.Description, - Arguments: args, - }, - } + return args } func (v *variable) valid() []failure { diff --git a/pkger/models_test.go b/pkger/models_test.go index 45e88e92b7..639574c332 100644 --- a/pkger/models_test.go +++ b/pkger/models_test.go @@ -94,12 +94,14 @@ func TestPkg(t *testing.T) { Name: "name2", Description: "desc2", Color: "blurple", - mappings: map[assocMapKey]assocMapVal{ - assocMapKey{ - resType: influxdb.BucketsResourceType, - name: bucket1.Name, - }: { - v: bucket1, + associationMapping: associationMapping{ + mappings: map[assocMapKey]assocMapVal{ + assocMapKey{ + resType: influxdb.BucketsResourceType, + name: bucket1.Name, + }: { + v: bucket1, + }, }, }, } diff --git a/pkger/parser.go b/pkger/parser.go index 8388beb467..305bba2eae 100644 --- a/pkger/parser.go +++ b/pkger/parser.go @@ -513,6 +513,15 @@ func (p *Pkg) graphVariables() error { MapValues: r.mapStrStr("values"), } + failures := p.parseNestedLabels(r, func(l *label) error { + newVar.labels = append(newVar.labels, l) + p.mLabels[l.Name].setVariableMapping(newVar, false) + return nil + }) + sort.Slice(newVar.labels, func(i, j int) bool { + return newVar.labels[i].Name < newVar.labels[j].Name + }) + p.mVariables[r.Name()] = newVar // here we set the var on the var map and return fails @@ -523,7 +532,7 @@ func (p *Pkg) graphVariables() error { // invalid. So the mapping is correct. So we keep this // to validate that mapping is correct, and return fails // to indicate fails from the var. - return newVar.valid() + return append(failures, newVar.valid()...) }) } diff --git a/pkger/parser_test.go b/pkger/parser_test.go index 9b6d5fa5ee..65d7d2cae1 100644 --- a/pkger/parser_test.go +++ b/pkger/parser_test.go @@ -1,6 +1,8 @@ package pkger import ( + "path/filepath" + "strings" "testing" "time" @@ -1831,6 +1833,47 @@ spec: } }) }) + + t.Run("pkg with variable and labels associated", func(t *testing.T) { + testfileRunner(t, "testdata/variables_associates_label.yml", func(t *testing.T, pkg *Pkg) { + sum := pkg.Summary() + require.Len(t, sum.Labels, 1) + + vars := sum.Variables + require.Len(t, vars, 1) + + expectedLabelMappings := []struct { + varName string + labels []string + }{ + { + varName: "var_1", + labels: []string{"label_1"}, + }, + } + for i, expected := range expectedLabelMappings { + v := vars[i] + require.Len(t, v.LabelAssociations, len(expected.labels)) + + for j, label := range expected.labels { + assert.Equal(t, label, v.LabelAssociations[j].Name) + } + } + + expectedMappings := []SummaryLabelMapping{ + { + ResourceName: "var_1", + LabelName: "label_1", + }, + } + + require.Len(t, sum.LabelMappings, len(expectedMappings)) + for i, expected := range expectedMappings { + expected.LabelMapping.ResourceType = influxdb.VariablesResourceType + assert.Equal(t, expected, sum.LabelMappings[i]) + } + }) + }) } type testPkgResourceError struct { @@ -1925,21 +1968,31 @@ func testfileRunner(t *testing.T, path string, testFn func(t *testing.T, pkg *Pk }{ { name: "yaml", - extension: "yml", + extension: ".yml", encoding: EncodingYAML, }, { name: "json", - extension: "json", + extension: ".json", encoding: EncodingJSON, }, } + ext := filepath.Ext(path) + switch ext { + case ".yml": + tests = tests[:1] + case ".json": + tests = tests[1:] + } + + path = strings.TrimSuffix(path, ext) + for _, tt := range tests { fn := func(t *testing.T) { t.Helper() - pkg := validParsedPkg(t, path+"."+tt.extension, tt.encoding, baseAsserts{ + pkg := validParsedPkg(t, path+tt.extension, tt.encoding, baseAsserts{ version: "0.1.0", kind: "Package", description: "pack description", diff --git a/pkger/service.go b/pkger/service.go index c8cdf84ab7..b81f162d0e 100644 --- a/pkger/service.go +++ b/pkger/service.go @@ -22,6 +22,53 @@ type SVC interface { Apply(ctx context.Context, orgID influxdb.ID, pkg *Pkg) (Summary, error) } +type serviceOpt struct { + logger *zap.Logger + + labelSVC influxdb.LabelService + bucketSVC influxdb.BucketService + dashSVC influxdb.DashboardService + varSVC influxdb.VariableService +} + +// ServiceSetterFn is a means of setting dependencies on the Service type. +type ServiceSetterFn func(opt *serviceOpt) + +// WithLogger sets the service logger. +func WithLogger(logger *zap.Logger) ServiceSetterFn { + return func(opt *serviceOpt) { + opt.logger = logger + } +} + +// WithBucketSVC sets the bucket service. +func WithBucketSVC(bktSVC influxdb.BucketService) ServiceSetterFn { + return func(opt *serviceOpt) { + opt.bucketSVC = bktSVC + } +} + +// WithDashboardSVC sets the dashboard service. +func WithDashboardSVC(dashSVC influxdb.DashboardService) ServiceSetterFn { + return func(opt *serviceOpt) { + opt.dashSVC = dashSVC + } +} + +// WithLabelSVC sets the label service. +func WithLabelSVC(labelSVC influxdb.LabelService) ServiceSetterFn { + return func(opt *serviceOpt) { + opt.labelSVC = labelSVC + } +} + +// WithVariableSVC sets the variable service. +func WithVariableSVC(varSVC influxdb.VariableService) ServiceSetterFn { + return func(opt *serviceOpt) { + opt.varSVC = varSVC + } +} + // Service provides the pkger business logic including all the dependencies to make // this resource sausage. type Service struct { @@ -30,21 +77,25 @@ type Service struct { labelSVC influxdb.LabelService bucketSVC influxdb.BucketService dashSVC influxdb.DashboardService + varSVC influxdb.VariableService } // NewService is a constructor for a pkger Service. -func NewService(l *zap.Logger, bucketSVC influxdb.BucketService, labelSVC influxdb.LabelService, dashSVC influxdb.DashboardService) *Service { - svc := Service{ - logger: zap.NewNop(), - bucketSVC: bucketSVC, - labelSVC: labelSVC, - dashSVC: dashSVC, +func NewService(opts ...ServiceSetterFn) *Service { + opt := &serviceOpt{ + logger: zap.NewNop(), + } + for _, o := range opts { + o(opt) } - if l != nil { - svc.logger = l + return &Service{ + logger: opt.logger, + bucketSVC: opt.bucketSVC, + labelSVC: opt.labelSVC, + dashSVC: opt.dashSVC, + varSVC: opt.varSVC, } - return &svc } // CreatePkgSetFn is a functional input for setting the pkg fields. @@ -105,6 +156,11 @@ func (s *Service) DryRun(ctx context.Context, orgID influxdb.ID, pkg *Pkg) (Summ return Summary{}, Diff{}, err } + diffVars, err := s.dryRunVariables(ctx, orgID, pkg) + if err != nil { + return Summary{}, Diff{}, err + } + diffLabelMappings, err := s.dryRunLabelMappings(ctx, pkg) if err != nil { return Summary{}, Diff{}, err @@ -120,6 +176,7 @@ func (s *Service) DryRun(ctx context.Context, orgID influxdb.ID, pkg *Pkg) (Summ Dashboards: diffDashes, Labels: diffLabels, LabelMappings: diffLabelMappings, + Variables: diffVars, } return pkg.Summary(), diff, nil } @@ -169,9 +226,9 @@ func (s *Service) dryRunLabels(ctx context.Context, orgID influxdb.ID, pkg *Pkg) mExistingLabels := make(map[string]DiffLabel) labels := pkg.labels() for i := range labels { - l := labels[i] + pkgLabel := labels[i] existingLabels, err := s.labelSVC.FindLabels(ctx, influxdb.LabelFilter{ - Name: l.Name, + Name: pkgLabel.Name, OrgID: &orgID, }, influxdb.FindOptions{Limit: 1}) switch { @@ -179,10 +236,10 @@ func (s *Service) dryRunLabels(ctx context.Context, orgID influxdb.ID, pkg *Pkg) // err isn't a not found (some other error) case err == nil && len(existingLabels) > 0: existingLabel := existingLabels[0] - l.existing = existingLabel - mExistingLabels[l.Name] = newDiffLabel(l, *existingLabel) + pkgLabel.existing = existingLabel + mExistingLabels[pkgLabel.Name] = newDiffLabel(pkgLabel, *existingLabel) default: - mExistingLabels[l.Name] = newDiffLabel(l, influxdb.Label{}) + mExistingLabels[pkgLabel.Name] = newDiffLabel(pkgLabel, influxdb.Label{}) } } @@ -197,6 +254,49 @@ func (s *Service) dryRunLabels(ctx context.Context, orgID influxdb.ID, pkg *Pkg) return diffs, nil } +func (s *Service) dryRunVariables(ctx context.Context, orgID influxdb.ID, pkg *Pkg) ([]DiffVariable, error) { + mExistingLabels := make(map[string]DiffVariable) + variables := pkg.variables() + +VarLoop: + for i := range variables { + pkgVar := variables[i] + existingLabels, err := s.varSVC.FindVariables(ctx, influxdb.VariableFilter{ + OrganizationID: &orgID, + // TODO: would be ideal to extend find variables to allow for a name matcher + // since names are unique for vars within an org, meanwhile, make large limit + // returned vars, should be more than enough for the time being. + }, influxdb.FindOptions{Limit: 10000}) + switch { + case err == nil && len(existingLabels) > 0: + for i := range existingLabels { + existingVar := existingLabels[i] + if existingVar.Name != pkgVar.Name { + continue + } + pkgVar.existing = existingVar + mExistingLabels[pkgVar.Name] = newDiffVariable(pkgVar, *existingVar) + continue VarLoop + } + // fallthrough here for when the variable is not found, it'll fall to the + // default case and add it as new. + fallthrough + default: + mExistingLabels[pkgVar.Name] = newDiffVariable(pkgVar, influxdb.Variable{}) + } + } + + diffs := make([]DiffVariable, 0, len(mExistingLabels)) + for _, diff := range mExistingLabels { + diffs = append(diffs, diff) + } + sort.Slice(diffs, func(i, j int) bool { + return diffs[i].Name < diffs[j].Name + }) + + return diffs, nil +} + type ( labelMappingDiffFn func(labelID influxdb.ID, labelName string, isNew bool) @@ -243,6 +343,23 @@ func (s *Service) dryRunLabelMappings(ctx context.Context, pkg *Pkg) ([]DiffLabe } } + for _, v := range pkg.variables() { + err := s.dryRunResourceLabelMapping(ctx, v, v.labels, func(labelID influxdb.ID, labelName string, isNew bool) { + pkg.mLabels[labelName].setVariableMapping(v, !isNew) + diffs = append(diffs, DiffLabelMapping{ + IsNew: isNew, + ResType: v.ResourceType(), + ResID: SafeID(v.ID()), + ResName: v.Name, + LabelID: SafeID(labelID), + LabelName: labelName, + }) + }) + if err != nil { + return nil, err + } + } + // sort by res type ASC, then res name ASC, then label name ASC sort.Slice(diffs, func(i, j int) bool { n, m := diffs[i], diffs[j] @@ -271,6 +388,7 @@ func (s *Service) dryRunResourceLabelMapping(ctx context.Context, la labelAssoci } return nil } + // loop through and hit api for all labels associated with a bkt // lookup labels in pkg, add it to the label mapping, if exists in // the results from API, mark it exists @@ -338,6 +456,7 @@ func (s *Service) Apply(ctx context.Context, orgID influxdb.ID, pkg *Pkg) (sum S { // primary resources s.applyLabels(pkg.labels()), + s.applyVariables(pkg.variables()), s.applyBuckets(pkg.buckets()), s.applyDashboards(pkg.dashboards()), }, @@ -589,7 +708,17 @@ func (s *Service) applyLabels(labels []*label) applier { func (s *Service) rollbackLabels(labels []*label) error { var errs []string for _, l := range labels { - err := s.labelSVC.DeleteLabel(context.Background(), l.ID()) + if l.existing == nil { + err := s.labelSVC.DeleteLabel(context.Background(), l.ID()) + if err != nil { + errs = append(errs, l.ID().String()) + } + continue + } + + _, err := s.labelSVC.UpdateLabel(context.Background(), l.ID(), influxdb.LabelUpdate{ + Properties: l.existing.Properties, + }) if err != nil { errs = append(errs, l.ID().String()) } @@ -626,6 +755,97 @@ func (s *Service) applyLabel(ctx context.Context, l *label) (influxdb.Label, err return influxLabel, nil } +func (s *Service) applyVariables(vars []*variable) applier { + const resource = "variable" + + rollBackVars := make([]*variable, 0, len(vars)) + createFn := func(ctx context.Context, orgID influxdb.ID) error { + ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) + defer cancel() + + var errs applyErrs + for i, v := range vars { + vars[i].OrgID = orgID + if !v.shouldApply() { + continue + } + influxVar, err := s.applyVariable(ctx, v) + if err != nil { + errs = append(errs, applyErrBody{ + name: v.Name, + msg: err.Error(), + }) + continue + } + vars[i].id = influxVar.ID + rollBackVars = append(rollBackVars, vars[i]) + } + + return errs.toError(resource, "failed to create variable") + } + + return applier{ + creater: createFn, + rollbacker: rollbacker{ + resource: resource, + fn: func() error { return s.rollbackVariables(rollBackVars) }, + }, + } +} + +func (s *Service) rollbackVariables(variables []*variable) error { + var errs []string + for _, v := range variables { + if v.existing == nil { + err := s.varSVC.DeleteVariable(context.Background(), v.ID()) + if err != nil { + errs = append(errs, v.ID().String()) + } + continue + } + + _, err := s.varSVC.UpdateVariable(context.Background(), v.ID(), &influxdb.VariableUpdate{ + Description: v.existing.Description, + Arguments: v.existing.Arguments, + }) + if err != nil { + errs = append(errs, v.ID().String()) + } + } + + if len(errs) > 0 { + return fmt.Errorf(`variable_ids=[%s] err="unable to delete variable"`, strings.Join(errs, ", ")) + } + + return nil +} + +func (s *Service) applyVariable(ctx context.Context, v *variable) (influxdb.Variable, error) { + if v.existing != nil { + updatedVar, err := s.varSVC.UpdateVariable(ctx, v.ID(), &influxdb.VariableUpdate{ + Description: v.Description, + Arguments: v.influxVarArgs(), + }) + if err != nil { + return influxdb.Variable{}, err + } + return *updatedVar, nil + } + + influxVar := influxdb.Variable{ + OrganizationID: v.OrgID, + Name: v.Name, + Description: v.Description, + Arguments: v.influxVarArgs(), + } + err := s.varSVC.CreateVariable(ctx, &influxVar) + if err != nil { + return influxdb.Variable{}, err + } + + return influxVar, nil +} + func (s *Service) applyLabelMappings(pkg *Pkg) applier { var mappings []influxdb.LabelMapping createFn := func(ctx context.Context, orgID influxdb.ID) error { diff --git a/pkger/service_test.go b/pkger/service_test.go index 316a336e84..cddfc2f90c 100644 --- a/pkger/service_test.go +++ b/pkger/service_test.go @@ -10,7 +10,6 @@ import ( "github.com/influxdata/influxdb/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/zap" ) func TestService(t *testing.T) { @@ -28,9 +27,7 @@ func TestService(t *testing.T) { RetentionPeriod: 30 * time.Hour, }, nil } - fakeLabelSVC := mock.NewLabelService() - fakeDashSVC := mock.NewDashboardService() - svc := NewService(zap.NewNop(), fakeBktSVC, fakeLabelSVC, fakeDashSVC) + svc := NewService(WithBucketSVC(fakeBktSVC), WithLabelSVC(mock.NewLabelService())) _, diff, err := svc.DryRun(context.TODO(), influxdb.ID(100), pkg) require.NoError(t, err) @@ -55,9 +52,7 @@ func TestService(t *testing.T) { fakeBktSVC.FindBucketByNameFn = func(_ context.Context, orgID influxdb.ID, name string) (*influxdb.Bucket, error) { return nil, errors.New("not found") } - fakeLabelSVC := mock.NewLabelService() - fakeDashSVC := mock.NewDashboardService() - svc := NewService(zap.NewNop(), fakeBktSVC, fakeLabelSVC, fakeDashSVC) + svc := NewService(WithBucketSVC(fakeBktSVC), WithLabelSVC(mock.NewLabelService())) _, diff, err := svc.DryRun(context.TODO(), influxdb.ID(100), pkg) require.NoError(t, err) @@ -77,7 +72,6 @@ func TestService(t *testing.T) { t.Run("labels", func(t *testing.T) { t.Run("two labels updated", func(t *testing.T) { testfileRunner(t, "testdata/label", func(t *testing.T, pkg *Pkg) { - fakeBktSVC := mock.NewBucketService() fakeLabelSVC := mock.NewLabelService() fakeLabelSVC.FindLabelsFn = func(_ context.Context, filter influxdb.LabelFilter) ([]*influxdb.Label, error) { return []*influxdb.Label{ @@ -91,8 +85,7 @@ func TestService(t *testing.T) { }, }, nil } - fakeDashSVC := mock.NewDashboardService() - svc := NewService(zap.NewNop(), fakeBktSVC, fakeLabelSVC, fakeDashSVC) + svc := NewService(WithLabelSVC(fakeLabelSVC)) _, diff, err := svc.DryRun(context.TODO(), influxdb.ID(100), pkg) require.NoError(t, err) @@ -118,13 +111,11 @@ func TestService(t *testing.T) { t.Run("two labels created", func(t *testing.T) { testfileRunner(t, "testdata/label", func(t *testing.T, pkg *Pkg) { - fakeBktSVC := mock.NewBucketService() fakeLabelSVC := mock.NewLabelService() fakeLabelSVC.FindLabelsFn = func(_ context.Context, filter influxdb.LabelFilter) ([]*influxdb.Label, error) { return nil, errors.New("no labels found") } - fakeDashSVC := mock.NewDashboardService() - svc := NewService(zap.NewNop(), fakeBktSVC, fakeLabelSVC, fakeDashSVC) + svc := NewService(WithLabelSVC(fakeLabelSVC)) _, diff, err := svc.DryRun(context.TODO(), influxdb.ID(100), pkg) require.NoError(t, err) @@ -145,26 +136,75 @@ func TestService(t *testing.T) { }) }) }) + + t.Run("variables", func(t *testing.T) { + testfileRunner(t, "testdata/variables", func(t *testing.T, pkg *Pkg) { + fakeVarSVC := mock.NewVariableService() + fakeVarSVC.FindVariablesF = func(_ context.Context, filter influxdb.VariableFilter, opts ...influxdb.FindOptions) ([]*influxdb.Variable, error) { + return []*influxdb.Variable{ + { + ID: influxdb.ID(1), + Name: "var_const", + Description: "old desc", + }, + }, nil + } + fakeLabelSVC := mock.NewLabelService() // ignore mappings for now + svc := NewService( + WithLabelSVC(fakeLabelSVC), + WithVariableSVC(fakeVarSVC), + ) + + _, diff, err := svc.DryRun(context.TODO(), influxdb.ID(100), pkg) + require.NoError(t, err) + + require.Len(t, diff.Variables, 4) + + expected := DiffVariable{ + ID: SafeID(1), + Name: "var_const", + OldDesc: "old desc", + NewDesc: "var_const desc", + NewArgs: &influxdb.VariableArguments{ + Type: "constant", + Values: influxdb.VariableConstantValues{"first val"}, + }, + } + assert.Equal(t, expected, diff.Variables[0]) + + expected = DiffVariable{ + // no ID here since this one would be new + Name: "var_map", + OldDesc: "", + NewDesc: "var_map desc", + NewArgs: &influxdb.VariableArguments{ + Type: "map", + Values: influxdb.VariableMapValues{"k1": "v1"}, + }, + } + assert.Equal(t, expected, diff.Variables[1]) + }) + }) }) t.Run("Apply", func(t *testing.T) { t.Run("buckets", func(t *testing.T) { t.Run("successfully creates pkg of buckets", func(t *testing.T) { - testfileRunner(t, "testdata/bucket", func(t *testing.T, pkg *Pkg) { - fakeBucketSVC := mock.NewBucketService() - fakeBucketSVC.CreateBucketFn = func(_ context.Context, b *influxdb.Bucket) error { + testfileRunner(t, "testdata/bucket.yml", func(t *testing.T, pkg *Pkg) { + fakeBktSVC := mock.NewBucketService() + fakeBktSVC.CreateBucketFn = func(_ context.Context, b *influxdb.Bucket) error { b.ID = influxdb.ID(b.RetentionPeriod) return nil } - fakeBucketSVC.FindBucketByNameFn = func(_ context.Context, id influxdb.ID, s string) (*influxdb.Bucket, error) { + fakeBktSVC.FindBucketByNameFn = func(_ context.Context, id influxdb.ID, s string) (*influxdb.Bucket, error) { // forces the bucket to be created a new return nil, errors.New("an error") } - fakeBucketSVC.UpdateBucketFn = func(_ context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { + fakeBktSVC.UpdateBucketFn = func(_ context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { return &influxdb.Bucket{ID: id}, nil } - svc := NewService(zap.NewNop(), fakeBucketSVC, nil, nil) + svc := NewService(WithBucketSVC(fakeBktSVC)) orgID := influxdb.ID(9000) @@ -196,19 +236,19 @@ func TestService(t *testing.T) { RetentionPeriod: pkgBkt.RetentionPeriod, } - fakeBucketSVC := mock.NewBucketService() + fakeBktSVC := mock.NewBucketService() var createCallCount int - fakeBucketSVC.CreateBucketFn = func(_ context.Context, b *influxdb.Bucket) error { + fakeBktSVC.CreateBucketFn = func(_ context.Context, b *influxdb.Bucket) error { createCallCount++ return nil } var updateCallCount int - fakeBucketSVC.UpdateBucketFn = func(_ context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { + fakeBktSVC.UpdateBucketFn = func(_ context.Context, id influxdb.ID, upd influxdb.BucketUpdate) (*influxdb.Bucket, error) { updateCallCount++ return &influxdb.Bucket{ID: id}, nil } - svc := NewService(zap.NewNop(), fakeBucketSVC, nil, nil) + svc := NewService(WithBucketSVC(fakeBktSVC)) sum, err := svc.Apply(context.TODO(), orgID, pkg) require.NoError(t, err) @@ -227,13 +267,13 @@ func TestService(t *testing.T) { t.Run("rolls back all created buckets on an error", func(t *testing.T) { testfileRunner(t, "testdata/bucket", func(t *testing.T, pkg *Pkg) { - fakeBucketSVC := mock.NewBucketService() - fakeBucketSVC.FindBucketByNameFn = func(_ context.Context, id influxdb.ID, s string) (*influxdb.Bucket, error) { + fakeBktSVC := mock.NewBucketService() + fakeBktSVC.FindBucketByNameFn = func(_ context.Context, id influxdb.ID, s string) (*influxdb.Bucket, error) { // forces the bucket to be created a new return nil, errors.New("an error") } var c int - fakeBucketSVC.CreateBucketFn = func(_ context.Context, b *influxdb.Bucket) error { + fakeBktSVC.CreateBucketFn = func(_ context.Context, b *influxdb.Bucket) error { if c == 2 { return errors.New("blowed up ") } @@ -241,7 +281,7 @@ func TestService(t *testing.T) { return nil } var count int - fakeBucketSVC.DeleteBucketFn = func(_ context.Context, id influxdb.ID) error { + fakeBktSVC.DeleteBucketFn = func(_ context.Context, id influxdb.ID) error { count++ return nil } @@ -249,7 +289,7 @@ func TestService(t *testing.T) { pkg.mBuckets["copybuck1"] = pkg.mBuckets["rucket_11"] pkg.mBuckets["copybuck2"] = pkg.mBuckets["rucket_11"] - svc := NewService(zap.NewNop(), fakeBucketSVC, nil, nil) + svc := NewService(WithBucketSVC(fakeBktSVC)) orgID := influxdb.ID(9000) @@ -272,7 +312,7 @@ func TestService(t *testing.T) { return nil } - svc := NewService(zap.NewNop(), nil, fakeLabelSVC, nil) + svc := NewService(WithLabelSVC(fakeLabelSVC)) orgID := influxdb.ID(9000) @@ -313,12 +353,11 @@ func TestService(t *testing.T) { count++ return nil } - fakeDashSVC := mock.NewDashboardService() pkg.mLabels["copy1"] = pkg.mLabels["label_1"] pkg.mLabels["copy2"] = pkg.mLabels["label_2"] - svc := NewService(zap.NewNop(), nil, fakeLabelSVC, fakeDashSVC) + svc := NewService(WithLabelSVC(fakeLabelSVC)) orgID := influxdb.ID(9000) @@ -363,7 +402,7 @@ func TestService(t *testing.T) { return &influxdb.Label{ID: id}, nil } - svc := NewService(zap.NewNop(), nil, fakeLabelSVC, nil) + svc := NewService(WithLabelSVC(fakeLabelSVC)) sum, err := svc.Apply(context.TODO(), orgID, pkg) require.NoError(t, err) @@ -386,12 +425,11 @@ func TestService(t *testing.T) { assert.Equal(t, 1, createCallCount) // only called for second label }) }) - }) t.Run("dashboards", func(t *testing.T) { t.Run("successfully creates a dashboard", func(t *testing.T) { - testfileRunner(t, "testdata/dashboard", func(t *testing.T, pkg *Pkg) { + testfileRunner(t, "testdata/dashboard.yml", func(t *testing.T, pkg *Pkg) { fakeDashSVC := mock.NewDashboardService() id := 1 fakeDashSVC.CreateDashboardF = func(_ context.Context, d *influxdb.Dashboard) error { @@ -405,7 +443,7 @@ func TestService(t *testing.T) { return &influxdb.View{}, nil } - svc := NewService(zap.NewNop(), nil, nil, fakeDashSVC) + svc := NewService(WithDashboardSVC(fakeDashSVC)) orgID := influxdb.ID(9000) @@ -422,7 +460,7 @@ func TestService(t *testing.T) { }) t.Run("rolls back created dashboard on an error", func(t *testing.T) { - testfileRunner(t, "testdata/dashboard", func(t *testing.T, pkg *Pkg) { + testfileRunner(t, "testdata/dashboard.yml", func(t *testing.T, pkg *Pkg) { fakeDashSVC := mock.NewDashboardService() var c int fakeDashSVC.CreateDashboardF = func(_ context.Context, d *influxdb.Dashboard) error { @@ -442,7 +480,7 @@ func TestService(t *testing.T) { pkg.mDashboards["copy1"] = pkg.mDashboards["dash_1"] - svc := NewService(zap.NewNop(), nil, nil, fakeDashSVC) + svc := NewService(WithDashboardSVC(fakeDashSVC)) orgID := influxdb.ID(9000) @@ -456,7 +494,7 @@ func TestService(t *testing.T) { t.Run("label mapping", func(t *testing.T) { t.Run("successfully creates pkg of labels", func(t *testing.T) { - testfileRunner(t, "testdata/bucket_associates_label", func(t *testing.T, pkg *Pkg) { + testfileRunner(t, "testdata/bucket_associates_label.yml", func(t *testing.T, pkg *Pkg) { fakeBktSVC := mock.NewBucketService() id := 1 fakeBktSVC.CreateBucketFn = func(_ context.Context, b *influxdb.Bucket) error { @@ -482,7 +520,11 @@ func TestService(t *testing.T) { return nil } fakeDashSVC := mock.NewDashboardService() - svc := NewService(zap.NewNop(), fakeBktSVC, fakeLabelSVC, fakeDashSVC) + svc := NewService( + WithBucketSVC(fakeBktSVC), + WithLabelSVC(fakeLabelSVC), + WithDashboardSVC(fakeDashSVC), + ) orgID := influxdb.ID(9000) @@ -493,6 +535,126 @@ func TestService(t *testing.T) { }) }) }) + + t.Run("variables", func(t *testing.T) { + t.Run("successfully creates pkg of variables", func(t *testing.T) { + testfileRunner(t, "testdata/variables.yml", func(t *testing.T, pkg *Pkg) { + fakeVarSVC := mock.NewVariableService() + id := 1 + fakeVarSVC.CreateVariableF = func(_ context.Context, v *influxdb.Variable) error { + v.ID = influxdb.ID(id) + id++ + return nil + } + + svc := NewService( + WithLabelSVC(mock.NewLabelService()), + WithVariableSVC(fakeVarSVC), + ) + + orgID := influxdb.ID(9000) + + sum, err := svc.Apply(context.TODO(), orgID, pkg) + require.NoError(t, err) + + require.Len(t, sum.Variables, 4) + expected := sum.Variables[0] + assert.Equal(t, influxdb.ID(1), expected.ID) + assert.Equal(t, orgID, expected.OrganizationID) + assert.Equal(t, "var_const", expected.Name) + assert.Equal(t, "var_const desc", expected.Description) + require.NotNil(t, expected.Arguments) + assert.Equal(t, influxdb.VariableConstantValues{"first val"}, expected.Arguments.Values) + + for i := 1; i < 3; i++ { + expected = sum.Variables[i] + assert.Equal(t, influxdb.ID(i+1), expected.ID) + } + }) + }) + + t.Run("rolls back all created variables on an error", func(t *testing.T) { + testfileRunner(t, "testdata/variables.yml", func(t *testing.T, pkg *Pkg) { + fakeVarSVC := mock.NewVariableService() + var c int + fakeVarSVC.CreateVariableF = func(_ context.Context, l *influxdb.Variable) error { + // 4th variable will return the error here, and 3 before should be rolled back + if c == 3 { + return errors.New("blowed up ") + } + c++ + return nil + } + var count int + fakeVarSVC.DeleteVariableF = func(_ context.Context, id influxdb.ID) error { + count++ + return nil + } + + svc := NewService( + WithLabelSVC(mock.NewLabelService()), + WithVariableSVC(fakeVarSVC), + ) + + orgID := influxdb.ID(9000) + + _, err := svc.Apply(context.TODO(), orgID, pkg) + require.Error(t, err) + + assert.Equal(t, 3, count) + }) + }) + + t.Run("will not apply variable if no changes to be applied", func(t *testing.T) { + testfileRunner(t, "testdata/variables.yml", func(t *testing.T, pkg *Pkg) { + orgID := influxdb.ID(9000) + + pkg.isVerified = true + pkgLabel := pkg.mVariables["var_const"] + pkgLabel.existing = &influxdb.Variable{ + // makes all pkg changes same as they are on the existing + ID: influxdb.ID(1), + OrganizationID: orgID, + Name: pkgLabel.Name, + Arguments: &influxdb.VariableArguments{ + Type: "constant", + Values: influxdb.VariableConstantValues{"first val"}, + }, + } + + fakeVarSVC := mock.NewVariableService() + var createCallCount int + fakeVarSVC.CreateVariableF = func(_ context.Context, l *influxdb.Variable) error { + createCallCount++ + if l.Name == "var_const" { + return errors.New("shouldn't get here") + } + return nil + } + fakeVarSVC.UpdateVariableF = func(_ context.Context, id influxdb.ID, v *influxdb.VariableUpdate) (*influxdb.Variable, error) { + if id > influxdb.ID(1) { + return nil, errors.New("this id should not be updated") + } + return &influxdb.Variable{ID: id}, nil + } + + svc := NewService( + WithLabelSVC(mock.NewLabelService()), + WithVariableSVC(fakeVarSVC), + ) + + sum, err := svc.Apply(context.TODO(), orgID, pkg) + require.NoError(t, err) + + require.Len(t, sum.Variables, 4) + expected := sum.Variables[0] + assert.Equal(t, influxdb.ID(1), expected.ID) + assert.Equal(t, "var_const", expected.Name) + + assert.Equal(t, 3, createCallCount) // only called for last 3 labels + }) + }) + }) }) t.Run("CreatePkg", func(t *testing.T) { diff --git a/pkger/testdata/variables_associates_label.yml b/pkger/testdata/variables_associates_label.yml new file mode 100644 index 0000000000..e1f822175a --- /dev/null +++ b/pkger/testdata/variables_associates_label.yml @@ -0,0 +1,18 @@ +apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Label + name: label_1 + - kind: Variable + name: var_1 + type: constant + values: + - first val + associations: + - kind: Label + name: label_1