From 4e1bc7ceb2cc8cfa3b3c72869741df25672753fa Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 15:25:28 -0600 Subject: [PATCH 01/20] feat(pkger): add ability to export dashboards by name --- cmd/influx/template.go | 74 +++++++++++++++++++++++++++------------- kv/dashboard.go | 4 ++- pkger/clone_resource.go | 49 ++++++++++++++++++-------- pkger/service.go | 51 ++++++++++++++++++++------- pkger/service_logging.go | 5 ++- 5 files changed, 130 insertions(+), 53 deletions(-) diff --git a/cmd/influx/template.go b/cmd/influx/template.go index 9031199031..c098cf271c 100644 --- a/cmd/influx/template.go +++ b/cmd/influx/template.go @@ -77,16 +77,25 @@ type cmdTemplateBuilder struct { } exportOpts struct { - resourceType string - buckets string - checks string - dashboards string - endpoints string - labels string - rules string - tasks string - telegrafs string - variables string + resourceType string + buckets string + checks string + dashboards string + endpoints string + labels string + rules string + tasks string + telegrafs string + variables string + bucketNames string + checkNames string + dashboardNames string + endpointNames string + labelNames string + ruleNames string + taskNames string + telegrafNames string + variableNames string } updateStackOpts struct { @@ -351,6 +360,15 @@ func (b *cmdTemplateBuilder) cmdExport() *cobra.Command { cmd.Flags().StringVar(&b.exportOpts.tasks, "tasks", "", "List of task ids comma separated") cmd.Flags().StringVar(&b.exportOpts.telegrafs, "telegraf-configs", "", "List of telegraf config ids comma separated") cmd.Flags().StringVar(&b.exportOpts.variables, "variables", "", "List of variable ids comma separated") + cmd.Flags().StringVar(&b.exportOpts.bucketNames, "bucket-names", "", "List of bucket names comma separated") + cmd.Flags().StringVar(&b.exportOpts.checkNames, "check-names", "", "List of check names comma separated") + cmd.Flags().StringVar(&b.exportOpts.dashboardNames, "dashboard-names", "", "List of dashboard names comma separated") + cmd.Flags().StringVar(&b.exportOpts.endpointNames, "endpoint-names", "", "List of notification endpoint names comma separated") + cmd.Flags().StringVar(&b.exportOpts.labelNames, "label-names", "", "List of label names comma separated") + cmd.Flags().StringVar(&b.exportOpts.ruleNames, "rule-names", "", "List of notification rule names comma separated") + cmd.Flags().StringVar(&b.exportOpts.taskNames, "task-names", "", "List of task names comma separated") + cmd.Flags().StringVar(&b.exportOpts.telegrafNames, "telegra-namef-configs", "", "List of telegraf config names comma separated") + cmd.Flags().StringVar(&b.exportOpts.variableNames, "variable-names", "", "List of variable names comma separated") return cmd } @@ -364,21 +382,22 @@ func (b *cmdTemplateBuilder) exportRunEFn(cmd *cobra.Command, args []string) err resTypes := []struct { kind pkger.Kind idStrs []string + names []string }{ - {kind: pkger.KindBucket, idStrs: strings.Split(b.exportOpts.buckets, ",")}, - {kind: pkger.KindCheck, idStrs: strings.Split(b.exportOpts.checks, ",")}, - {kind: pkger.KindDashboard, idStrs: strings.Split(b.exportOpts.dashboards, ",")}, - {kind: pkger.KindLabel, idStrs: strings.Split(b.exportOpts.labels, ",")}, - {kind: pkger.KindNotificationEndpoint, idStrs: strings.Split(b.exportOpts.endpoints, ",")}, - {kind: pkger.KindNotificationRule, idStrs: strings.Split(b.exportOpts.rules, ",")}, - {kind: pkger.KindTask, idStrs: strings.Split(b.exportOpts.tasks, ",")}, - {kind: pkger.KindTelegraf, idStrs: strings.Split(b.exportOpts.telegrafs, ",")}, - {kind: pkger.KindVariable, idStrs: strings.Split(b.exportOpts.variables, ",")}, + {kind: pkger.KindBucket, idStrs: strings.Split(b.exportOpts.buckets, ","), names: strings.Split(b.exportOpts.bucketNames, ",")}, + {kind: pkger.KindCheck, idStrs: strings.Split(b.exportOpts.checks, ","), names: strings.Split(b.exportOpts.checkNames, ",")}, + {kind: pkger.KindDashboard, idStrs: strings.Split(b.exportOpts.dashboards, ","), names: strings.Split(b.exportOpts.dashboardNames, ",")}, + {kind: pkger.KindLabel, idStrs: strings.Split(b.exportOpts.labels, ","), names: strings.Split(b.exportOpts.labelNames, ",")}, + {kind: pkger.KindNotificationEndpoint, idStrs: strings.Split(b.exportOpts.endpoints, ","), names: strings.Split(b.exportOpts.endpointNames, ",")}, + {kind: pkger.KindNotificationRule, idStrs: strings.Split(b.exportOpts.rules, ","), names: strings.Split(b.exportOpts.ruleNames, ",")}, + {kind: pkger.KindTask, idStrs: strings.Split(b.exportOpts.tasks, ","), names: strings.Split(b.exportOpts.taskNames, ",")}, + {kind: pkger.KindTelegraf, idStrs: strings.Split(b.exportOpts.telegrafs, ","), names: strings.Split(b.exportOpts.telegrafNames, ",")}, + {kind: pkger.KindVariable, idStrs: strings.Split(b.exportOpts.variables, ","), names: strings.Split(b.exportOpts.variableNames, ",")}, } var opts []pkger.ExportOptFn for _, rt := range resTypes { - newOpt, err := newResourcesToClone(rt.kind, rt.idStrs) + newOpt, err := newResourcesToClone(rt.kind, rt.idStrs, rt.names) if err != nil { return ierror.Wrap(err, rt.kind.String()) } @@ -410,7 +429,7 @@ func (b *cmdTemplateBuilder) exportRunEFn(cmd *cobra.Command, args []string) err } } - resTypeOpt, err := newResourcesToClone(resKind, args) + resTypeOpt, err := newResourcesToClone(resKind, args, []string{}) if err != nil { return err } @@ -1133,7 +1152,7 @@ func (b *cmdTemplateBuilder) convertEncoding() pkger.Encoding { } } -func newResourcesToClone(kind pkger.Kind, idStrs []string) (pkger.ExportOptFn, error) { +func newResourcesToClone(kind pkger.Kind, idStrs, names []string) (pkger.ExportOptFn, error) { ids, err := toInfluxIDs(idStrs) if err != nil { return nil, err @@ -1146,6 +1165,15 @@ func newResourcesToClone(kind pkger.Kind, idStrs []string) (pkger.ExportOptFn, e ID: id, }) } + for _, name := range names { + if len(name) == 0 { + continue + } + resources = append(resources, pkger.ResourceToClone{ + Kind: kind, + Name: name, + }) + } return pkger.ExportWithExistingResources(resources...), nil } @@ -1156,7 +1184,7 @@ func toInfluxIDs(args []string) ([]influxdb.ID, error) { ) for _, arg := range args { normedArg := strings.TrimSpace(strings.ToLower(arg)) - if normedArg == "" { + if len(normedArg) == 0 { continue } diff --git a/kv/dashboard.go b/kv/dashboard.go index a348169be3..e1b3cf3a92 100644 --- a/kv/dashboard.go +++ b/kv/dashboard.go @@ -135,7 +135,9 @@ func filterDashboardsFn(filter influxdb.DashboardFilter) func(d *influxdb.Dashbo } } - return func(d *influxdb.Dashboard) bool { return true } + return func(d *influxdb.Dashboard) bool { + return ((filter.OrganizationID == nil) || (*filter.OrganizationID == d.OrganizationID)) + } } // FindDashboards retrives all dashboards that match an arbitrary dashboard filter. diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index 6f372fb9d6..1bbb223d5f 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -27,7 +27,7 @@ type NameGenerator func() string // ResourceToClone is a resource that will be cloned. type ResourceToClone struct { Kind Kind `json:"kind"` - ID influxdb.ID `json:"id"` + ID influxdb.ID `json:"id,omitempty"` Name string `json:"name"` // note(jsteenb2): For time being we'll allow this internally, but not externally. A lot of // issues to account for when exposing this to the outside world. Not something I'm keen @@ -40,8 +40,8 @@ func (r ResourceToClone) OK() error { if err := r.Kind.OK(); err != nil { return err } - if r.ID == influxdb.ID(0) { - return errors.New("must provide an ID") + if r.ID == influxdb.ID(0) && len(r.Name) == 0 { + return errors.New("must provide an ID or name") } return nil } @@ -135,6 +135,7 @@ func (ex *resourceExporter) Export(ctx context.Context, resourcesToClone []Resou // i.e. if a bucket depends on a label, then labels need to be run first // to guarantee they are available before a bucket is exported. sort.Slice(resourcesToClone, func(i, j int) bool { + // todo: how did this compile, i just added Name to the type. iName, jName := resourcesToClone[i].Name, resourcesToClone[j].Name iKind, jKind := resourcesToClone[i].Kind, resourcesToClone[j].Kind @@ -171,13 +172,10 @@ func (ex *resourceExporter) StackResources() []StackResource { return resources } -func (ex *resourceExporter) uniqByNameResID() influxdb.ID { - // we only need an id when we have resources that are not unique by name via the - // metastore. resoureces that are unique by name will be provided a default stamp - // making looksup unique since each resource will be unique by name. - const uniqByNameResID = 0 - return uniqByNameResID -} +// we only need an id when we have resources that are not unique by name via the +// metastore. resoureces that are unique by name will be provided a default stamp +// making looksup unique since each resource will be unique by name. +const uniqByNameResID = influxdb.ID(0) type cloneAssociationsFn func(context.Context, ResourceToClone) (associations []ObjectAssociation, skipResource bool, err error) @@ -203,6 +201,7 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT metaName = ex.uniqName() } + fmt.Println("STACK Resourcing", r.Kind, r.Name, r.ID, object.Name()) stackResource := StackResource{ APIVersion: APIVersion, ID: r.ID, @@ -220,8 +219,6 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT ex.mStackResources[key] = stackResource } - uniqByNameResID := ex.uniqByNameResID() - switch { case r.Kind.is(KindBucket): bkt, err := ex.bucketSVC.FindBucketByID(ctx, r.ID) @@ -238,11 +235,29 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } mapResource(ch.GetOrgID(), uniqByNameResID, KindCheck, CheckToObject(r.Name, ch)) case r.Kind.is(KindDashboard): - dash, err := findDashboardByIDFull(ctx, ex.dashSVC, r.ID) + filter := influxdb.DashboardFilter{} + if r.ID != influxdb.ID(0) { + filter.IDs = []*influxdb.ID{&r.ID} + } + dashs, i, err := ex.dashSVC.FindDashboards(ctx, filter, influxdb.DefaultDashboardFindOptions) if err != nil { return err } - mapResource(dash.OrganizationID, dash.ID, KindDashboard, DashboardToObject(r.Name, *dash)) + if i < 1 { + return errors.New("no dashboards found") + } + + for _, dash := range dashs { + for _, cell := range dash.Cells { + v, err := ex.dashSVC.GetDashboardCellView(ctx, dash.ID, cell.ID) + if err != nil { + continue + } + cell.View = v + } + + mapResource(dash.OrganizationID, dash.ID, KindDashboard, DashboardToObject(r.Name, *dash)) + } case r.Kind.is(KindLabel): l, err := ex.labelSVC.FindLabelByID(ctx, r.ID) if err != nil { @@ -319,6 +334,10 @@ func (ex *resourceExporter) resourceCloneAssociationsGen(ctx context.Context, la return nil, shouldSkip, nil } + if len(r.Name) > 0 { + return nil, false, nil + } + labels, err := ex.labelSVC.FindResourceLabels(ctx, influxdb.LabelMappingFilter{ ResourceID: r.ID, ResourceType: r.Kind.ResourceType(), @@ -355,7 +374,7 @@ func (ex *resourceExporter) resourceCloneAssociationsGen(ctx context.Context, la } labelObject.Metadata[fieldName] = metaName - k := newExportKey(l.OrgID, ex.uniqByNameResID(), KindLabel, l.Name) + k := newExportKey(l.OrgID, uniqByNameResID, KindLabel, l.Name) existing, ok := ex.mObjects[k] if ok { associations = append(associations, ObjectAssociation{ diff --git a/pkger/service.go b/pkger/service.go index 0a21af3c3a..a4bf52846c 100644 --- a/pkger/service.go +++ b/pkger/service.go @@ -72,6 +72,7 @@ type ( StackResource struct { APIVersion string ID influxdb.ID + Name string Kind Kind MetaName string Associations []StackResourceAssociation @@ -631,6 +632,7 @@ func (s *Service) Export(ctx context.Context, setters ...ExportOptFn) (*Template Kind: r.Kind, ID: r.ID, MetaName: r.MetaName, + Name: r.Name, })) } @@ -668,7 +670,7 @@ func (s *Service) Export(ctx context.Context, setters ...ExportOptFn) (*Template func (s *Service) cloneOrgResources(ctx context.Context, orgID influxdb.ID, resourceKinds []Kind) ([]ResourceToClone, error) { var resources []ResourceToClone for _, resGen := range s.filterOrgResourceKinds(resourceKinds) { - existingResources, err := resGen.cloneFn(ctx, orgID) + existingResources, err := resGen.cloneFn(ctx, orgID, "") if err != nil { return nil, ierrors.Wrap(err, "finding "+string(resGen.resType)) } @@ -678,9 +680,15 @@ func (s *Service) cloneOrgResources(ctx context.Context, orgID influxdb.ID, reso return resources, nil } -func (s *Service) cloneOrgBuckets(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { +func (s *Service) cloneOrgBuckets(ctx context.Context, orgID influxdb.ID, name string) ([]ResourceToClone, error) { + var n *string + if len(name) > 0 { + n = &name + } + buckets, _, err := s.bucketSVC.FindBuckets(ctx, influxdb.BucketFilter{ OrganizationID: &orgID, + Name: n, }) if err != nil { return nil, err @@ -694,14 +702,21 @@ func (s *Service) cloneOrgBuckets(ctx context.Context, orgID influxdb.ID) ([]Res resources = append(resources, ResourceToClone{ Kind: KindBucket, ID: b.ID, + Name: b.Name, }) } return resources, nil } -func (s *Service) cloneOrgChecks(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { +func (s *Service) cloneOrgChecks(ctx context.Context, orgID influxdb.ID, name string) ([]ResourceToClone, error) { + var n *string + if len(name) > 0 { + n = &name + } + checks, _, err := s.checkSVC.FindChecks(ctx, influxdb.CheckFilter{ OrgID: &orgID, + Name: n, }) if err != nil { return nil, err @@ -712,12 +727,13 @@ func (s *Service) cloneOrgChecks(ctx context.Context, orgID influxdb.ID) ([]Reso resources = append(resources, ResourceToClone{ Kind: KindCheck, ID: c.GetID(), + Name: c.GetName(), }) } return resources, nil } -func (s *Service) cloneOrgDashboards(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { +func (s *Service) cloneOrgDashboards(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { dashs, _, err := s.dashSVC.FindDashboards(ctx, influxdb.DashboardFilter{ OrganizationID: &orgID, }, influxdb.FindOptions{Limit: 100}) @@ -735,10 +751,16 @@ func (s *Service) cloneOrgDashboards(ctx context.Context, orgID influxdb.ID) ([] return resources, nil } -func (s *Service) cloneOrgLabels(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { - labels, err := s.labelSVC.FindLabels(ctx, influxdb.LabelFilter{ +func (s *Service) cloneOrgLabels(ctx context.Context, orgID influxdb.ID, name string) ([]ResourceToClone, error) { + filter := influxdb.LabelFilter{ OrgID: &orgID, - }, influxdb.FindOptions{Limit: 10000}) + } + if len(name) > 0 { + filter.Name = name + } + + // todo: if this was ever called, it would error, 100 is the limit. + labels, err := s.labelSVC.FindLabels(ctx, filter, influxdb.FindOptions{Limit: 10000}) if err != nil { return nil, ierrors.Wrap(err, "finding labels") } @@ -748,12 +770,13 @@ func (s *Service) cloneOrgLabels(ctx context.Context, orgID influxdb.ID) ([]Reso resources = append(resources, ResourceToClone{ Kind: KindLabel, ID: l.ID, + Name: l.Name, }) } return resources, nil } -func (s *Service) cloneOrgNotificationEndpoints(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { +func (s *Service) cloneOrgNotificationEndpoints(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { endpoints, _, err := s.endpointSVC.FindNotificationEndpoints(ctx, influxdb.NotificationEndpointFilter{ OrgID: &orgID, }) @@ -766,12 +789,13 @@ func (s *Service) cloneOrgNotificationEndpoints(ctx context.Context, orgID influ resources = append(resources, ResourceToClone{ Kind: KindNotificationEndpoint, ID: e.GetID(), + Name: e.GetName(), }) } return resources, nil } -func (s *Service) cloneOrgNotificationRules(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { +func (s *Service) cloneOrgNotificationRules(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { rules, _, err := s.ruleSVC.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{ OrgID: &orgID, }) @@ -784,12 +808,13 @@ func (s *Service) cloneOrgNotificationRules(ctx context.Context, orgID influxdb. resources = append(resources, ResourceToClone{ Kind: KindNotificationRule, ID: r.GetID(), + Name: r.GetName(), }) } return resources, nil } -func (s *Service) cloneOrgTasks(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { +func (s *Service) cloneOrgTasks(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { tasks, err := s.getAllTasks(ctx, orgID) if err != nil { return nil, err @@ -834,7 +859,7 @@ func (s *Service) cloneOrgTasks(ctx context.Context, orgID influxdb.ID) ([]Resou return resources, nil } -func (s *Service) cloneOrgTelegrafs(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { +func (s *Service) cloneOrgTelegrafs(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { teles, _, err := s.teleSVC.FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{OrgID: &orgID}) if err != nil { return nil, err @@ -850,7 +875,7 @@ func (s *Service) cloneOrgTelegrafs(ctx context.Context, orgID influxdb.ID) ([]R return resources, nil } -func (s *Service) cloneOrgVariables(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { +func (s *Service) cloneOrgVariables(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { vars, err := s.varSVC.FindVariables(ctx, influxdb.VariableFilter{ OrganizationID: &orgID, }, influxdb.FindOptions{Limit: 10000}) @@ -869,7 +894,7 @@ func (s *Service) cloneOrgVariables(ctx context.Context, orgID influxdb.ID) ([]R return resources, nil } -type cloneResFn func(context.Context, influxdb.ID) ([]ResourceToClone, error) +type cloneResFn func(context.Context, influxdb.ID, string) ([]ResourceToClone, error) func (s *Service) filterOrgResourceKinds(resourceKindFilters []Kind) []struct { resType influxdb.ResourceType diff --git a/pkger/service_logging.go b/pkger/service_logging.go index 54e24b9a11..6bbe9aee82 100644 --- a/pkger/service_logging.go +++ b/pkger/service_logging.go @@ -148,7 +148,8 @@ func (s *loggingMW) Export(ctx context.Context, opts ...ExportOptFn) (template * s.logger.Error("failed to export template", zap.Error(err), dur) return } - s.logger.Info("failed to export template", append(s.summaryLogFields(template.Summary()), dur)...) + // todo: should these be Debug logs? + s.logger.Info("exported template", append(s.summaryLogFields(template.Summary()), dur)...) }(time.Now()) return s.next.Export(ctx, opts...) } @@ -176,6 +177,7 @@ func (s *loggingMW) DryRun(ctx context.Context, orgID, userID influxdb.ID, opts fields = append(fields, zap.Stringer("stackID", opt.StackID)) } fields = append(fields, dur) + // todo: should these be Debug logs? s.logger.Info("template dry run successful", fields...) }(time.Now()) return s.next.DryRun(ctx, orgID, userID, opts...) @@ -201,6 +203,7 @@ func (s *loggingMW) Apply(ctx context.Context, orgID, userID influxdb.ID, opts . fields = append(fields, zap.Stringer("stackID", opt.StackID)) } fields = append(fields, dur) + // todo: should these be Debug logs? s.logger.Info("template apply successful", fields...) }(time.Now()) return s.next.Apply(ctx, orgID, userID, opts...) From 7cc195556d3cdddc60c1613dcee0621446af4b71 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 15:37:44 -0600 Subject: [PATCH 02/20] chore: cleanup types --- pkger/service.go | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/pkger/service.go b/pkger/service.go index a4bf52846c..9776641e72 100644 --- a/pkger/service.go +++ b/pkger/service.go @@ -894,12 +894,15 @@ func (s *Service) cloneOrgVariables(ctx context.Context, orgID influxdb.ID, _ st return resources, nil } -type cloneResFn func(context.Context, influxdb.ID, string) ([]ResourceToClone, error) +type ( + cloneResFn func(context.Context, influxdb.ID, string) ([]ResourceToClone, error) + resClone struct { + resType influxdb.ResourceType + cloneFn cloneResFn + } +) -func (s *Service) filterOrgResourceKinds(resourceKindFilters []Kind) []struct { - resType influxdb.ResourceType - cloneFn cloneResFn -} { +func (s *Service) filterOrgResourceKinds(resourceKindFilters []Kind) []resClone { mKinds := map[Kind]cloneResFn{ KindBucket: s.cloneOrgBuckets, KindCheck: s.cloneOrgChecks, @@ -912,23 +915,14 @@ func (s *Service) filterOrgResourceKinds(resourceKindFilters []Kind) []struct { KindVariable: s.cloneOrgVariables, } - newResGen := func(resType influxdb.ResourceType, cloneFn cloneResFn) struct { - resType influxdb.ResourceType - cloneFn cloneResFn - } { - return struct { - resType influxdb.ResourceType - cloneFn cloneResFn - }{ + newResGen := func(resType influxdb.ResourceType, cloneFn cloneResFn) resClone { + return resClone{ resType: resType, cloneFn: cloneFn, } } - var resourceTypeGens []struct { - resType influxdb.ResourceType - cloneFn cloneResFn - } + var resourceTypeGens []resClone if len(resourceKindFilters) == 0 { for k, cloneFn := range mKinds { resourceTypeGens = append(resourceTypeGens, newResGen(k.ResourceType(), cloneFn)) From aca096a79ae4e239fe3ec5f504b49b778319cdd1 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 16:10:13 -0600 Subject: [PATCH 03/20] chore: actually restrict exported dashboards to specified name --- pkger/clone_resource.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index 1bbb223d5f..9e9e74d7b6 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -201,7 +201,6 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT metaName = ex.uniqName() } - fmt.Println("STACK Resourcing", r.Kind, r.Name, r.ID, object.Name()) stackResource := StackResource{ APIVersion: APIVersion, ID: r.ID, @@ -235,8 +234,12 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } mapResource(ch.GetOrgID(), uniqByNameResID, KindCheck, CheckToObject(r.Name, ch)) case r.Kind.is(KindDashboard): - filter := influxdb.DashboardFilter{} + var ( + hasID bool + filter = influxdb.DashboardFilter{} + ) if r.ID != influxdb.ID(0) { + hasID = true filter.IDs = []*influxdb.ID{&r.ID} } dashs, i, err := ex.dashSVC.FindDashboards(ctx, filter, influxdb.DefaultDashboardFindOptions) @@ -248,6 +251,9 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } for _, dash := range dashs { + if (len(r.Name) > 0 && dash.Name != r.Name) || (hasID && dash.ID != r.ID) { + continue + } for _, cell := range dash.Cells { v, err := ex.dashSVC.GetDashboardCellView(ctx, dash.ID, cell.ID) if err != nil { From a0c0c392da11802aeb0b6bd4f693b83f95ba8014 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 16:31:25 -0600 Subject: [PATCH 04/20] feat: export buckets by name --- pkger/clone_resource.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index 9e9e74d7b6..8e721633da 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -220,11 +220,24 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT switch { case r.Kind.is(KindBucket): - bkt, err := ex.bucketSVC.FindBucketByID(ctx, r.ID) + filter := influxdb.BucketFilter{} + if r.ID != influxdb.ID(0) { + filter.ID = &r.ID + } + if len(r.Name) > 0 { + filter.Name = &r.Name + } + + bkts, n, err := ex.bucketSVC.FindBuckets(ctx, filter) if err != nil { return err } - mapResource(bkt.OrgID, uniqByNameResID, KindBucket, BucketToObject(r.Name, *bkt)) + if n < 1 { + return errors.New("no buckets found") + } + for _, bkt := range bkts { + mapResource(bkt.OrgID, uniqByNameResID, KindBucket, BucketToObject(r.Name, *bkt)) + } case r.Kind.is(KindCheck), r.Kind.is(KindCheckDeadman), r.Kind.is(KindCheckThreshold): @@ -242,11 +255,11 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT hasID = true filter.IDs = []*influxdb.ID{&r.ID} } - dashs, i, err := ex.dashSVC.FindDashboards(ctx, filter, influxdb.DefaultDashboardFindOptions) + dashs, n, err := ex.dashSVC.FindDashboards(ctx, filter, influxdb.DefaultDashboardFindOptions) if err != nil { return err } - if i < 1 { + if n < 1 { return errors.New("no dashboards found") } From e69af9a331ebad911301646be8e879ec6c37e8f6 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 16:55:23 -0600 Subject: [PATCH 05/20] feat: export checks by name --- pkger/clone_resource.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index 8e721633da..f0151aaaf3 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -235,17 +235,30 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT if n < 1 { return errors.New("no buckets found") } + for _, bkt := range bkts { mapResource(bkt.OrgID, uniqByNameResID, KindBucket, BucketToObject(r.Name, *bkt)) } - case r.Kind.is(KindCheck), - r.Kind.is(KindCheckDeadman), - r.Kind.is(KindCheckThreshold): - ch, err := ex.checkSVC.FindCheckByID(ctx, r.ID) + case r.Kind.is(KindCheck), r.Kind.is(KindCheckDeadman), r.Kind.is(KindCheckThreshold): + filter := influxdb.CheckFilter{} + if r.ID != influxdb.ID(0) { + filter.ID = &r.ID + } + if len(r.Name) > 0 { + filter.Name = &r.Name + } + + chs, n, err := ex.checkSVC.FindChecks(ctx, filter) if err != nil { return err } - mapResource(ch.GetOrgID(), uniqByNameResID, KindCheck, CheckToObject(r.Name, ch)) + if n < 1 { + return errors.New("no checks found") + } + + for _, ch := range chs { + mapResource(ch.GetOrgID(), uniqByNameResID, KindCheck, CheckToObject(r.Name, ch)) + } case r.Kind.is(KindDashboard): var ( hasID bool @@ -255,6 +268,7 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT hasID = true filter.IDs = []*influxdb.ID{&r.ID} } + dashs, n, err := ex.dashSVC.FindDashboards(ctx, filter, influxdb.DefaultDashboardFindOptions) if err != nil { return err From 7565ce299236c3cda825e2f387946979337f3136 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 17:01:23 -0600 Subject: [PATCH 06/20] feat: export labels by name --- pkger/clone_resource.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index f0151aaaf3..0535986cd1 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -292,11 +292,23 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT mapResource(dash.OrganizationID, dash.ID, KindDashboard, DashboardToObject(r.Name, *dash)) } case r.Kind.is(KindLabel): - l, err := ex.labelSVC.FindLabelByID(ctx, r.ID) - if err != nil { - return err + switch { + case r.ID != influxdb.ID(0): + l, err := ex.labelSVC.FindLabelByID(ctx, r.ID) + if err != nil { + return err + } + mapResource(l.OrgID, uniqByNameResID, KindLabel, LabelToObject(r.Name, *l)) + case len(r.Name) > 0: + labels, err := ex.labelSVC.FindLabels(ctx, influxdb.LabelFilter{Name: r.Name}) + if err != nil { + return err + } + + for _, l := range labels { + mapResource(l.OrgID, uniqByNameResID, KindLabel, LabelToObject(r.Name, *l)) + } } - mapResource(l.OrgID, uniqByNameResID, KindLabel, LabelToObject(r.Name, *l)) case r.Kind.is(KindNotificationEndpoint), r.Kind.is(KindNotificationEndpointHTTP), r.Kind.is(KindNotificationEndpointPagerDuty), From 57d21d26c366908409b2e01e9584042cbc4e3383 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 17:19:55 -0600 Subject: [PATCH 07/20] feat: export notification endpoints by name --- pkger/clone_resource.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index 0535986cd1..d93f671001 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -313,11 +313,29 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT r.Kind.is(KindNotificationEndpointHTTP), r.Kind.is(KindNotificationEndpointPagerDuty), r.Kind.is(KindNotificationEndpointSlack): - e, err := ex.endpointSVC.FindNotificationEndpointByID(ctx, r.ID) + var ( + hasID bool + filter = influxdb.NotificationEndpointFilter{} + ) + if r.ID != influxdb.ID(0) { + filter.ID = &r.ID + } + + endpoints, n, err := ex.endpointSVC.FindNotificationEndpoints(ctx, filter) if err != nil { return err } - mapResource(e.GetOrgID(), uniqByNameResID, KindNotificationEndpoint, NotificationEndpointToObject(r.Name, e)) + if n < 1 { + return errors.New("no notification endpoints found") + } + + for _, e := range endpoints { + if (len(r.Name) > 0 && e.GetName() != r.Name) || (hasID && e.GetID() != r.ID) { + continue + } + + mapResource(e.GetOrgID(), uniqByNameResID, KindNotificationEndpoint, NotificationEndpointToObject(r.Name, e)) + } case r.Kind.is(KindNotificationRule): rule, ruleEndpoint, err := ex.getEndpointRule(ctx, r.ID) if err != nil { From a9421279c54bd0963582edaa8f9823b9241d31d9 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 17:29:25 -0600 Subject: [PATCH 08/20] feat: export notification rules by name --- pkger/clone_resource.go | 80 ++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index d93f671001..d20159e750 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -337,20 +337,49 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT mapResource(e.GetOrgID(), uniqByNameResID, KindNotificationEndpoint, NotificationEndpointToObject(r.Name, e)) } case r.Kind.is(KindNotificationRule): - rule, ruleEndpoint, err := ex.getEndpointRule(ctx, r.ID) - if err != nil { - return err + var rules []influxdb.NotificationRule + + if r.ID != influxdb.ID(0) { + r, err := ex.ruleSVC.FindNotificationRuleByID(ctx, r.ID) + if err != nil { + return err + } + rules = append(rules, r) } - endpointKey := newExportKey(ruleEndpoint.GetOrgID(), uniqByNameResID, KindNotificationEndpoint, ruleEndpoint.GetName()) - object, ok := ex.mObjects[endpointKey] - if !ok { - mapResource(ruleEndpoint.GetOrgID(), uniqByNameResID, KindNotificationEndpoint, NotificationEndpointToObject("", ruleEndpoint)) - object = ex.mObjects[endpointKey] - } - endpointObjectName := object.Name() + if len(r.Name) != 0 { + allRules, n, err := ex.ruleSVC.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{}) + if err != nil { + return err + } + if n < 1 { + return errors.New("no notification rules found") + } - mapResource(rule.GetOrgID(), rule.GetID(), KindNotificationRule, NotificationRuleToObject(r.Name, endpointObjectName, rule)) + for _, rule := range allRules { + if rule.GetName() != r.Name { + continue + } + rules = append(rules, rule) + } + } + + for _, rule := range rules { + ruleEndpoint, err := ex.endpointSVC.FindNotificationEndpointByID(ctx, rule.GetEndpointID()) + if err != nil { + return err + } + + endpointKey := newExportKey(ruleEndpoint.GetOrgID(), uniqByNameResID, KindNotificationEndpoint, ruleEndpoint.GetName()) + object, ok := ex.mObjects[endpointKey] + if !ok { + mapResource(ruleEndpoint.GetOrgID(), uniqByNameResID, KindNotificationEndpoint, NotificationEndpointToObject("", ruleEndpoint)) + object = ex.mObjects[endpointKey] + } + endpointObjectName := object.Name() + + mapResource(rule.GetOrgID(), rule.GetID(), KindNotificationRule, NotificationRuleToObject(r.Name, endpointObjectName, rule)) + } case r.Kind.is(KindTask): t, err := ex.taskSVC.FindTaskByID(ctx, r.ID) if err != nil { @@ -461,20 +490,6 @@ func (ex *resourceExporter) resourceCloneAssociationsGen(ctx context.Context, la return cloneFn, nil } -func (ex *resourceExporter) getEndpointRule(ctx context.Context, id influxdb.ID) (influxdb.NotificationRule, influxdb.NotificationEndpoint, error) { - rule, err := ex.ruleSVC.FindNotificationRuleByID(ctx, id) - if err != nil { - return nil, nil, err - } - - ruleEndpoint, err := ex.endpointSVC.FindNotificationEndpointByID(ctx, rule.GetEndpointID()) - if err != nil { - return nil, nil, err - } - - return rule, ruleEndpoint, nil -} - func (ex *resourceExporter) uniqName() string { return uniqMetaName(ex.nameGen, idGenerator, ex.mPkgNames) } @@ -491,21 +506,6 @@ func uniqMetaName(nameGen NameGenerator, idGen influxdb.IDGenerator, existingNam return name } -func findDashboardByIDFull(ctx context.Context, dashSVC influxdb.DashboardService, id influxdb.ID) (*influxdb.Dashboard, error) { - dash, err := dashSVC.FindDashboardByID(ctx, id) - if err != nil { - return nil, err - } - for _, cell := range dash.Cells { - v, err := dashSVC.GetDashboardCellView(ctx, id, cell.ID) - if err != nil { - return nil, err - } - cell.View = v - } - return dash, nil -} - func uniqResourcesToClone(resources []ResourceToClone) []ResourceToClone { type key struct { kind Kind From 0413713ba16b9fa555bdba92d4042b309a2aca83 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 17:35:04 -0600 Subject: [PATCH 09/20] feat: export tasks by name --- pkger/clone_resource.go | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index d20159e750..68e07000f9 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -381,11 +381,26 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT mapResource(rule.GetOrgID(), rule.GetID(), KindNotificationRule, NotificationRuleToObject(r.Name, endpointObjectName, rule)) } case r.Kind.is(KindTask): - t, err := ex.taskSVC.FindTaskByID(ctx, r.ID) - if err != nil { - return err + switch { + case r.ID != influxdb.ID(0): + t, err := ex.taskSVC.FindTaskByID(ctx, r.ID) + if err != nil { + return err + } + mapResource(t.OrganizationID, t.ID, KindTask, TaskToObject(r.Name, *t)) + case len(r.Name) > 0: + tasks, n, err := ex.taskSVC.FindTasks(ctx, influxdb.TaskFilter{Name: &r.Name}) + if err != nil { + return err + } + if n < 1 { + return errors.New("no tasks found") + } + + for _, t := range tasks { + mapResource(t.OrganizationID, uniqByNameResID, KindTask, TaskToObject(r.Name, *t)) + } } - mapResource(t.OrganizationID, t.ID, KindTask, TaskToObject(r.Name, *t)) case r.Kind.is(KindTelegraf): t, err := ex.teleSVC.FindTelegrafConfigByID(ctx, r.ID) if err != nil { From 826462f5f8c7522c434aed29b2ae412228258d43 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 17:37:46 -0600 Subject: [PATCH 10/20] feat: export telegraf configs by name --- pkger/clone_resource.go | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index 68e07000f9..3f71dded90 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -402,11 +402,30 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } } case r.Kind.is(KindTelegraf): - t, err := ex.teleSVC.FindTelegrafConfigByID(ctx, r.ID) - if err != nil { - return err + switch { + case r.ID != influxdb.ID(0): + t, err := ex.teleSVC.FindTelegrafConfigByID(ctx, r.ID) + if err != nil { + return err + } + mapResource(t.OrgID, t.ID, KindTelegraf, TelegrafToObject(r.Name, *t)) + case len(r.Name) > 0: + telegrafs, n, err := ex.teleSVC.FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{}) + if err != nil { + return err + } + if n < 1 { + return errors.New("no telegraf configs found") + } + + for _, t := range telegrafs { + if t.Name != r.Name { + continue + } + + mapResource(t.OrgID, t.ID, KindTelegraf, TelegrafToObject(r.Name, *t)) + } } - mapResource(t.OrgID, t.ID, KindTelegraf, TelegrafToObject(r.Name, *t)) case r.Kind.is(KindVariable): v, err := ex.varSVC.FindVariableByID(ctx, r.ID) if err != nil { From f5731b83977baee9306d434e915aaec0b0eacf78 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 17:39:55 -0600 Subject: [PATCH 11/20] feat: export variables by name --- pkger/clone_resource.go | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index 3f71dded90..be12452439 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -339,15 +339,14 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT case r.Kind.is(KindNotificationRule): var rules []influxdb.NotificationRule - if r.ID != influxdb.ID(0) { + switch { + case r.ID != influxdb.ID(0): r, err := ex.ruleSVC.FindNotificationRuleByID(ctx, r.ID) if err != nil { return err } rules = append(rules, r) - } - - if len(r.Name) != 0 { + case len(r.Name) != 0: allRules, n, err := ex.ruleSVC.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{}) if err != nil { return err @@ -427,11 +426,27 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } } case r.Kind.is(KindVariable): - v, err := ex.varSVC.FindVariableByID(ctx, r.ID) - if err != nil { - return err + switch { + case r.ID != influxdb.ID(0): + v, err := ex.varSVC.FindVariableByID(ctx, r.ID) + if err != nil { + return err + } + mapResource(v.OrganizationID, uniqByNameResID, KindVariable, VariableToObject(r.Name, *v)) + case len(r.Name) > 0: + variables, err := ex.varSVC.FindVariables(ctx, influxdb.VariableFilter{}) + if err != nil { + return err + } + + for _, v := range variables { + if v.Name != r.Name { + continue + } + + mapResource(v.OrganizationID, uniqByNameResID, KindVariable, VariableToObject(r.Name, *v)) + } } - mapResource(v.OrganizationID, uniqByNameResID, KindVariable, VariableToObject(r.Name, *v)) default: return errors.New("unsupported kind provided: " + string(r.Kind)) } From 1d9a5623e3b2af35101328077918b0bf6fa4d7fa Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 27 Aug 2020 17:48:13 -0600 Subject: [PATCH 12/20] chore: remove name from service clone org resources these functions are not hit by the cli --- pkger/service.go | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/pkger/service.go b/pkger/service.go index 9776641e72..305cfc67c7 100644 --- a/pkger/service.go +++ b/pkger/service.go @@ -670,7 +670,7 @@ func (s *Service) Export(ctx context.Context, setters ...ExportOptFn) (*Template func (s *Service) cloneOrgResources(ctx context.Context, orgID influxdb.ID, resourceKinds []Kind) ([]ResourceToClone, error) { var resources []ResourceToClone for _, resGen := range s.filterOrgResourceKinds(resourceKinds) { - existingResources, err := resGen.cloneFn(ctx, orgID, "") + existingResources, err := resGen.cloneFn(ctx, orgID) if err != nil { return nil, ierrors.Wrap(err, "finding "+string(resGen.resType)) } @@ -680,15 +680,9 @@ func (s *Service) cloneOrgResources(ctx context.Context, orgID influxdb.ID, reso return resources, nil } -func (s *Service) cloneOrgBuckets(ctx context.Context, orgID influxdb.ID, name string) ([]ResourceToClone, error) { - var n *string - if len(name) > 0 { - n = &name - } - +func (s *Service) cloneOrgBuckets(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { buckets, _, err := s.bucketSVC.FindBuckets(ctx, influxdb.BucketFilter{ OrganizationID: &orgID, - Name: n, }) if err != nil { return nil, err @@ -708,15 +702,9 @@ func (s *Service) cloneOrgBuckets(ctx context.Context, orgID influxdb.ID, name s return resources, nil } -func (s *Service) cloneOrgChecks(ctx context.Context, orgID influxdb.ID, name string) ([]ResourceToClone, error) { - var n *string - if len(name) > 0 { - n = &name - } - +func (s *Service) cloneOrgChecks(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { checks, _, err := s.checkSVC.FindChecks(ctx, influxdb.CheckFilter{ OrgID: &orgID, - Name: n, }) if err != nil { return nil, err @@ -733,7 +721,7 @@ func (s *Service) cloneOrgChecks(ctx context.Context, orgID influxdb.ID, name st return resources, nil } -func (s *Service) cloneOrgDashboards(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { +func (s *Service) cloneOrgDashboards(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { dashs, _, err := s.dashSVC.FindDashboards(ctx, influxdb.DashboardFilter{ OrganizationID: &orgID, }, influxdb.FindOptions{Limit: 100}) @@ -751,13 +739,10 @@ func (s *Service) cloneOrgDashboards(ctx context.Context, orgID influxdb.ID, _ s return resources, nil } -func (s *Service) cloneOrgLabels(ctx context.Context, orgID influxdb.ID, name string) ([]ResourceToClone, error) { +func (s *Service) cloneOrgLabels(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { filter := influxdb.LabelFilter{ OrgID: &orgID, } - if len(name) > 0 { - filter.Name = name - } // todo: if this was ever called, it would error, 100 is the limit. labels, err := s.labelSVC.FindLabels(ctx, filter, influxdb.FindOptions{Limit: 10000}) @@ -776,7 +761,7 @@ func (s *Service) cloneOrgLabels(ctx context.Context, orgID influxdb.ID, name st return resources, nil } -func (s *Service) cloneOrgNotificationEndpoints(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { +func (s *Service) cloneOrgNotificationEndpoints(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { endpoints, _, err := s.endpointSVC.FindNotificationEndpoints(ctx, influxdb.NotificationEndpointFilter{ OrgID: &orgID, }) @@ -795,7 +780,7 @@ func (s *Service) cloneOrgNotificationEndpoints(ctx context.Context, orgID influ return resources, nil } -func (s *Service) cloneOrgNotificationRules(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { +func (s *Service) cloneOrgNotificationRules(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { rules, _, err := s.ruleSVC.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{ OrgID: &orgID, }) @@ -814,7 +799,7 @@ func (s *Service) cloneOrgNotificationRules(ctx context.Context, orgID influxdb. return resources, nil } -func (s *Service) cloneOrgTasks(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { +func (s *Service) cloneOrgTasks(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { tasks, err := s.getAllTasks(ctx, orgID) if err != nil { return nil, err @@ -859,7 +844,7 @@ func (s *Service) cloneOrgTasks(ctx context.Context, orgID influxdb.ID, _ string return resources, nil } -func (s *Service) cloneOrgTelegrafs(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { +func (s *Service) cloneOrgTelegrafs(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { teles, _, err := s.teleSVC.FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{OrgID: &orgID}) if err != nil { return nil, err @@ -875,7 +860,7 @@ func (s *Service) cloneOrgTelegrafs(ctx context.Context, orgID influxdb.ID, _ st return resources, nil } -func (s *Service) cloneOrgVariables(ctx context.Context, orgID influxdb.ID, _ string) ([]ResourceToClone, error) { +func (s *Service) cloneOrgVariables(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) { vars, err := s.varSVC.FindVariables(ctx, influxdb.VariableFilter{ OrganizationID: &orgID, }, influxdb.FindOptions{Limit: 10000}) @@ -895,7 +880,7 @@ func (s *Service) cloneOrgVariables(ctx context.Context, orgID influxdb.ID, _ st } type ( - cloneResFn func(context.Context, influxdb.ID, string) ([]ResourceToClone, error) + cloneResFn func(context.Context, influxdb.ID) ([]ResourceToClone, error) resClone struct { resType influxdb.ResourceType cloneFn cloneResFn From 8c61b922dd23c6b51d9fcdcf9cd897630eec893a Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 3 Sep 2020 17:56:02 -0600 Subject: [PATCH 13/20] chore: update old tests and add new tests --- pkger/clone_resource.go | 68 +- pkger/parser.go | 5 +- pkger/parser_models.go | 1 - pkger/parser_test.go | 122 ++-- pkger/service.go | 1 + pkger/service_test.go | 1439 +++++++++++++++++++++++++++++++++++++-- 6 files changed, 1499 insertions(+), 137 deletions(-) diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index be12452439..47e9ede4d4 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -135,7 +135,6 @@ func (ex *resourceExporter) Export(ctx context.Context, resourcesToClone []Resou // i.e. if a bucket depends on a label, then labels need to be run first // to guarantee they are available before a bucket is exported. sort.Slice(resourcesToClone, func(i, j int) bool { - // todo: how did this compile, i just added Name to the type. iName, jName := resourcesToClone[i].Name, resourcesToClone[j].Name iKind, jKind := resourcesToClone[i].Kind, resourcesToClone[j].Kind @@ -237,7 +236,7 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } for _, bkt := range bkts { - mapResource(bkt.OrgID, uniqByNameResID, KindBucket, BucketToObject(r.Name, *bkt)) + mapResource(bkt.OrgID, bkt.ID, KindBucket, BucketToObject(r.Name, *bkt)) } case r.Kind.is(KindCheck), r.Kind.is(KindCheckDeadman), r.Kind.is(KindCheckThreshold): filter := influxdb.CheckFilter{} @@ -247,7 +246,6 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT if len(r.Name) > 0 { filter.Name = &r.Name } - chs, n, err := ex.checkSVC.FindChecks(ctx, filter) if err != nil { return err @@ -257,7 +255,7 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } for _, ch := range chs { - mapResource(ch.GetOrgID(), uniqByNameResID, KindCheck, CheckToObject(r.Name, ch)) + mapResource(ch.GetOrgID(), ch.GetID(), KindCheck, CheckToObject(r.Name, ch)) } case r.Kind.is(KindDashboard): var ( @@ -269,18 +267,17 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT filter.IDs = []*influxdb.ID{&r.ID} } - dashs, n, err := ex.dashSVC.FindDashboards(ctx, filter, influxdb.DefaultDashboardFindOptions) + dashs, _, err := ex.dashSVC.FindDashboards(ctx, filter, influxdb.DefaultDashboardFindOptions) if err != nil { return err } - if n < 1 { - return errors.New("no dashboards found") - } + var mapped bool for _, dash := range dashs { - if (len(r.Name) > 0 && dash.Name != r.Name) || (hasID && dash.ID != r.ID) { + if (!hasID && len(r.Name) > 0 && dash.Name != r.Name) || (hasID && dash.ID != r.ID) { continue } + for _, cell := range dash.Cells { v, err := ex.dashSVC.GetDashboardCellView(ctx, dash.ID, cell.ID) if err != nil { @@ -290,6 +287,11 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } mapResource(dash.OrganizationID, dash.ID, KindDashboard, DashboardToObject(r.Name, *dash)) + mapped = true + } + + if !mapped { + return errors.New("no dashboards found") } case r.Kind.is(KindLabel): switch { @@ -298,7 +300,8 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT if err != nil { return err } - mapResource(l.OrgID, uniqByNameResID, KindLabel, LabelToObject(r.Name, *l)) + + mapResource(l.OrgID, l.ID, KindLabel, LabelToObject(r.Name, *l)) case len(r.Name) > 0: labels, err := ex.labelSVC.FindLabels(ctx, influxdb.LabelFilter{Name: r.Name}) if err != nil { @@ -306,7 +309,7 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } for _, l := range labels { - mapResource(l.OrgID, uniqByNameResID, KindLabel, LabelToObject(r.Name, *l)) + mapResource(l.OrgID, l.ID, KindLabel, LabelToObject(r.Name, *l)) } } case r.Kind.is(KindNotificationEndpoint), @@ -318,23 +321,27 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT filter = influxdb.NotificationEndpointFilter{} ) if r.ID != influxdb.ID(0) { + hasID = true filter.ID = &r.ID } - endpoints, n, err := ex.endpointSVC.FindNotificationEndpoints(ctx, filter) + endpoints, _, err := ex.endpointSVC.FindNotificationEndpoints(ctx, filter) if err != nil { return err } - if n < 1 { - return errors.New("no notification endpoints found") - } + var mapped bool for _, e := range endpoints { - if (len(r.Name) > 0 && e.GetName() != r.Name) || (hasID && e.GetID() != r.ID) { + if (!hasID && len(r.Name) > 0 && e.GetName() != r.Name) || (hasID && e.GetID() != r.ID) { continue } - mapResource(e.GetOrgID(), uniqByNameResID, KindNotificationEndpoint, NotificationEndpointToObject(r.Name, e)) + mapResource(e.GetOrgID(), e.GetID(), KindNotificationEndpoint, NotificationEndpointToObject(r.Name, e)) + mapped = true + } + + if !mapped { + return errors.New("no notification endpoints found") } case r.Kind.is(KindNotificationRule): var rules []influxdb.NotificationRule @@ -347,13 +354,10 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } rules = append(rules, r) case len(r.Name) != 0: - allRules, n, err := ex.ruleSVC.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{}) + allRules, _, err := ex.ruleSVC.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{}) if err != nil { return err } - if n < 1 { - return errors.New("no notification rules found") - } for _, rule := range allRules { if rule.GetName() != r.Name { @@ -363,6 +367,10 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } } + if len(rules) == 0 { + return errors.New("no notification rules found") + } + for _, rule := range rules { ruleEndpoint, err := ex.endpointSVC.FindNotificationEndpointByID(ctx, rule.GetEndpointID()) if err != nil { @@ -397,7 +405,7 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } for _, t := range tasks { - mapResource(t.OrganizationID, uniqByNameResID, KindTask, TaskToObject(r.Name, *t)) + mapResource(t.OrganizationID, t.ID, KindTask, TaskToObject(r.Name, *t)) } } case r.Kind.is(KindTelegraf): @@ -409,21 +417,24 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } mapResource(t.OrgID, t.ID, KindTelegraf, TelegrafToObject(r.Name, *t)) case len(r.Name) > 0: - telegrafs, n, err := ex.teleSVC.FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{}) + telegrafs, _, err := ex.teleSVC.FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{}) if err != nil { return err } - if n < 1 { - return errors.New("no telegraf configs found") - } + var mapped bool for _, t := range telegrafs { if t.Name != r.Name { continue } mapResource(t.OrgID, t.ID, KindTelegraf, TelegrafToObject(r.Name, *t)) + mapped = true } + if !mapped { + return errors.New("no telegraf configs found") + } + } case r.Kind.is(KindVariable): switch { @@ -439,12 +450,17 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT return err } + var mapped bool for _, v := range variables { if v.Name != r.Name { continue } mapResource(v.OrganizationID, uniqByNameResID, KindVariable, VariableToObject(r.Name, *v)) + mapped = true + } + if !mapped { + return errors.New("no variables found") } } default: diff --git a/pkger/parser.go b/pkger/parser.go index 8b3e29bac5..198c25b460 100644 --- a/pkger/parser.go +++ b/pkger/parser.go @@ -860,7 +860,8 @@ func (p *Template) graphLabels() *parseErr { func (p *Template) graphChecks() *parseErr { p.mChecks = make(map[string]*check) - tracker := p.trackNames(true) + // todo: what is the business goal wrt having unique names? (currently duplicates are allowed) + tracker := p.trackNames(false) checkKinds := []struct { kind Kind @@ -974,7 +975,7 @@ func (p *Template) graphDashboards() *parseErr { func (p *Template) graphNotificationEndpoints() *parseErr { p.mNotificationEndpoints = make(map[string]*notificationEndpoint) - tracker := p.trackNames(true) + tracker := p.trackNames(false) notificationKinds := []struct { kind Kind diff --git a/pkger/parser_models.go b/pkger/parser_models.go index 7606cf656b..b5dae8f522 100644 --- a/pkger/parser_models.go +++ b/pkger/parser_models.go @@ -2100,7 +2100,6 @@ func (v *variable) summarize() SummaryVariable { envRefs = append(envRefs, convertRefToRefSummary(field, sel)) } } - return SummaryVariable{ SummaryIdentifier: SummaryIdentifier{ Kind: KindVariable, diff --git a/pkger/parser_test.go b/pkger/parser_test.go index 0936cef80a..5d67f58184 100644 --- a/pkger/parser_test.go +++ b/pkger/parser_test.go @@ -904,40 +904,42 @@ spec: `, }, }, - { - kind: KindCheckDeadman, - resErr: testTemplateResourceError{ - name: "duplicate meta name and spec name", - validationErrs: 1, - valFields: []string{fieldSpec, fieldAssociations}, - templateStr: ` -apiVersion: influxdata.com/v2alpha1 -kind: CheckDeadman -metadata: - name: check-1 -spec: - every: 5m - level: cRiT - query: > - from(bucket: "rucket_1") |> yield(name: "mean") - statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }" - timeSince: 90s ---- -apiVersion: influxdata.com/v2alpha1 -kind: CheckDeadman -metadata: - name: valid-name -spec: - name: check-1 - every: 5m - level: cRiT - query: > - from(bucket: "rucket_1") |> yield(name: "mean") - statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }" - timeSince: 90s -`, - }, - }, + /* checks are not name unique + { + kind: KindCheckDeadman, + resErr: testTemplateResourceError{ + name: "duplicate meta name and spec name", + validationErrs: 1, + valFields: []string{fieldSpec, fieldAssociations}, + templateStr: ` + apiVersion: influxdata.com/v2alpha1 + kind: CheckDeadman + metadata: + name: check-1 + spec: + every: 5m + level: cRiT + query: > + from(bucket: "rucket_1") |> yield(name: "mean") + statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }" + timeSince: 90s + --- + apiVersion: influxdata.com/v2alpha1 + kind: CheckDeadman + metadata: + name: valid-name + spec: + name: check-1 + every: 5m + level: cRiT + query: > + from(bucket: "rucket_1") |> yield(name: "mean") + statusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }" + timeSince: 90s + `, + }, + }, + */ } for _, tt := range tests { @@ -2985,31 +2987,33 @@ spec: `, }, }, - { - kind: KindNotificationEndpointSlack, - resErr: testTemplateResourceError{ - name: "duplicate meta name and spec name", - validationErrs: 1, - valFields: []string{fieldSpec, fieldName}, - templateStr: `apiVersion: influxdata.com/v2alpha1 -kind: NotificationEndpointSlack -metadata: - name: slack -spec: - description: slack desc - url: https://hooks.slack.com/services/bip/piddy/boppidy ---- -apiVersion: influxdata.com/v2alpha1 -kind: NotificationEndpointSlack -metadata: - name: slack-notification-endpoint -spec: - name: slack - description: slack desc - url: https://hooks.slack.com/services/bip/piddy/boppidy -`, - }, - }, + /* notification endpoints are not name unique + { + kind: KindNotificationEndpointSlack, + resErr: testTemplateResourceError{ + name: "duplicate meta name and spec name", + validationErrs: 1, + valFields: []string{fieldSpec, fieldName}, + templateStr: `apiVersion: influxdata.com/v2alpha1 + kind: NotificationEndpointSlack + metadata: + name: slack + spec: + description: slack desc + url: https://hooks.slack.com/services/bip/piddy/boppidy + --- + apiVersion: influxdata.com/v2alpha1 + kind: NotificationEndpointSlack + metadata: + name: slack-notification-endpoint + spec: + name: slack + description: slack desc + url: https://hooks.slack.com/services/bip/piddy/boppidy + `, + }, + }, + */ } for _, tt := range tests { diff --git a/pkger/service.go b/pkger/service.go index 305cfc67c7..fbafa4c046 100644 --- a/pkger/service.go +++ b/pkger/service.go @@ -569,6 +569,7 @@ func ExportWithExistingResources(resources ...ResourceToClone) ExportOptFn { return func(opt *ExportOpt) error { for _, r := range resources { if err := r.OK(); err != nil { + // todo: log and continue else append r to opt.Resources?? return err } } diff --git a/pkger/service_test.go b/pkger/service_test.go index 85642b9038..cd68257e79 100644 --- a/pkger/service_test.go +++ b/pkger/service_test.go @@ -1834,6 +1834,16 @@ func TestService(t *testing.T) { } return expected, nil } + bktSVC.FindBucketsFn = func(_ context.Context, filter influxdb.BucketFilter, _ ...influxdb.FindOptions) ([]*influxdb.Bucket, int, error) { + if filter.ID != nil { + if *filter.ID != expected.ID { + return nil, 0, errors.New("uh ohhh, wrong id here: " + filter.ID.String()) + } + } else if filter.Name != nil && *filter.Name != expected.Name { + return nil, 0, errors.New("uh ohhh, wrong name here: " + *filter.Name) + } + return []*influxdb.Bucket{expected}, 1, nil + } svc := newTestService(WithBucketSVC(bktSVC), WithLabelSVC(mock.NewLabelService())) @@ -1863,6 +1873,129 @@ func TestService(t *testing.T) { } }) + // todo: bucket names are unique. + t.Run("bucket by name", func(t *testing.T) { + knownBuckets := []*influxdb.Bucket{ + { + ID: influxdb.ID(1), + Name: "bucket", + Description: "desc", + RetentionPeriod: time.Hour, + }, + { + ID: influxdb.ID(2), + Name: "bucketCopy", + Description: "desc", + RetentionPeriod: time.Hour, + }, + { + ID: influxdb.ID(3), + Name: "bucket3", + Description: "desc", + RetentionPeriod: time.Hour, + }, + } + + tests := []struct { + name string + findName string + findID influxdb.ID + expected []*influxdb.Bucket + }{ + { + name: "find bucket with unique name", + findName: "bucket", + expected: []*influxdb.Bucket{knownBuckets[0]}, + }, + { + name: "find no buckets", + findName: "fakeBucket", + expected: nil, + }, + { + name: "find bucket by id", + findID: influxdb.ID(2), + expected: []*influxdb.Bucket{knownBuckets[1]}, + }, + { + // todo: verify this is intended behavior (it is in swagger) + name: "find by id, set new name", + findID: influxdb.ID(2), + findName: "renamedBucket", + expected: []*influxdb.Bucket{knownBuckets[1]}, + }, + } + + for _, tt := range tests { + fn := func(t *testing.T) { + bktSVC := mock.NewBucketService() + bktSVC.FindBucketsFn = func(_ context.Context, filter influxdb.BucketFilter, _ ...influxdb.FindOptions) ([]*influxdb.Bucket, int, error) { + if filter.ID != nil { + for i := range knownBuckets { + if knownBuckets[i].ID == *filter.ID { + return []*influxdb.Bucket{knownBuckets[i]}, 1, nil + } + } + return nil, 0, errors.New("uh ohhh, wrong id here: " + filter.ID.String()) + } else if filter.Name != nil { + bkts := []*influxdb.Bucket{} + + for i := range knownBuckets { + if knownBuckets[i].Name == *filter.Name { + bkts = append(bkts, knownBuckets[i]) + } + } + + if lBkts := len(bkts); lBkts > 0 { + return bkts, lBkts, nil + } + return nil, 0, errors.New("uh ohhh, wrong name here: " + *filter.Name) + } + + return knownBuckets, len(knownBuckets), nil + } + + resToClone := ResourceToClone{ + Kind: KindBucket, + } + if tt.findName != "" { + resToClone.Name = tt.findName + } + if tt.findID != influxdb.ID(0) { + resToClone.ID = tt.findID + } + + svc := newTestService( + WithBucketSVC(bktSVC), + WithLabelSVC(mock.NewLabelService()), + ) + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resToClone)) + if tt.expected == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + + if tt.findName != "" && tt.findID != influxdb.ID(0) { + tt.expected[0].Name = tt.findName + } + + actual := template.Summary().Buckets + require.Len(t, actual, len(tt.expected)) + + for i := range actual { + // can't verify id's match due to the use of SafeID's + assert.Equal(t, tt.expected[i].Name, actual[i].Name) + assert.Equal(t, tt.expected[i].Description, actual[i].Description) + assert.Equal(t, tt.expected[i].RetentionPeriod, actual[i].RetentionPeriod) + } + + assert.True(t, encodeAndDecode(t, template) != nil) + } + } + t.Run(tt.name, fn) + } + }) + t.Run("checks", func(t *testing.T) { tests := []struct { name string @@ -1934,6 +2067,17 @@ func TestService(t *testing.T) { } return tt.expected, nil } + checkSVC.FindChecksFn = func(_ context.Context, filter influxdb.CheckFilter, _ ...influxdb.FindOptions) ([]influxdb.Check, int, error) { + if filter.ID != nil { + if *filter.ID != tt.expected.GetID() { + return nil, 0, errors.New("uh ohhh, wrong id here: " + filter.ID.String()) + } + } else if filter.Name != nil && *filter.Name != tt.expected.GetName() { + return nil, 0, errors.New("uh ohhh, wrong name here: " + *filter.Name) + } + + return []influxdb.Check{tt.expected}, 1, nil + } svc := newTestService(WithCheckSVC(checkSVC)) @@ -1961,6 +2105,179 @@ func TestService(t *testing.T) { } }) + t.Run("checks by name", func(t *testing.T) { + knownChecks := []influxdb.Check{ + &icheck.Threshold{ + Base: newThresholdBase(0), + Thresholds: []icheck.ThresholdConfig{ + icheck.Lesser{ + ThresholdConfigBase: icheck.ThresholdConfigBase{ + AllValues: true, + Level: notification.Critical, + }, + Value: 20, + }, + icheck.Greater{ + ThresholdConfigBase: icheck.ThresholdConfigBase{ + AllValues: true, + Level: notification.Warn, + }, + Value: 30, + }, + icheck.Range{ + ThresholdConfigBase: icheck.ThresholdConfigBase{ + AllValues: true, + Level: notification.Info, + }, + Within: false, // outside_range + Min: 10, + Max: 25, + }, + icheck.Range{ + ThresholdConfigBase: icheck.ThresholdConfigBase{ + AllValues: true, + Level: notification.Ok, + }, + Within: true, // inside_range + Min: 21, + Max: 24, + }, + }, + }, + &icheck.Deadman{ + Base: newThresholdBase(1), + TimeSince: mustDuration(t, time.Hour), + StaleTime: mustDuration(t, 5*time.Hour), + ReportZero: true, + Level: notification.Critical, + }, + &icheck.Deadman{ + Base: icheck.Base{ + ID: influxdb.ID(2), + TaskID: 300, + Name: "check_1", + Description: "desc_2", + Every: mustDuration(t, 2*time.Minute), + Offset: mustDuration(t, 30*time.Second), + Query: influxdb.DashboardQuery{ + Text: `from(bucket: "telegraf") |> range(start: -1m) |> filter(fn: (r) => r._field == "usage_user")`, + }, + StatusMessageTemplate: "Check: ${ r._check_name } is: ${ r._level }", + Tags: []influxdb.Tag{ + {Key: "key_1", Value: "val_1"}, + {Key: "key_2", Value: "val_2"}, + }, + }, + TimeSince: mustDuration(t, time.Hour), + StaleTime: mustDuration(t, 5*time.Hour), + ReportZero: true, + Level: notification.Critical, + }, + } + + tests := []struct { + name string + findName string + findID influxdb.ID + expected []influxdb.Check + }{ + { + name: "find check with unique name", + findName: "check_0", + expected: []influxdb.Check{knownChecks[0]}, + }, + { + name: "find multiple checks with same name", + findName: "check_1", + expected: []influxdb.Check{knownChecks[1], knownChecks[2]}, + }, + { + name: "find no checks", + findName: "fakeCheck", + expected: nil, + }, + { + name: "find check by id", + findID: influxdb.ID(1), + expected: []influxdb.Check{knownChecks[1]}, + }, + { + name: "find check by id, set new name", + findID: influxdb.ID(1), + findName: "chex original", + expected: []influxdb.Check{knownChecks[1]}, + }, + } + + for _, tt := range tests { + fn := func(t *testing.T) { + checkSVC := mock.NewCheckService() + checkSVC.FindChecksFn = func(_ context.Context, filter influxdb.CheckFilter, _ ...influxdb.FindOptions) ([]influxdb.Check, int, error) { + if filter.ID != nil { + for i := range knownChecks { + if knownChecks[i].GetID() == *filter.ID { + return []influxdb.Check{knownChecks[i]}, 1, nil + } + } + return nil, 0, errors.New("uh ohhh, wrong id here: " + filter.ID.String()) + } else if filter.Name != nil { + checks := []influxdb.Check{} + + for i := range knownChecks { + if knownChecks[i].GetName() == *filter.Name { + checks = append(checks, knownChecks[i]) + } + } + + if lChecks := len(checks); lChecks > 0 { + return checks, lChecks, nil + } + + return nil, 0, errors.New("uh ohhh, wrong name here: " + *filter.Name) + } + + return knownChecks, len(knownChecks), nil + } + + resToClone := ResourceToClone{ + Kind: KindCheck, + } + if tt.findName != "" { + resToClone.Name = tt.findName + } + if tt.findID != influxdb.ID(0) { + resToClone.ID = tt.findID + } + + svc := newTestService(WithCheckSVC(checkSVC)) + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resToClone)) + if tt.expected == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + + if tt.findName != "" && tt.findID != influxdb.ID(0) { + tt.expected[0].SetName(tt.findName) + } + + actual := template.Summary().Checks + require.Len(t, actual, len(tt.expected)) + sort.Slice(actual, func(i, j int) bool { + return actual[i].Check.GetDescription() < actual[j].Check.GetDescription() + }) + + for i := range actual { + assert.Equal(t, tt.expected[i].GetName(), actual[i].Check.GetName()) + assert.Equal(t, tt.expected[i].GetDescription(), actual[i].Check.GetDescription()) + } + + assert.True(t, encodeAndDecode(t, template) != nil) + } + } + t.Run(tt.name, fn) + } + }) + newQuery := func() influxdb.DashboardQuery { return influxdb.DashboardQuery{ Text: "from(v.bucket) |> count()", @@ -2362,6 +2679,15 @@ func TestService(t *testing.T) { } return expected, nil } + dashSVC.FindDashboardsF = func(_ context.Context, filter influxdb.DashboardFilter, _ influxdb.FindOptions) ([]*influxdb.Dashboard, int, error) { + if len(filter.IDs) < 1 { + return nil, 0, errors.New("uh ohhh, no id here") + } + if filter.IDs[0] != nil && *filter.IDs[0] != expected.ID { + return nil, 0, errors.New("uh ohhh, wrong id here: " + filter.IDs[0].String()) + } + return []*influxdb.Dashboard{expected}, 1, nil + } dashSVC.GetDashboardCellViewF = func(_ context.Context, id influxdb.ID, cID influxdb.ID) (*influxdb.View, error) { if id == expected.ID && cID == expectedCell.ID { return &tt.expectedView, nil @@ -2413,6 +2739,16 @@ func TestService(t *testing.T) { Description: "desc", }, nil } + dashSVC.FindDashboardsF = func(_ context.Context, filter influxdb.DashboardFilter, _ influxdb.FindOptions) ([]*influxdb.Dashboard, int, error) { + if len(filter.IDs) < 1 { + return nil, 0, errors.New("uh ohhh, no id here") + } + return []*influxdb.Dashboard{{ + ID: *filter.IDs[0], + Name: "dash name", + Description: "desc", + }}, 1, nil + } svc := newTestService(WithDashboardSVC(dashSVC), WithLabelSVC(mock.NewLabelService())) @@ -2442,6 +2778,196 @@ func TestService(t *testing.T) { }) }) + t.Run("dashboard by name", func(t *testing.T) { + id := 0 + newDash := func(name string, view influxdb.View) *influxdb.Dashboard { + id++ + return &influxdb.Dashboard{ + ID: influxdb.ID(id), + Name: name, + Description: fmt.Sprintf("desc_%d", id), + Cells: []*influxdb.Cell{ + { + ID: 0, + CellProperty: influxdb.CellProperty{X: 1, Y: 2, W: 3, H: 4}, + View: &view, + }, + }, + } + } + knownDashboards := []*influxdb.Dashboard{ + newDash("dasher", influxdb.View{ + ViewContents: influxdb.ViewContents{ + Name: "view name", + }, + Properties: influxdb.GaugeViewProperties{ + Type: influxdb.ViewPropertyTypeGauge, + DecimalPlaces: influxdb.DecimalPlaces{IsEnforced: true, Digits: 1}, + Note: "a note", + Prefix: "pre", + TickPrefix: "true", + Suffix: "suf", + TickSuffix: "false", + Queries: []influxdb.DashboardQuery{newQuery()}, + ShowNoteWhenEmpty: true, + ViewColors: newColors("min", "max", "threshold"), + }, + }), + newDash("prancer", influxdb.View{ + ViewContents: influxdb.ViewContents{ + Name: "view name", + }, + Properties: influxdb.HeatmapViewProperties{ + Type: influxdb.ViewPropertyTypeHeatMap, + Note: "a note", + Queries: []influxdb.DashboardQuery{newQuery()}, + ShowNoteWhenEmpty: true, + ViewColors: []string{"#8F8AF4", "#8F8AF4", "#8F8AF4"}, + XColumn: "x", + YColumn: "y", + XDomain: []float64{0, 10}, + YDomain: []float64{0, 100}, + XAxisLabel: "x_label", + XPrefix: "x_prefix", + XSuffix: "x_suffix", + YAxisLabel: "y_label", + YPrefix: "y_prefix", + YSuffix: "y_suffix", + BinSize: 10, + TimeFormat: "", + }, + }), + newDash("prancer", influxdb.View{ + ViewContents: influxdb.ViewContents{ + Name: "view name", + }, + Properties: influxdb.HistogramViewProperties{ + Type: influxdb.ViewPropertyTypeHistogram, + Note: "a note", + Queries: []influxdb.DashboardQuery{newQuery()}, + ShowNoteWhenEmpty: true, + ViewColors: []influxdb.ViewColor{{Type: "scale", Hex: "#8F8AF4", Value: 0}, {Type: "scale", Hex: "#8F8AF4", Value: 0}, {Type: "scale", Hex: "#8F8AF4", Value: 0}}, + FillColumns: []string{"a", "b"}, + XColumn: "_value", + XDomain: []float64{0, 10}, + XAxisLabel: "x_label", + BinCount: 30, + Position: "stacked", + }, + }), + } + + tests := []struct { + name string + findName string + findID influxdb.ID + expected []*influxdb.Dashboard + }{ + { + name: "find dash with unique name", + findName: "dasher", + expected: []*influxdb.Dashboard{knownDashboards[0]}, + }, + { + name: "find multiple dash with shared name", + findName: "prancer", + expected: []*influxdb.Dashboard{knownDashboards[1], knownDashboards[2]}, + }, + { + name: "find no dash", + findName: "fakeDash", + expected: nil, + }, + { + name: "find dash by id", + findID: 1, + expected: []*influxdb.Dashboard{knownDashboards[0]}, + }, + { + name: "find dash by id, set new name", + findID: 1, + findName: "dancer", + expected: []*influxdb.Dashboard{knownDashboards[0]}, + }, + } + + for _, tt := range tests { + fn := func(t *testing.T) { + dashSVC := mock.NewDashboardService() + dashSVC.FindDashboardsF = func(_ context.Context, filter influxdb.DashboardFilter, _ influxdb.FindOptions) ([]*influxdb.Dashboard, int, error) { + if filter.IDs != nil && filter.IDs[0] != nil { + for i := range knownDashboards { + if knownDashboards[i].ID == *filter.IDs[0] { + return []*influxdb.Dashboard{knownDashboards[i]}, 1, nil + } + } + + return nil, 0, errors.New("uh ohhh, wrong id here: " + filter.IDs[0].String()) + } + + return knownDashboards, len(knownDashboards), nil + } + + dashSVC.GetDashboardCellViewF = func(_ context.Context, id influxdb.ID, cID influxdb.ID) (*influxdb.View, error) { + for i := range knownDashboards { + if knownDashboards[i].ID == id { + return knownDashboards[i].Cells[0].View, nil + } + } + + return nil, errors.New("wrongo ids") + } + + resToClone := ResourceToClone{ + Kind: KindDashboard, + } + if tt.findName != "" { + resToClone.Name = tt.findName + } + if tt.findID != influxdb.ID(0) { + resToClone.ID = tt.findID + } + + svc := newTestService( + WithDashboardSVC(dashSVC), + WithLabelSVC(mock.NewLabelService()), + ) + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resToClone)) + if tt.expected == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + + if tt.findName != "" && tt.findID != influxdb.ID(0) { + tt.expected[0].Name = tt.findName + } + + actual := template.Summary().Dashboards + require.Len(t, actual, len(tt.expected)) + sort.Slice(actual, func(i, j int) bool { + return actual[i].Description < actual[j].Description + }) + + for i := range actual { + assert.Equal(t, tt.expected[i].Name, actual[i].Name) + assert.Equal(t, tt.expected[i].Description, actual[i].Description) + + require.Len(t, actual[i].Charts, 1) + ch := actual[i].Charts[0] + assert.Equal(t, int(tt.expected[i].Cells[0].CellProperty.X), ch.XPosition) + assert.Equal(t, int(tt.expected[i].Cells[0].CellProperty.Y), ch.YPosition) + assert.Equal(t, int(tt.expected[i].Cells[0].CellProperty.H), ch.Height) + assert.Equal(t, int(tt.expected[i].Cells[0].CellProperty.W), ch.Width) + assert.Equal(t, tt.expected[i].Cells[0].View.Properties, ch.Properties) + } + + assert.True(t, encodeAndDecode(t, template) != nil) + } + } + t.Run(tt.name, fn) + } + }) + t.Run("label", func(t *testing.T) { tests := []struct { name string @@ -2474,6 +3000,12 @@ func TestService(t *testing.T) { } return expectedLabel, nil } + labelSVC.FindLabelsFn = func(_ context.Context, filter influxdb.LabelFilter) ([]*influxdb.Label, error) { + if filter.Name != expectedLabel.Name { + return nil, errors.New("uh ohhh, wrong name here: " + filter.Name) + } + return []*influxdb.Label{expectedLabel}, nil + } svc := newTestService(WithLabelSVC(labelSVC)) @@ -2503,6 +3035,117 @@ func TestService(t *testing.T) { } }) + t.Run("label by name", func(t *testing.T) { + knownLabels := []*influxdb.Label{ + { + ID: 1, + Name: "label one", + Properties: map[string]string{ + "description": "desc", + "color": "red", + }, + }, + { + ID: 2, + Name: "label two", + Properties: map[string]string{ + "description": "desc2", + "color": "green", + }, + }, + } + tests := []struct { + name string + findName string + findID influxdb.ID + expected []*influxdb.Label + }{ + { + name: "find label by name", + findName: "label one", + expected: []*influxdb.Label{knownLabels[0]}, + }, + { + name: "find no label", + findName: "label none", + expected: nil, + }, + { + name: "find label by id", + findID: influxdb.ID(2), + expected: []*influxdb.Label{knownLabels[1]}, + }, + { + name: "find label by id, set new name", + findName: "label three", + findID: influxdb.ID(2), + expected: []*influxdb.Label{knownLabels[1]}, + }, + } + + for _, tt := range tests { + fn := func(t *testing.T) { + labelSVC := mock.NewLabelService() + labelSVC.FindLabelByIDFn = func(_ context.Context, id influxdb.ID) (*influxdb.Label, error) { + for i := range knownLabels { + if knownLabels[i].ID == id { + return knownLabels[i], nil + } + } + + return nil, errors.New("uh ohhh, wrong id here: " + id.String()) + } + labelSVC.FindLabelsFn = func(_ context.Context, filter influxdb.LabelFilter) ([]*influxdb.Label, error) { + if filter.Name != "" { + for i := range knownLabels { + if knownLabels[i].Name == filter.Name { + return []*influxdb.Label{knownLabels[i]}, nil + } + } + + return nil, errors.New("uh ohhh, wrong name here: " + filter.Name) + } + + return knownLabels, nil + } + + resToClone := ResourceToClone{ + Kind: KindLabel, + } + if tt.findName != "" { + resToClone.Name = tt.findName + } + if tt.findID != influxdb.ID(0) { + resToClone.ID = tt.findID + } + + svc := newTestService(WithLabelSVC(labelSVC)) + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resToClone)) + if tt.expected == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + + if tt.findName != "" && tt.findID != influxdb.ID(0) { + tt.expected[0].Name = tt.findName + } + + actual := template.Summary().Labels + require.Len(t, actual, len(tt.expected)) + + for i := range actual { + assert.Equal(t, tt.expected[i].Name, actual[i].Name) + assert.Equal(t, tt.expected[i].Properties["color"], actual[i].Properties.Color) + assert.Equal(t, tt.expected[i].Properties["description"], actual[i].Properties.Description) + } + + assert.True(t, encodeAndDecode(t, template) != nil) + } + } + t.Run(tt.name, fn) + } + }) + t.Run("notification endpoints", func(t *testing.T) { tests := []struct { name string @@ -2602,6 +3245,12 @@ func TestService(t *testing.T) { } return tt.expected, nil } + endpointSVC.FindNotificationEndpointsF = func(ctx context.Context, filter influxdb.NotificationEndpointFilter, _ ...influxdb.FindOptions) ([]influxdb.NotificationEndpoint, int, error) { + if filter.ID != nil && *filter.ID != tt.expected.GetID() { + return nil, 0, errors.New("uh ohhh, wrong id here: " + filter.ID.String()) + } + return []influxdb.NotificationEndpoint{tt.expected}, 1, nil + } svc := newTestService(WithNotificationEndpointSVC(endpointSVC)) @@ -2632,6 +3281,129 @@ func TestService(t *testing.T) { } }) + knownEndpoints := []influxdb.NotificationEndpoint{ + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: newTestIDPtr(1), + Name: "pd endpoint", + Description: "desc", + Status: influxdb.TaskStatusActive, + }, + ClientURL: "http://example.com", + RoutingKey: influxdb.SecretField{Key: "-routing-key"}, + }, + &endpoint.PagerDuty{ + Base: endpoint.Base{ + ID: newTestIDPtr(2), + Name: "pd-endpoint", + Description: "desc pd", + Status: influxdb.TaskStatusActive, + }, + ClientURL: "http://example.com", + RoutingKey: influxdb.SecretField{Key: "-routing-key"}, + }, + &endpoint.Slack{ + Base: endpoint.Base{ + ID: newTestIDPtr(3), + Name: "pd-endpoint", + Description: "desc slack", + Status: influxdb.TaskStatusInactive, + }, + URL: "http://example.com", + Token: influxdb.SecretField{Key: "tokne"}, + }, + } + + t.Run("notification endpoints by name", func(t *testing.T) { + tests := []struct { + name string + findName string + findID influxdb.ID + expected []influxdb.NotificationEndpoint + }{ + { + name: "find notification endpoint with unique name", + findName: "pd endpoint", + expected: []influxdb.NotificationEndpoint{knownEndpoints[0]}, + }, + { + name: "find multiple notification endpoints with shared name", + findName: "pd-endpoint", + expected: []influxdb.NotificationEndpoint{knownEndpoints[1], knownEndpoints[2]}, + }, + { + name: "find no notification endpoints", + findName: "fakeEndpoint", + expected: nil, + }, + { + name: "find notification endpoint by id", + findID: influxdb.ID(2), + expected: []influxdb.NotificationEndpoint{knownEndpoints[1]}, + }, + { + name: "find by id, set new name", + findID: influxdb.ID(3), + findName: "slack-endpoint", + expected: []influxdb.NotificationEndpoint{knownEndpoints[2]}, + }, + } + + for _, tt := range tests { + fn := func(t *testing.T) { + endpointSVC := mock.NewNotificationEndpointService() + endpointSVC.FindNotificationEndpointsF = func(ctx context.Context, filter influxdb.NotificationEndpointFilter, _ ...influxdb.FindOptions) ([]influxdb.NotificationEndpoint, int, error) { + if filter.ID != nil { + for i := range knownEndpoints { + if knownEndpoints[i].GetID() == *filter.ID { + return []influxdb.NotificationEndpoint{knownEndpoints[i]}, 1, nil + } + } + + return nil, 0, errors.New("uh ohhh, wrong id here: " + filter.ID.String()) + } + + return knownEndpoints, len(knownEndpoints), nil + } + + resToClone := ResourceToClone{ + Kind: KindNotificationEndpoint, + } + if tt.findName != "" { + resToClone.Name = tt.findName + } + if tt.findID != influxdb.ID(0) { + resToClone.ID = tt.findID + } + + svc := newTestService(WithNotificationEndpointSVC(endpointSVC)) + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resToClone)) + if tt.expected == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + + if tt.findName != "" && tt.findID != influxdb.ID(0) { + tt.expected[0].SetName(tt.findName) + } + + actual := template.Summary().NotificationEndpoints + require.Len(t, actual, len(tt.expected)) + + for i := range actual { + assert.Equal(t, tt.expected[i].GetName(), actual[i].NotificationEndpoint.GetName()) + assert.Equal(t, tt.expected[i].GetDescription(), actual[i].NotificationEndpoint.GetDescription()) + assert.Equal(t, tt.expected[i].GetStatus(), actual[i].NotificationEndpoint.GetStatus()) + assert.Equal(t, tt.expected[i].SecretFields(), actual[i].NotificationEndpoint.SecretFields()) + } + + assert.True(t, encodeAndDecode(t, template) != nil) + } + } + t.Run(tt.name, fn) + } + }) + t.Run("notification rules", func(t *testing.T) { newRuleBase := func(id int) rule.Base { return rule.Base{ @@ -2726,6 +3498,9 @@ func TestService(t *testing.T) { ruleSVC.FindNotificationRuleByIDF = func(ctx context.Context, id influxdb.ID) (influxdb.NotificationRule, error) { return tt.rule, nil } + ruleSVC.FindNotificationRulesF = func(ctx context.Context, _ influxdb.NotificationRuleFilter, _ ...influxdb.FindOptions) ([]influxdb.NotificationRule, int, error) { + return []influxdb.NotificationRule{tt.rule}, 1, nil + } svc := newTestService( WithNotificationEndpointSVC(endpointSVC), @@ -2862,6 +3637,193 @@ func TestService(t *testing.T) { }) }) + t.Run("notification rules by name", func(t *testing.T) { + newRuleBase := func(id int, name string) rule.Base { + return rule.Base{ + ID: influxdb.ID(id), + Name: name, + Description: fmt.Sprintf("desc %d", id), + EndpointID: influxdb.ID(1), // todo: setting to id as well likely doesn't work due to safeID + Every: mustDuration(t, time.Hour), + Offset: mustDuration(t, time.Minute), + TagRules: []notification.TagRule{ + {Tag: influxdb.Tag{Key: "k1", Value: "v1"}}, + }, + StatusRules: []notification.StatusRule{ + {CurrentLevel: notification.Ok, PreviousLevel: levelPtr(notification.Warn)}, + {CurrentLevel: notification.Critical}, + }, + } + } + + knownRules := []influxdb.NotificationRule{ + &rule.PagerDuty{ + Base: newRuleBase(1, "pd notify"), + MessageTemplate: "Template", + }, + &rule.PagerDuty{ + Base: newRuleBase(2, "pd-notify"), + MessageTemplate: "Template2 ", + }, + &rule.Slack{ + Base: newRuleBase(3, "pd-notify"), + Channel: "abc", + MessageTemplate: "SLACK TEMPlate", + }, + } + + tests := []struct { + name string + findName string + findID influxdb.ID + expected []influxdb.NotificationRule + }{ + { + name: "find rule with unique name", + findName: "pd notify", + expected: []influxdb.NotificationRule{knownRules[0]}, + }, + { + name: "find multiple rules with shared name", + findName: "pd-notify", + expected: []influxdb.NotificationRule{knownRules[1], knownRules[2]}, + }, + { + name: "find no rules", + findName: "fakeRule", + expected: nil, + }, + { + name: "find rule by id", + findID: influxdb.ID(2), + expected: []influxdb.NotificationRule{knownRules[1]}, + }, + { + name: "find by id, set new name", + findID: influxdb.ID(3), + findName: "slack-notify", + expected: []influxdb.NotificationRule{knownRules[2]}, + }, + } + + for _, tt := range tests { + fn := func(t *testing.T) { + endpointSVC := mock.NewNotificationEndpointService() + endpointSVC.FindNotificationEndpointByIDF = func(ctx context.Context, id influxdb.ID) (influxdb.NotificationEndpoint, error) { + for i := range knownEndpoints { + if knownEndpoints[i].GetID() == id { + return knownEndpoints[i], nil + } + } + + return nil, errors.New("uh ohhh, wrong endpoint id here: " + id.String()) + } + + ruleSVC := mock.NewNotificationRuleStore() + ruleSVC.FindNotificationRuleByIDF = func(ctx context.Context, id influxdb.ID) (influxdb.NotificationRule, error) { + for i := range knownRules { + if knownRules[i].GetID() == id { + return knownRules[i], nil + } + } + + return nil, errors.New("uh ohhh, wrong rule id here: " + id.String()) + } + ruleSVC.FindNotificationRulesF = func(ctx context.Context, _ influxdb.NotificationRuleFilter, _ ...influxdb.FindOptions) ([]influxdb.NotificationRule, int, error) { + return knownRules, len(knownRules), nil + } + + resToClone := ResourceToClone{ + Kind: KindNotificationRule, + } + if tt.findName != "" { + resToClone.Name = tt.findName + } + if tt.findID != influxdb.ID(0) { + resToClone.ID = tt.findID + } + + svc := newTestService( + WithNotificationEndpointSVC(endpointSVC), + WithNotificationRuleSVC(ruleSVC), + ) + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resToClone)) + if tt.expected == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + + actual := template.Summary() + require.Len(t, actual.NotificationRules, len(tt.expected)) + require.Len(t, actual.NotificationEndpoints, 1) + sort.Slice(actual.NotificationRules, func(i, j int) bool { + return actual.NotificationRules[i].Description < actual.NotificationRules[j].Description + }) + sort.Slice(actual.NotificationEndpoints, func(i, j int) bool { + return actual.NotificationEndpoints[i].NotificationEndpoint.GetDescription() < actual.NotificationEndpoints[j].NotificationEndpoint.GetDescription() + }) + + if tt.findName != "" && tt.findID != influxdb.ID(0) { + tt.expected[0].SetName(tt.findName) + } + + for i := range actual.NotificationRules { + assert.Zero(t, actual.NotificationRules[i].ID) + assert.Zero(t, actual.NotificationRules[i].EndpointID) + assert.NotEmpty(t, actual.NotificationRules[i].EndpointType) + assert.NotEmpty(t, actual.NotificationRules[i].EndpointMetaName) + + baseEqual := func(t *testing.T, base rule.Base) { + assert.Equal(t, base.Name, actual.NotificationRules[i].Name) + assert.Equal(t, base.Description, actual.NotificationRules[i].Description) + assert.Equal(t, base.Every.TimeDuration().String(), actual.NotificationRules[i].Every) + assert.Equal(t, base.Offset.TimeDuration().String(), actual.NotificationRules[i].Offset) + + for _, sRule := range base.StatusRules { + expected := SummaryStatusRule{CurrentLevel: sRule.CurrentLevel.String()} + if sRule.PreviousLevel != nil { + expected.PreviousLevel = sRule.PreviousLevel.String() + } + assert.Contains(t, actual.NotificationRules[i].StatusRules, expected) + } + for _, tRule := range base.TagRules { + expected := SummaryTagRule{ + Key: tRule.Key, + Value: tRule.Value, + Operator: tRule.Operator.String(), + } + assert.Contains(t, actual.NotificationRules[i].TagRules, expected) + } + } + + switch p := tt.expected[i].(type) { + case *rule.HTTP: + baseEqual(t, p.Base) + case *rule.PagerDuty: + baseEqual(t, p.Base) + assert.Equal(t, p.MessageTemplate, actual.NotificationRules[i].MessageTemplate) + case *rule.Slack: + baseEqual(t, p.Base) + assert.Equal(t, p.MessageTemplate, actual.NotificationRules[i].MessageTemplate) + } + + for j := range actual.NotificationEndpoints { + endpoint, err := endpointSVC.FindNotificationEndpointByIDF(context.Background(), tt.expected[i].GetEndpointID()) + require.NoError(t, err) + + assert.Equal(t, endpoint.GetName(), actual.NotificationEndpoints[j].NotificationEndpoint.GetName()) + assert.Equal(t, endpoint.GetDescription(), actual.NotificationEndpoints[j].NotificationEndpoint.GetDescription()) + assert.Equal(t, endpoint.GetStatus(), actual.NotificationEndpoints[j].NotificationEndpoint.GetStatus()) + } + } + + assert.True(t, encodeAndDecode(t, template) != nil) + } + } + t.Run(tt.name, fn) + } + }) + t.Run("tasks", func(t *testing.T) { t.Run("single task exports", func(t *testing.T) { tests := []struct { @@ -2902,6 +3864,9 @@ func TestService(t *testing.T) { } return &tt.task, nil } + taskSVC.FindTasksFn = func(ctx context.Context, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) { + return []*influxdb.Task{&tt.task}, 1, nil + } svc := newTestService(WithTaskSVC(taskSVC)) @@ -2984,17 +3949,146 @@ func TestService(t *testing.T) { }) }) + t.Run("tasks by name", func(t *testing.T) { + knownTasks := []*influxdb.Task{ + { + ID: 1, + Name: "task", + Description: "task 1", + Every: time.Minute.String(), + Offset: 10 * time.Second, + Type: influxdb.TaskSystemType, + Flux: `option task = { name: "larry" } from(bucket: "rucket") |> yield()`, + }, + { + ID: 2, + Name: "taskCopy", + Description: "task 2", + Cron: "2 * * * *", + Type: influxdb.TaskSystemType, + Flux: `option task = { name: "curly" } from(bucket: "rucket") |> yield()`, + }, + { + ID: 3, + Name: "taskCopy", + Description: "task 3", + Cron: "2 3 4 5 *", + Type: influxdb.TaskSystemType, + Flux: `option task = { name: "moe" } from(bucket: "rucket") |> yield()`, + }, + } + + tests := []struct { + name string + findName string + findID influxdb.ID + expected []*influxdb.Task + }{ + { + name: "find task with unique name", + findName: "task", + expected: []*influxdb.Task{knownTasks[0]}, + }, + { + name: "find multiple tasks with shared name", + findName: "taskCopy", + expected: []*influxdb.Task{knownTasks[1], knownTasks[2]}, + }, + { + name: "find no tasks", + findName: "faketask", + expected: nil, + }, + { + name: "find task by id", + findID: influxdb.ID(2), + expected: []*influxdb.Task{knownTasks[1]}, + }, + { + name: "find by id, set new name", + findID: influxdb.ID(2), + findName: "renamedTask", + expected: []*influxdb.Task{knownTasks[1]}, + }, + } + + for _, tt := range tests { + fn := func(t *testing.T) { + taskSVC := mock.NewTaskService() + taskSVC.FindTaskByIDFn = func(ctx context.Context, id influxdb.ID) (*influxdb.Task, error) { + for i := range knownTasks { + if knownTasks[i].ID == id { + return knownTasks[i], nil + } + } + + return nil, errors.New("wrong id provided: " + id.String()) + } + taskSVC.FindTasksFn = func(ctx context.Context, filter influxdb.TaskFilter) ([]*influxdb.Task, int, error) { + tasks := []*influxdb.Task{} + for i := range knownTasks { + if knownTasks[i].Name == *filter.Name { + tasks = append(tasks, knownTasks[i]) + } + } + return tasks, len(tasks), nil + } + + resToClone := ResourceToClone{ + Kind: KindTask, + } + if tt.findName != "" { + resToClone.Name = tt.findName + } + if tt.findID != influxdb.ID(0) { + resToClone.ID = tt.findID + } + + svc := newTestService(WithTaskSVC(taskSVC)) + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resToClone)) + if tt.expected == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + + if tt.findName != "" && tt.findID != influxdb.ID(0) { + tt.expected[0].Name = tt.findName + } + + actual := template.Summary().Tasks + require.Len(t, actual, len(tt.expected)) + sort.Slice(actual, func(i, j int) bool { + return actual[i].Description < actual[j].Description + }) + + for i := range actual { + assert.Equal(t, tt.expected[i].Name, actual[i].Name) + assert.Equal(t, tt.expected[i].Cron, actual[i].Cron) + assert.Equal(t, tt.expected[i].Description, actual[i].Description) + assert.Equal(t, tt.expected[i].Every, actual[i].Every) + assert.Equal(t, durToStr(tt.expected[i].Offset), actual[i].Offset) + + assert.Equal(t, `from(bucket: "rucket") |> yield()`, actual[i].Query) + } + assert.True(t, encodeAndDecode(t, template) != nil) + } + } + t.Run(tt.name, fn) + } + }) + t.Run("telegraf configs", func(t *testing.T) { t.Run("allows for duplicate telegraf names to be exported", func(t *testing.T) { + tConfig := &influxdb.TelegrafConfig{ + OrgID: 9000, + Name: "same name", + Description: "desc", + Config: "some config string", + } teleStore := mock.NewTelegrafConfigStore() teleStore.FindTelegrafConfigByIDF = func(ctx context.Context, id influxdb.ID) (*influxdb.TelegrafConfig, error) { - return &influxdb.TelegrafConfig{ - ID: id, - OrgID: 9000, - Name: "same name", - Description: "desc", - Config: "some config string", - }, nil + tConfig.ID = id + return tConfig, nil } svc := newTestService(WithTelegrafSVC(teleStore)) @@ -3009,6 +4103,17 @@ func TestService(t *testing.T) { ID: 2, }, } + + teleStore.FindTelegrafConfigsF = func(ctx context.Context, filter influxdb.TelegrafConfigFilter, _ ...influxdb.FindOptions) ([]*influxdb.TelegrafConfig, int, error) { + tgrafs := []*influxdb.TelegrafConfig{} + for _, r := range resourcesToClone { + t := tConfig + t.ID = r.ID + tgrafs = append(tgrafs, t) + } + return tgrafs, len(tgrafs), nil + } + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resourcesToClone...)) require.NoError(t, err) @@ -3031,6 +4136,120 @@ func TestService(t *testing.T) { }) }) + t.Run("telegraf configs by name", func(t *testing.T) { + knownConfigs := []*influxdb.TelegrafConfig{ + { + ID: 1, + OrgID: 9000, + Name: "my config", + Description: "desc1", + Config: "a config string", + }, + { + ID: 2, + OrgID: 9000, + Name: "telConfig", + Description: "desc2", + Config: "some config string", + }, + { + ID: 3, + OrgID: 9000, + Name: "telConfig", + Description: "desc3", + Config: "some other config string", + }, + } + + tests := []struct { + name string + findName string + findID influxdb.ID + expected []*influxdb.TelegrafConfig + }{ + { + name: "find telegraf with unique name", + findName: "my config", + expected: []*influxdb.TelegrafConfig{knownConfigs[0]}, + }, + { + name: "find multiple telegrafs with shared name", + findName: "telConfig", + expected: []*influxdb.TelegrafConfig{knownConfigs[1], knownConfigs[2]}, + }, + { + name: "find no telegrafs", + findName: "fakeConfig", + expected: nil, + }, + { + name: "find telegraf by id", + findID: influxdb.ID(2), + expected: []*influxdb.TelegrafConfig{knownConfigs[1]}, + }, + { + name: "find by id, set new name", + findID: influxdb.ID(2), + findName: "newConfig", + expected: []*influxdb.TelegrafConfig{knownConfigs[1]}, + }, + } + + for _, tt := range tests { + fn := func(t *testing.T) { + teleStore := mock.NewTelegrafConfigStore() + teleStore.FindTelegrafConfigByIDF = func(_ context.Context, id influxdb.ID) (*influxdb.TelegrafConfig, error) { + for i := range knownConfigs { + if knownConfigs[i].ID == id { + return knownConfigs[i], nil + } + } + return nil, errors.New("uh ohhh, wrong id here: " + id.String()) + } + teleStore.FindTelegrafConfigsF = func(_ context.Context, filter influxdb.TelegrafConfigFilter, _ ...influxdb.FindOptions) ([]*influxdb.TelegrafConfig, int, error) { + return knownConfigs, len(knownConfigs), nil + } + + resToClone := ResourceToClone{ + Kind: KindTelegraf, + } + if tt.findName != "" { + resToClone.Name = tt.findName + } + if tt.findID != influxdb.ID(0) { + resToClone.ID = tt.findID + } + + svc := newTestService(WithTelegrafSVC(teleStore)) + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resToClone)) + if tt.expected == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + + if tt.findName != "" && tt.findID != influxdb.ID(0) { + tt.expected[0].Name = tt.findName + } + + actual := template.Summary().TelegrafConfigs + require.Len(t, actual, len(tt.expected)) + sort.Slice(actual, func(i, j int) bool { + return actual[i].TelegrafConfig.Description < actual[j].TelegrafConfig.Description + }) + + for i := range actual { + assert.Equal(t, tt.expected[i].Name, actual[i].TelegrafConfig.Name) + assert.Equal(t, tt.expected[i].Description, actual[i].TelegrafConfig.Description) + assert.Equal(t, tt.expected[i].Config, actual[i].TelegrafConfig.Config) + } + + assert.True(t, encodeAndDecode(t, template) != nil) + } + } + t.Run(tt.name, fn) + } + }) + t.Run("variable", func(t *testing.T) { tests := []struct { name string @@ -3101,6 +4320,12 @@ func TestService(t *testing.T) { } return &tt.expectedVar, nil } + varSVC.FindVariablesF = func(_ context.Context, filter influxdb.VariableFilter, _ ...influxdb.FindOptions) ([]*influxdb.Variable, error) { + if filter.ID != nil && *filter.ID != tt.expectedVar.ID { + return nil, errors.New("uh ohhh, wrong id here: " + fmt.Sprint(*filter.ID)) + } + return []*influxdb.Variable{&tt.expectedVar}, nil + } svc := newTestService(WithVariableSVC(varSVC), WithLabelSVC(mock.NewLabelService())) @@ -3131,6 +4356,120 @@ func TestService(t *testing.T) { } }) + t.Run("variable by name", func(t *testing.T) { + knownVariables := []*influxdb.Variable{ + { + ID: 1, + Name: "variable", + Description: "desc", + Selected: []string{"val1"}, + Arguments: &influxdb.VariableArguments{ + Type: "constant", + Values: influxdb.VariableConstantValues{"val"}, + }, + }, + { + ID: 2, + Name: "var 2", + Selected: []string{"val2"}, + Arguments: &influxdb.VariableArguments{ + Type: "constant", + Values: influxdb.VariableConstantValues{"val"}, + }, + }, + { + ID: 3, + Name: "var 3", + Selected: []string{"v"}, + Arguments: &influxdb.VariableArguments{ + Type: "map", + Values: influxdb.VariableMapValues{"k": "v"}, + }, + }, + } + + tests := []struct { + name string + findName string + findID influxdb.ID + expected []*influxdb.Variable + }{ + { + name: "find variable with unique name", + findName: "variable", + expected: []*influxdb.Variable{knownVariables[0]}, + }, + { + name: "find no variables", + findName: "fakeVariable", + expected: nil, + }, + { + name: "find variable by id", + findID: influxdb.ID(2), + expected: []*influxdb.Variable{knownVariables[1]}, + }, + { + name: "find by id, set new name", + findID: influxdb.ID(2), + findName: "useful var", + expected: []*influxdb.Variable{knownVariables[1]}, + }, + } + + for _, tt := range tests { + fn := func(t *testing.T) { + varSVC := mock.NewVariableService() + varSVC.FindVariableByIDF = func(_ context.Context, id influxdb.ID) (*influxdb.Variable, error) { + for i := range knownVariables { + if knownVariables[i].ID == id { + return knownVariables[i], nil + } + } + + return nil, errors.New("uh ohhh, wrong id here: " + id.String()) + } + varSVC.FindVariablesF = func(_ context.Context, filter influxdb.VariableFilter, _ ...influxdb.FindOptions) ([]*influxdb.Variable, error) { + return knownVariables, nil + } + + resToClone := ResourceToClone{ + Kind: KindVariable, + } + if tt.findName != "" { + resToClone.Name = tt.findName + } + if tt.findID != influxdb.ID(0) { + resToClone.ID = tt.findID + } + + svc := newTestService(WithVariableSVC(varSVC), WithLabelSVC(mock.NewLabelService())) + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resToClone)) + if tt.expected == nil { + require.Error(t, err) + } else { + require.NoError(t, err) + + if tt.findName != "" && tt.findID != influxdb.ID(0) { + tt.expected[0].Name = tt.findName + } + + actual := template.Summary().Variables + require.Len(t, actual, len(tt.expected)) + + for i := range actual { + assert.Equal(t, tt.expected[i].Name, actual[i].Name) + assert.Equal(t, tt.expected[i].Description, actual[i].Description) + assert.Equal(t, tt.expected[i].Arguments, actual[i].Arguments) + } + + assert.True(t, encodeAndDecode(t, template) != nil) + } + } + t.Run(tt.name, fn) + } + }) + t.Run("includes resource associations", func(t *testing.T) { t.Run("single resource with single association", func(t *testing.T) { expected := &influxdb.Bucket{ @@ -3147,6 +4486,12 @@ func TestService(t *testing.T) { } return expected, nil } + bktSVC.FindBucketsFn = func(_ context.Context, f influxdb.BucketFilter, opts ...influxdb.FindOptions) ([]*influxdb.Bucket, int, error) { + if f.ID != nil && *f.ID != expected.ID { + return nil, 0, errors.New("not suppose to get here") + } + return []*influxdb.Bucket{expected}, 1, nil + } labelSVC := mock.NewLabelService() labelSVC.FindResourceLabelsFn = func(_ context.Context, f influxdb.LabelMappingFilter) ([]*influxdb.Label, error) { @@ -3212,6 +4557,15 @@ func TestService(t *testing.T) { ID: 20, }, } + + bktSVC.FindBucketsFn = func(_ context.Context, f influxdb.BucketFilter, opts ...influxdb.FindOptions) ([]*influxdb.Bucket, int, error) { + bkts := []*influxdb.Bucket{} + for _, r := range resourcesToClone { + bkts = append(bkts, &influxdb.Bucket{ID: r.ID, Name: strconv.Itoa(int(r.ID)), Type: influxdb.BucketTypeUser}) + } + return bkts, len(bkts), nil + } + template, err := svc.Export(context.TODO(), ExportWithExistingResources(resourcesToClone...)) require.NoError(t, err) @@ -3268,19 +4622,20 @@ func TestService(t *testing.T) { t.Run("with org id", func(t *testing.T) { orgID := influxdb.ID(9000) + bkt := &influxdb.Bucket{ID: 1, Name: "bucket"} bktSVC := mock.NewBucketService() bktSVC.FindBucketsFn = func(_ context.Context, f influxdb.BucketFilter, opts ...influxdb.FindOptions) ([]*influxdb.Bucket, int, error) { - if f.OrganizationID == nil || *f.OrganizationID != orgID { + if (f.ID != nil && *f.ID != bkt.ID) && (f.OrganizationID == nil || *f.OrganizationID != orgID) { return nil, 0, errors.New("not suppose to get here") } - return []*influxdb.Bucket{{ID: 1, Name: "bucket"}}, 1, nil + return []*influxdb.Bucket{bkt}, 1, nil } bktSVC.FindBucketByIDFn = func(_ context.Context, id influxdb.ID) (*influxdb.Bucket, error) { if id != 1 { return nil, errors.New("wrong id") } - return &influxdb.Bucket{ID: 1, Name: "bucket"}, nil + return bkt, nil } checkSVC := mock.NewCheckService() @@ -3292,7 +4647,7 @@ func TestService(t *testing.T) { Level: notification.Critical, } checkSVC.FindChecksFn = func(ctx context.Context, f influxdb.CheckFilter, _ ...influxdb.FindOptions) ([]influxdb.Check, int, error) { - if f.OrgID == nil || *f.OrgID != orgID { + if (f.ID != nil && *f.ID != expectedCheck.GetID()) && (f.OrgID == nil || *f.OrgID != orgID) { return nil, 0, errors.New("not suppose to get here") } return []influxdb.Check{expectedCheck}, 1, nil @@ -3301,58 +4656,44 @@ func TestService(t *testing.T) { return expectedCheck, nil } + dash := &influxdb.Dashboard{ + ID: 2, + Name: "dashboard", + Cells: []*influxdb.Cell{}, + } dashSVC := mock.NewDashboardService() dashSVC.FindDashboardsF = func(_ context.Context, f influxdb.DashboardFilter, _ influxdb.FindOptions) ([]*influxdb.Dashboard, int, error) { - if f.OrganizationID == nil || *f.OrganizationID != orgID { + if (f.IDs != nil && len(f.IDs) > 0 && + f.IDs[0] != nil && *f.IDs[0] != dash.ID) && + (f.OrganizationID == nil || *f.OrganizationID != orgID) { return nil, 0, errors.New("not suppose to get here") } - return []*influxdb.Dashboard{{ - ID: 2, - Name: "dashboard", - Cells: []*influxdb.Cell{}, - }}, 1, nil + return []*influxdb.Dashboard{dash}, 1, nil } dashSVC.FindDashboardByIDF = func(_ context.Context, id influxdb.ID) (*influxdb.Dashboard, error) { if id != 2 { return nil, errors.New("wrong id") } - return &influxdb.Dashboard{ - ID: 2, - Name: "dashboard", - Cells: []*influxdb.Cell{}, - }, nil + return dash, nil } + notificationEndpoint := &endpoint.HTTP{ + Base: endpoint.Base{ + ID: newTestIDPtr(2), + Name: "http", + }, + URL: "http://example.com/id", + Username: influxdb.SecretField{Key: "2-username"}, + Password: influxdb.SecretField{Key: "2-password"}, + AuthMethod: "basic", + Method: "POST", + } endpointSVC := mock.NewNotificationEndpointService() endpointSVC.FindNotificationEndpointsF = func(ctx context.Context, f influxdb.NotificationEndpointFilter, _ ...influxdb.FindOptions) ([]influxdb.NotificationEndpoint, int, error) { - id := influxdb.ID(2) - endpoints := []influxdb.NotificationEndpoint{ - &endpoint.HTTP{ - Base: endpoint.Base{ - ID: &id, - Name: "http", - }, - URL: "http://example.com", - Username: influxdb.SecretField{Key: id.String() + "-username"}, - Password: influxdb.SecretField{Key: id.String() + "-password"}, - AuthMethod: "basic", - Method: "POST", - }, - } - return endpoints, len(endpoints), nil + return []influxdb.NotificationEndpoint{notificationEndpoint}, 1, nil } endpointSVC.FindNotificationEndpointByIDF = func(ctx context.Context, id influxdb.ID) (influxdb.NotificationEndpoint, error) { - return &endpoint.HTTP{ - Base: endpoint.Base{ - ID: &id, - Name: "http", - }, - URL: "http://example.com", - Username: influxdb.SecretField{Key: id.String() + "-username"}, - Password: influxdb.SecretField{Key: id.String() + "-password"}, - AuthMethod: "basic", - Method: "POST", - }, nil + return notificationEndpoint, nil } expectedRule := &rule.HTTP{ @@ -3464,7 +4805,7 @@ func TestService(t *testing.T) { assert.Equal(t, "label", labels[0].Name) endpoints := summary.NotificationEndpoints - require.Len(t, endpoints, 1) + require.Len(t, endpoints, 2) // todo: somehow find endpoint by id gets combined with find endpoints unless `uniqByNameResID` is used in clone_resource assert.Equal(t, "http", endpoints[0].NotificationEndpoint.GetName()) rules := summary.NotificationRules From 0eb53e0e0b18cd226bf2d953aa2a45fa2e5dddf4 Mon Sep 17 00:00:00 2001 From: greg linton Date: Thu, 10 Sep 2020 17:49:36 -0600 Subject: [PATCH 14/20] chore: revert notificationEndpoints to be name unique --- cmd/influxd/launcher/pkger_test.go | 1 + pkger/clone_resource.go | 49 +++++++++++++++------------- pkger/parser.go | 2 +- pkger/parser_test.go | 52 ++++++++++++++---------------- pkger/service_test.go | 18 +++++++---- 5 files changed, 64 insertions(+), 58 deletions(-) diff --git a/cmd/influxd/launcher/pkger_test.go b/cmd/influxd/launcher/pkger_test.go index 38af051c62..59449370b5 100644 --- a/cmd/influxd/launcher/pkger_test.go +++ b/cmd/influxd/launcher/pkger_test.go @@ -1970,6 +1970,7 @@ func TestLauncher_Pkger(t *testing.T) { testStackApplyFn := func(t *testing.T) (pkger.Summary, pkger.Stack, func()) { t.Helper() + // asdfasfd stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer func() { if t.Failed() { diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index 47e9ede4d4..d6a24afc15 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -301,7 +301,7 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT return err } - mapResource(l.OrgID, l.ID, KindLabel, LabelToObject(r.Name, *l)) + mapResource(l.OrgID, uniqByNameResID, KindLabel, LabelToObject(r.Name, *l)) case len(r.Name) > 0: labels, err := ex.labelSVC.FindLabels(ctx, influxdb.LabelFilter{Name: r.Name}) if err != nil { @@ -309,40 +309,43 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT } for _, l := range labels { - mapResource(l.OrgID, l.ID, KindLabel, LabelToObject(r.Name, *l)) + mapResource(l.OrgID, uniqByNameResID, KindLabel, LabelToObject(r.Name, *l)) } } case r.Kind.is(KindNotificationEndpoint), r.Kind.is(KindNotificationEndpointHTTP), r.Kind.is(KindNotificationEndpointPagerDuty), r.Kind.is(KindNotificationEndpointSlack): - var ( - hasID bool - filter = influxdb.NotificationEndpointFilter{} - ) - if r.ID != influxdb.ID(0) { - hasID = true - filter.ID = &r.ID - } + var endpoints []influxdb.NotificationEndpoint - endpoints, _, err := ex.endpointSVC.FindNotificationEndpoints(ctx, filter) - if err != nil { - return err - } - - var mapped bool - for _, e := range endpoints { - if (!hasID && len(r.Name) > 0 && e.GetName() != r.Name) || (hasID && e.GetID() != r.ID) { - continue + switch { + case r.ID != influxdb.ID(0): + ndpoint, err := ex.endpointSVC.FindNotificationEndpointByID(ctx, r.ID) + if err != nil { + return err + } + endpoints = append(endpoints, ndpoint) + case len(r.Name) != 0: + allEndpoints, _, err := ex.endpointSVC.FindNotificationEndpoints(ctx, influxdb.NotificationEndpointFilter{}) + if err != nil { + return err } - mapResource(e.GetOrgID(), e.GetID(), KindNotificationEndpoint, NotificationEndpointToObject(r.Name, e)) - mapped = true + for _, ndpoint := range allEndpoints { + if ndpoint.GetName() != r.Name || ndpoint == nil { + continue + } + endpoints = append(endpoints, ndpoint) + } } - if !mapped { + if len(endpoints) == 0 { return errors.New("no notification endpoints found") } + + for _, e := range endpoints { + mapResource(e.GetOrgID(), uniqByNameResID, KindNotificationEndpoint, NotificationEndpointToObject(r.Name, e)) + } case r.Kind.is(KindNotificationRule): var rules []influxdb.NotificationRule @@ -491,7 +494,7 @@ func (ex *resourceExporter) resourceCloneAssociationsGen(ctx context.Context, la return nil, shouldSkip, nil } - if len(r.Name) > 0 { + if len(r.Name) > 0 && r.ID == influxdb.ID(0) { return nil, false, nil } diff --git a/pkger/parser.go b/pkger/parser.go index 198c25b460..b01aeca1ee 100644 --- a/pkger/parser.go +++ b/pkger/parser.go @@ -975,7 +975,7 @@ func (p *Template) graphDashboards() *parseErr { func (p *Template) graphNotificationEndpoints() *parseErr { p.mNotificationEndpoints = make(map[string]*notificationEndpoint) - tracker := p.trackNames(false) + tracker := p.trackNames(true) notificationKinds := []struct { kind Kind diff --git a/pkger/parser_test.go b/pkger/parser_test.go index 5d67f58184..593b69d8fc 100644 --- a/pkger/parser_test.go +++ b/pkger/parser_test.go @@ -2987,33 +2987,31 @@ spec: `, }, }, - /* notification endpoints are not name unique - { - kind: KindNotificationEndpointSlack, - resErr: testTemplateResourceError{ - name: "duplicate meta name and spec name", - validationErrs: 1, - valFields: []string{fieldSpec, fieldName}, - templateStr: `apiVersion: influxdata.com/v2alpha1 - kind: NotificationEndpointSlack - metadata: - name: slack - spec: - description: slack desc - url: https://hooks.slack.com/services/bip/piddy/boppidy - --- - apiVersion: influxdata.com/v2alpha1 - kind: NotificationEndpointSlack - metadata: - name: slack-notification-endpoint - spec: - name: slack - description: slack desc - url: https://hooks.slack.com/services/bip/piddy/boppidy - `, - }, - }, - */ + { + kind: KindNotificationEndpointSlack, + resErr: testTemplateResourceError{ + name: "duplicate meta name and spec name", + validationErrs: 1, + valFields: []string{fieldSpec, fieldName}, + templateStr: `apiVersion: influxdata.com/v2alpha1 +kind: NotificationEndpointSlack +metadata: + name: slack +spec: + description: slack desc + url: https://hooks.slack.com/services/bip/piddy/boppidy +--- +apiVersion: influxdata.com/v2alpha1 +kind: NotificationEndpointSlack +metadata: + name: slack-notification-endpoint +spec: + name: slack + description: slack desc + url: https://hooks.slack.com/services/bip/piddy/boppidy +`, + }, + }, } for _, tt := range tests { diff --git a/pkger/service_test.go b/pkger/service_test.go index cd68257e79..1598494724 100644 --- a/pkger/service_test.go +++ b/pkger/service_test.go @@ -3305,7 +3305,7 @@ func TestService(t *testing.T) { &endpoint.Slack{ Base: endpoint.Base{ ID: newTestIDPtr(3), - Name: "pd-endpoint", + Name: "slack endpoint", Description: "desc slack", Status: influxdb.TaskStatusInactive, }, @@ -3326,11 +3326,6 @@ func TestService(t *testing.T) { findName: "pd endpoint", expected: []influxdb.NotificationEndpoint{knownEndpoints[0]}, }, - { - name: "find multiple notification endpoints with shared name", - findName: "pd-endpoint", - expected: []influxdb.NotificationEndpoint{knownEndpoints[1], knownEndpoints[2]}, - }, { name: "find no notification endpoints", findName: "fakeEndpoint", @@ -3352,6 +3347,15 @@ func TestService(t *testing.T) { for _, tt := range tests { fn := func(t *testing.T) { endpointSVC := mock.NewNotificationEndpointService() + endpointSVC.FindNotificationEndpointByIDF = func(ctx context.Context, id influxdb.ID) (influxdb.NotificationEndpoint, error) { + for i := range knownEndpoints { + if knownEndpoints[i].GetID() == id { + return knownEndpoints[i], nil + } + } + + return nil, errors.New("uh ohhh, wrong endpoint id here: " + id.String()) + } endpointSVC.FindNotificationEndpointsF = func(ctx context.Context, filter influxdb.NotificationEndpointFilter, _ ...influxdb.FindOptions) ([]influxdb.NotificationEndpoint, int, error) { if filter.ID != nil { for i := range knownEndpoints { @@ -4805,7 +4809,7 @@ func TestService(t *testing.T) { assert.Equal(t, "label", labels[0].Name) endpoints := summary.NotificationEndpoints - require.Len(t, endpoints, 2) // todo: somehow find endpoint by id gets combined with find endpoints unless `uniqByNameResID` is used in clone_resource + require.Len(t, endpoints, 1) assert.Equal(t, "http", endpoints[0].NotificationEndpoint.GetName()) rules := summary.NotificationRules From 6dc785e8947c43e6e21323ecab57e6e901c9ef0f Mon Sep 17 00:00:00 2001 From: greg linton Date: Tue, 15 Sep 2020 09:33:44 -0600 Subject: [PATCH 15/20] chore: address feedback --- cmd/influxd/launcher/pkger_test.go | 1 - pkger/clone_resource.go | 14 +++++++------- pkger/service.go | 4 +--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/cmd/influxd/launcher/pkger_test.go b/cmd/influxd/launcher/pkger_test.go index 4d7cea6651..8a09788bd8 100644 --- a/cmd/influxd/launcher/pkger_test.go +++ b/cmd/influxd/launcher/pkger_test.go @@ -1970,7 +1970,6 @@ func TestLauncher_Pkger(t *testing.T) { testStackApplyFn := func(t *testing.T) (pkger.Summary, pkger.Stack, func()) { t.Helper() - // asdfasfd stack, cleanup := newStackFn(t, pkger.StackCreate{}) defer func() { if t.Failed() { diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index a467657757..7830a08e7d 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -267,13 +267,13 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT filter.IDs = []*influxdb.ID{&r.ID} } - dashs, _, err := ex.dashSVC.FindDashboards(ctx, filter, influxdb.DefaultDashboardFindOptions) + dashes, _, err := ex.dashSVC.FindDashboards(ctx, filter, influxdb.DefaultDashboardFindOptions) if err != nil { return err } var mapped bool - for _, dash := range dashs { + for _, dash := range dashes { if (!hasID && len(r.Name) > 0 && dash.Name != r.Name) || (hasID && dash.ID != r.ID) { continue } @@ -320,22 +320,22 @@ func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceT switch { case r.ID != influxdb.ID(0): - ndpoint, err := ex.endpointSVC.FindNotificationEndpointByID(ctx, r.ID) + notifEndpoint, err := ex.endpointSVC.FindNotificationEndpointByID(ctx, r.ID) if err != nil { return err } - endpoints = append(endpoints, ndpoint) + endpoints = append(endpoints, notifEndpoint) case len(r.Name) != 0: allEndpoints, _, err := ex.endpointSVC.FindNotificationEndpoints(ctx, influxdb.NotificationEndpointFilter{}) if err != nil { return err } - for _, ndpoint := range allEndpoints { - if ndpoint.GetName() != r.Name || ndpoint == nil { + for _, notifEndpoint := range allEndpoints { + if notifEndpoint.GetName() != r.Name || notifEndpoint == nil { continue } - endpoints = append(endpoints, ndpoint) + endpoints = append(endpoints, notifEndpoint) } } diff --git a/pkger/service.go b/pkger/service.go index fbafa4c046..a4b874a70c 100644 --- a/pkger/service.go +++ b/pkger/service.go @@ -569,7 +569,6 @@ func ExportWithExistingResources(resources ...ResourceToClone) ExportOptFn { return func(opt *ExportOpt) error { for _, r := range resources { if err := r.OK(); err != nil { - // todo: log and continue else append r to opt.Resources?? return err } } @@ -745,8 +744,7 @@ func (s *Service) cloneOrgLabels(ctx context.Context, orgID influxdb.ID) ([]Reso OrgID: &orgID, } - // todo: if this was ever called, it would error, 100 is the limit. - labels, err := s.labelSVC.FindLabels(ctx, filter, influxdb.FindOptions{Limit: 10000}) + labels, err := s.labelSVC.FindLabels(ctx, filter, influxdb.FindOptions{Limit: 100}) if err != nil { return nil, ierrors.Wrap(err, "finding labels") } From d32385bac9cc706abdce6d519c7cbc970f7e5937 Mon Sep 17 00:00:00 2001 From: greg linton Date: Mon, 21 Sep 2020 14:24:08 -0600 Subject: [PATCH 16/20] chore: define template export by name in swagger --- http/swagger.yml | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/http/swagger.yml b/http/swagger.yml index dcafa9329e..4bc109487d 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -4602,7 +4602,9 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/TemplateExport" + oneOf: + $ref: "#/components/schemas/TemplateExportByID" + $ref: "#/components/schemas/TemplateExportByName" responses: "200": description: InfluxDB template created @@ -7475,7 +7477,7 @@ components: - Task - Telegraf - Variable - TemplateExport: + TemplateExportByID: type: object properties: stackID: @@ -7507,7 +7509,39 @@ components: $ref: "#/components/schemas/TemplateKind" name: type: string + description: "if defined with id, name is used for resource exported by id. if defined independently, resources strictly matching name are exported" required: [id, kind] + TemplateExportByName: + type: object + properties: + stackID: + type: string + orgIDs: + type: array + items: + type: object + properties: + orgID: + type: string + resourceFilters: + type: object + properties: + byLabel: + type: array + items: + type: string + byResourceKind: + type: array + items: + $ref: "#/components/schemas/TemplateKind" + resources: + type: object + properties: + kind: + $ref: "#/components/schemas/TemplateKind" + name: + type: string + required: [name, kind] Template: type: array items: From bd834ac74de27d89e50088579f389e01cf0ce223 Mon Sep 17 00:00:00 2001 From: greg linton Date: Tue, 22 Sep 2020 09:50:25 -0600 Subject: [PATCH 17/20] chore: proper swagger syntax --- http/swagger.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http/swagger.yml b/http/swagger.yml index 4d8955cd98..4212ce71e4 100644 --- a/http/swagger.yml +++ b/http/swagger.yml @@ -4603,8 +4603,8 @@ paths: application/json: schema: oneOf: - $ref: "#/components/schemas/TemplateExportByID" - $ref: "#/components/schemas/TemplateExportByName" + - $ref: "#/components/schemas/TemplateExportByID" + - $ref: "#/components/schemas/TemplateExportByName" responses: "200": description: InfluxDB template created From 214625e7c0dc1dc36d0debd5e88959b14eef6a36 Mon Sep 17 00:00:00 2001 From: greg linton Date: Tue, 22 Sep 2020 10:22:55 -0600 Subject: [PATCH 18/20] chore: remove crufty comments --- pkger/service_logging.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkger/service_logging.go b/pkger/service_logging.go index 6bbe9aee82..b34cd7838c 100644 --- a/pkger/service_logging.go +++ b/pkger/service_logging.go @@ -148,7 +148,6 @@ func (s *loggingMW) Export(ctx context.Context, opts ...ExportOptFn) (template * s.logger.Error("failed to export template", zap.Error(err), dur) return } - // todo: should these be Debug logs? s.logger.Info("exported template", append(s.summaryLogFields(template.Summary()), dur)...) }(time.Now()) return s.next.Export(ctx, opts...) @@ -177,7 +176,6 @@ func (s *loggingMW) DryRun(ctx context.Context, orgID, userID influxdb.ID, opts fields = append(fields, zap.Stringer("stackID", opt.StackID)) } fields = append(fields, dur) - // todo: should these be Debug logs? s.logger.Info("template dry run successful", fields...) }(time.Now()) return s.next.DryRun(ctx, orgID, userID, opts...) @@ -203,7 +201,6 @@ func (s *loggingMW) Apply(ctx context.Context, orgID, userID influxdb.ID, opts . fields = append(fields, zap.Stringer("stackID", opt.StackID)) } fields = append(fields, dur) - // todo: should these be Debug logs? s.logger.Info("template apply successful", fields...) }(time.Now()) return s.next.Apply(ctx, orgID, userID, opts...) From 724de9d0d1529984c4f09768809370b6c62019c7 Mon Sep 17 00:00:00 2001 From: greg linton Date: Tue, 22 Sep 2020 11:49:26 -0600 Subject: [PATCH 19/20] chore: fix typo in cli flag --- cmd/influx/template.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/influx/template.go b/cmd/influx/template.go index c098cf271c..97f0d6d413 100644 --- a/cmd/influx/template.go +++ b/cmd/influx/template.go @@ -367,7 +367,7 @@ func (b *cmdTemplateBuilder) cmdExport() *cobra.Command { cmd.Flags().StringVar(&b.exportOpts.labelNames, "label-names", "", "List of label names comma separated") cmd.Flags().StringVar(&b.exportOpts.ruleNames, "rule-names", "", "List of notification rule names comma separated") cmd.Flags().StringVar(&b.exportOpts.taskNames, "task-names", "", "List of task names comma separated") - cmd.Flags().StringVar(&b.exportOpts.telegrafNames, "telegra-namef-configs", "", "List of telegraf config names comma separated") + cmd.Flags().StringVar(&b.exportOpts.telegrafNames, "telegraf-config-names", "", "List of telegraf config names comma separated") cmd.Flags().StringVar(&b.exportOpts.variableNames, "variable-names", "", "List of variable names comma separated") return cmd From 9c9863051f36636d143c84ca7d643e97f643d742 Mon Sep 17 00:00:00 2001 From: greg linton Date: Tue, 22 Sep 2020 11:51:38 -0600 Subject: [PATCH 20/20] chore: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 736d5dd54a..24a0f904ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ need to update any InfluxDB CLI config profiles with the new port number. 1. [19433](https://github.com/influxdata/influxdb/pull/19433): Add option to dump raw query results in CLI 1. [19506](https://github.com/influxdata/influxdb/pull/19506): Add TSM 1.x storage options as flags 1. [19508](https://github.com/influxdata/influxdb/pull/19508): Add subset of InfluxQL coordinator options as flags +1. [19457](https://github.com/influxdata/influxdb/pull/19457): Add ability to export resources by name via the CLI ### Bug Fixes