influxdb/pkger/clone_resource.go

1514 lines
43 KiB
Go

package pkger
import (
"context"
"errors"
"fmt"
"regexp"
"sort"
"strings"
"github.com/influxdata/influxdb/v2"
ierrors "github.com/influxdata/influxdb/v2/kit/errors"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/notification"
icheck "github.com/influxdata/influxdb/v2/notification/check"
"github.com/influxdata/influxdb/v2/notification/endpoint"
"github.com/influxdata/influxdb/v2/notification/rule"
"github.com/influxdata/influxdb/v2/pkger/internal/wordplay"
"github.com/influxdata/influxdb/v2/snowflake"
"github.com/influxdata/influxdb/v2/task/taskmodel"
)
var idGenerator = snowflake.NewDefaultIDGenerator()
// NameGenerator generates a random name. Includes an optional fuzz option to
// further randomize the name.
type NameGenerator func() string
// ResourceToClone is a resource that will be cloned.
type ResourceToClone struct {
Kind Kind `json:"kind"`
ID platform.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
// to accommodate at this time.
MetaName string `json:"-"`
}
// OK validates a resource clone is viable.
func (r ResourceToClone) OK() error {
if err := r.Kind.OK(); err != nil {
return err
}
if r.ID == platform.ID(0) && len(r.Name) == 0 {
return errors.New("must provide an ID or name")
}
return nil
}
var kindPriorities = map[Kind]int{
KindLabel: 1,
KindBucket: 2,
KindCheck: 3,
KindCheckDeadman: 4,
KindCheckThreshold: 5,
KindNotificationEndpoint: 6,
KindNotificationEndpointHTTP: 7,
KindNotificationEndpointPagerDuty: 8,
KindNotificationEndpointSlack: 9,
KindNotificationRule: 10,
KindTask: 11,
KindVariable: 12,
KindDashboard: 13,
KindTelegraf: 14,
}
type exportKey struct {
orgID platform.ID
id platform.ID
name string
kind Kind
}
func newExportKey(orgID, id platform.ID, k Kind, name string) exportKey {
return exportKey{
orgID: orgID,
id: id,
name: name,
kind: k,
}
}
type resourceExporter struct {
nameGen NameGenerator
bucketSVC influxdb.BucketService
checkSVC influxdb.CheckService
dashSVC influxdb.DashboardService
labelSVC influxdb.LabelService
endpointSVC influxdb.NotificationEndpointService
ruleSVC influxdb.NotificationRuleStore
taskSVC taskmodel.TaskService
teleSVC influxdb.TelegrafConfigStore
varSVC influxdb.VariableService
mObjects map[exportKey]Object
mPkgNames map[string]bool
mStackResources map[exportKey]StackResource
}
func newResourceExporter(svc *Service) *resourceExporter {
return &resourceExporter{
nameGen: wordplay.GetRandomName,
bucketSVC: svc.bucketSVC,
checkSVC: svc.checkSVC,
dashSVC: svc.dashSVC,
labelSVC: svc.labelSVC,
endpointSVC: svc.endpointSVC,
ruleSVC: svc.ruleSVC,
taskSVC: svc.taskSVC,
teleSVC: svc.teleSVC,
varSVC: svc.varSVC,
mObjects: make(map[exportKey]Object),
mPkgNames: make(map[string]bool),
mStackResources: make(map[exportKey]StackResource),
}
}
func (ex *resourceExporter) Export(ctx context.Context, resourcesToClone []ResourceToClone, labelNames ...string) error {
mLabelIDsToMetaName := make(map[platform.ID]string)
for _, r := range resourcesToClone {
if !r.Kind.is(KindLabel) || r.MetaName == "" {
continue
}
mLabelIDsToMetaName[r.ID] = r.MetaName
}
cloneAssFn, err := ex.resourceCloneAssociationsGen(ctx, mLabelIDsToMetaName, labelNames...)
if err != nil {
return err
}
resourcesToClone = uniqResourcesToClone(resourcesToClone)
// sorting this in priority order guarantees that the dependencies/associations
// for a resource are handled prior to the resource being processed.
// 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 {
iName, jName := resourcesToClone[i].Name, resourcesToClone[j].Name
iKind, jKind := resourcesToClone[i].Kind, resourcesToClone[j].Kind
if iKind.is(jKind) {
return iName < jName
}
return kindPriorities[iKind] < kindPriorities[jKind]
})
for _, r := range resourcesToClone {
err := ex.resourceCloneToKind(ctx, r, cloneAssFn)
if err != nil {
return internalErr(fmt.Errorf("failed to clone resource: resource_id=%s resource_kind=%s err=%q", r.ID, r.Kind, err))
}
}
return nil
}
func (ex *resourceExporter) Objects() []Object {
objects := make([]Object, 0, len(ex.mObjects))
for _, obj := range ex.mObjects {
objects = append(objects, obj)
}
return sortObjects(objects)
}
func (ex *resourceExporter) StackResources() []StackResource {
resources := make([]StackResource, 0, len(ex.mStackResources))
for _, res := range ex.mStackResources {
resources = append(resources, res)
}
return resources
}
// 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 = platform.ID(0)
type cloneAssociationsFn func(context.Context, ResourceToClone) (associations []ObjectAssociation, skipResource bool, err error)
func (ex *resourceExporter) resourceCloneToKind(ctx context.Context, r ResourceToClone, cFn cloneAssociationsFn) (e error) {
defer func() {
if e != nil {
e = ierrors.Wrap(e, "cloning resource")
}
}()
ass, skipResource, err := cFn(ctx, r)
if err != nil {
return err
}
if skipResource {
return nil
}
mapResource := func(orgID, uniqResID platform.ID, k Kind, object Object) {
// overwrite the default metadata.name field with export generated one here
metaName := r.MetaName
if r.MetaName == "" {
metaName = ex.uniqName()
}
stackResource := StackResource{
APIVersion: APIVersion,
ID: r.ID,
MetaName: metaName,
Kind: r.Kind,
}
for _, a := range ass {
stackResource.Associations = append(stackResource.Associations, StackResourceAssociation(a))
}
object.SetMetadataName(metaName)
object.AddAssociations(ass...)
key := newExportKey(orgID, uniqResID, k, object.Spec.stringShort(fieldName))
ex.mObjects[key] = object
ex.mStackResources[key] = stackResource
}
switch {
case r.Kind.is(KindBucket):
filter := influxdb.BucketFilter{}
if r.ID != platform.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
}
if n < 1 {
return errors.New("no buckets found")
}
for _, bkt := range bkts {
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{}
if r.ID != platform.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
}
if n < 1 {
return errors.New("no checks found")
}
for _, ch := range chs {
mapResource(ch.GetOrgID(), ch.GetID(), KindCheck, CheckToObject(r.Name, ch))
}
case r.Kind.is(KindDashboard):
var (
hasID bool
filter = influxdb.DashboardFilter{}
)
if r.ID != platform.ID(0) {
hasID = true
filter.IDs = []*platform.ID{&r.ID}
}
dashes, _, err := ex.dashSVC.FindDashboards(ctx, filter, influxdb.DefaultDashboardFindOptions)
if err != nil {
return err
}
var mapped bool
for _, dash := range dashes {
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 {
continue
}
cell.View = v
}
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 {
case r.ID != platform.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))
}
}
case r.Kind.is(KindNotificationEndpoint),
r.Kind.is(KindNotificationEndpointHTTP),
r.Kind.is(KindNotificationEndpointPagerDuty),
r.Kind.is(KindNotificationEndpointSlack):
var endpoints []influxdb.NotificationEndpoint
switch {
case r.ID != platform.ID(0):
notifEndpoint, err := ex.endpointSVC.FindNotificationEndpointByID(ctx, r.ID)
if err != nil {
return err
}
endpoints = append(endpoints, notifEndpoint)
case len(r.Name) != 0:
allEndpoints, _, err := ex.endpointSVC.FindNotificationEndpoints(ctx, influxdb.NotificationEndpointFilter{})
if err != nil {
return err
}
for _, notifEndpoint := range allEndpoints {
if notifEndpoint.GetName() != r.Name || notifEndpoint == nil {
continue
}
endpoints = append(endpoints, notifEndpoint)
}
}
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
switch {
case r.ID != platform.ID(0):
r, err := ex.ruleSVC.FindNotificationRuleByID(ctx, r.ID)
if err != nil {
return err
}
rules = append(rules, r)
case len(r.Name) != 0:
allRules, _, err := ex.ruleSVC.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{})
if err != nil {
return err
}
for _, rule := range allRules {
if rule.GetName() != r.Name {
continue
}
rules = append(rules, rule)
}
}
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 {
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):
switch {
case r.ID != platform.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, taskmodel.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, t.ID, KindTask, TaskToObject(r.Name, *t))
}
}
case r.Kind.is(KindTelegraf):
switch {
case r.ID != platform.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, _, err := ex.teleSVC.FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{})
if err != nil {
return err
}
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 {
case r.ID != platform.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
}
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:
return errors.New("unsupported kind provided: " + string(r.Kind))
}
return nil
}
func (ex *resourceExporter) resourceCloneAssociationsGen(ctx context.Context, labelIDsToMetaName map[platform.ID]string, labelNames ...string) (cloneAssociationsFn, error) {
mLabelNames := make(map[string]bool)
for _, labelName := range labelNames {
mLabelNames[labelName] = true
}
mLabelIDs, err := getLabelIDMap(ctx, ex.labelSVC, labelNames)
if err != nil {
return nil, err
}
cloneFn := func(ctx context.Context, r ResourceToClone) ([]ObjectAssociation, bool, error) {
if r.Kind.is(KindUnknown) {
return nil, true, nil
}
if r.Kind.is(KindLabel) {
// check here verifies the label maps to an id of a valid label name
shouldSkip := len(mLabelIDs) > 0 && !mLabelIDs[r.ID]
return nil, shouldSkip, nil
}
if len(r.Name) > 0 && r.ID == platform.ID(0) {
return nil, false, nil
}
labels, err := ex.labelSVC.FindResourceLabels(ctx, influxdb.LabelMappingFilter{
ResourceID: r.ID,
ResourceType: r.Kind.ResourceType(),
})
if err != nil {
return nil, false, ierrors.Wrap(err, "finding resource labels")
}
if len(mLabelNames) > 0 {
shouldSkip := true
for _, l := range labels {
if _, ok := mLabelNames[l.Name]; ok {
shouldSkip = false
break
}
}
if shouldSkip {
return nil, true, nil
}
}
var associations []ObjectAssociation
for _, l := range labels {
if len(mLabelNames) > 0 {
if _, ok := mLabelNames[l.Name]; !ok {
continue
}
}
labelObject := LabelToObject("", *l)
metaName := labelIDsToMetaName[l.ID]
if metaName == "" {
metaName = ex.uniqName()
}
labelObject.Metadata[fieldName] = metaName
k := newExportKey(l.OrgID, uniqByNameResID, KindLabel, l.Name)
existing, ok := ex.mObjects[k]
if ok {
associations = append(associations, ObjectAssociation{
Kind: KindLabel,
MetaName: existing.Name(),
})
continue
}
associations = append(associations, ObjectAssociation{
Kind: KindLabel,
MetaName: labelObject.Name(),
})
ex.mObjects[k] = labelObject
}
sort.Slice(associations, func(i, j int) bool {
return associations[i].MetaName < associations[j].MetaName
})
return associations, false, nil
}
return cloneFn, nil
}
func (ex *resourceExporter) uniqName() string {
return uniqMetaName(ex.nameGen, idGenerator, ex.mPkgNames)
}
func uniqMetaName(nameGen NameGenerator, idGen platform.IDGenerator, existingNames map[string]bool) string {
uuid := strings.ToLower(idGen.ID().String())
name := uuid
for i := 1; i < 250; i++ {
name = fmt.Sprintf("%s-%s", nameGen(), uuid[10:])
if !existingNames[name] {
break
}
}
return name
}
func uniqResourcesToClone(resources []ResourceToClone) []ResourceToClone {
type key struct {
kind Kind
id platform.ID
}
m := make(map[key]ResourceToClone)
for i := range resources {
r := resources[i]
rKey := key{kind: r.Kind, id: r.ID}
kr, ok := m[rKey]
switch {
case ok && kr.Name == r.Name && kr.MetaName == r.MetaName:
case ok && kr.MetaName != "" && r.MetaName == "":
case ok && kr.MetaName == "" && kr.Name != "" && r.Name == "":
default:
m[rKey] = r
}
}
out := make([]ResourceToClone, 0, len(resources))
for _, r := range m {
out = append(out, r)
}
return out
}
// BucketToObject converts a influxdb.Bucket into an Object.
func BucketToObject(name string, bkt influxdb.Bucket) Object {
if name == "" {
name = bkt.Name
}
o := newObject(KindBucket, name)
assignNonZeroStrings(o.Spec, map[string]string{fieldDescription: bkt.Description})
if bkt.RetentionPeriod != 0 {
o.Spec[fieldBucketRetentionRules] = retentionRules{newRetentionRule(bkt.RetentionPeriod)}
}
return o
}
func CheckToObject(name string, ch influxdb.Check) Object {
if name == "" {
name = ch.GetName()
}
o := newObject(KindCheck, name)
assignNonZeroStrings(o.Spec, map[string]string{
fieldDescription: ch.GetDescription(),
fieldStatus: taskmodel.TaskStatusActive,
})
assignBase := func(base icheck.Base) {
o.Spec[fieldQuery] = strings.TrimSpace(base.Query.Text)
o.Spec[fieldCheckStatusMessageTemplate] = base.StatusMessageTemplate
assignNonZeroFluxDurs(o.Spec, map[string]*notification.Duration{
fieldEvery: base.Every,
fieldOffset: base.Offset,
})
var tags []Resource
for _, t := range base.Tags {
if t.Valid() != nil {
continue
}
tags = append(tags, Resource{
fieldKey: t.Key,
fieldValue: t.Value,
})
}
if len(tags) > 0 {
o.Spec[fieldCheckTags] = tags
}
}
switch cT := ch.(type) {
case *icheck.Deadman:
o.Kind = KindCheckDeadman
assignBase(cT.Base)
assignNonZeroFluxDurs(o.Spec, map[string]*notification.Duration{
fieldCheckTimeSince: cT.TimeSince,
fieldCheckStaleTime: cT.StaleTime,
})
o.Spec[fieldLevel] = cT.Level.String()
assignNonZeroBools(o.Spec, map[string]bool{fieldCheckReportZero: cT.ReportZero})
case *icheck.Threshold:
o.Kind = KindCheckThreshold
assignBase(cT.Base)
var thresholds []Resource
for _, th := range cT.Thresholds {
thresholds = append(thresholds, convertThreshold(th))
}
o.Spec[fieldCheckThresholds] = thresholds
}
return o
}
func convertThreshold(th icheck.ThresholdConfig) Resource {
r := Resource{fieldLevel: th.GetLevel().String()}
assignLesser := func(threshType thresholdType, allValues bool, val float64) {
r[fieldType] = string(threshType)
assignNonZeroBools(r, map[string]bool{fieldCheckAllValues: allValues})
r[fieldValue] = val
}
switch realType := th.(type) {
case icheck.Lesser:
assignLesser(thresholdTypeLesser, realType.AllValues, realType.Value)
case *icheck.Lesser:
assignLesser(thresholdTypeLesser, realType.AllValues, realType.Value)
case icheck.Greater:
assignLesser(thresholdTypeGreater, realType.AllValues, realType.Value)
case *icheck.Greater:
assignLesser(thresholdTypeGreater, realType.AllValues, realType.Value)
case icheck.Range:
assignRangeThreshold(r, realType)
case *icheck.Range:
assignRangeThreshold(r, *realType)
}
return r
}
func assignRangeThreshold(r Resource, rangeThreshold icheck.Range) {
thType := thresholdTypeOutsideRange
if rangeThreshold.Within {
thType = thresholdTypeInsideRange
}
r[fieldType] = string(thType)
assignNonZeroBools(r, map[string]bool{fieldCheckAllValues: rangeThreshold.AllValues})
r[fieldMax] = rangeThreshold.Max
r[fieldMin] = rangeThreshold.Min
}
func convertCellView(cell influxdb.Cell) chart {
var name string
if cell.View != nil {
name = cell.View.Name
}
ch := chart{
Name: name,
Height: int(cell.H),
Width: int(cell.W),
XPos: int(cell.X),
YPos: int(cell.Y),
}
setCommon := func(k chartKind, iColors []influxdb.ViewColor, dec influxdb.DecimalPlaces, iQueries []influxdb.DashboardQuery) {
ch.Kind = k
ch.Colors = convertColors(iColors)
ch.DecimalPlaces = int(dec.Digits)
ch.EnforceDecimals = dec.IsEnforced
ch.Queries = convertQueries(iQueries)
}
setNoteFixes := func(note string, noteOnEmpty bool, prefix, suffix string) {
ch.Note = note
ch.NoteOnEmpty = noteOnEmpty
ch.Prefix = prefix
ch.Suffix = suffix
}
setStaticLegend := func(sl influxdb.StaticLegend) {
ch.StaticLegend.ColorizeRows = sl.ColorizeRows
ch.StaticLegend.HeightRatio = sl.HeightRatio
ch.StaticLegend.Show = sl.Show
ch.StaticLegend.Opacity = sl.Opacity
ch.StaticLegend.OrientationThreshold = sl.OrientationThreshold
ch.StaticLegend.ValueAxis = sl.ValueAxis
ch.StaticLegend.WidthRatio = sl.WidthRatio
}
props := cell.View.Properties
switch p := props.(type) {
case influxdb.GaugeViewProperties:
setCommon(chartKindGauge, p.ViewColors, p.DecimalPlaces, p.Queries)
setNoteFixes(p.Note, p.ShowNoteWhenEmpty, p.Prefix, p.Suffix)
ch.TickPrefix = p.TickPrefix
ch.TickSuffix = p.TickSuffix
case influxdb.GeoViewProperties:
ch.Kind = chartKindGeo
ch.Queries = convertQueries(p.Queries)
ch.Zoom = p.Zoom
ch.Center = center{Lat: p.Center.Lat, Lon: p.Center.Lon}
ch.MapStyle = p.MapStyle
ch.AllowPanAndZoom = p.AllowPanAndZoom
ch.DetectCoordinateFields = p.DetectCoordinateFields
ch.Colors = convertColors(p.ViewColor)
ch.GeoLayers = convertGeoLayers(p.GeoLayers)
ch.Note = p.Note
ch.NoteOnEmpty = p.ShowNoteWhenEmpty
case influxdb.HeatmapViewProperties:
ch.Kind = chartKindHeatMap
ch.Queries = convertQueries(p.Queries)
ch.Colors = stringsToColors(p.ViewColors)
ch.XCol = p.XColumn
ch.GenerateXAxisTicks = p.GenerateXAxisTicks
ch.XTotalTicks = p.XTotalTicks
ch.XTickStart = p.XTickStart
ch.XTickStep = p.XTickStep
ch.YCol = p.YColumn
ch.GenerateYAxisTicks = p.GenerateYAxisTicks
ch.YTotalTicks = p.YTotalTicks
ch.YTickStart = p.YTickStart
ch.YTickStep = p.YTickStep
ch.Axes = []axis{
{Label: p.XAxisLabel, Prefix: p.XPrefix, Suffix: p.XSuffix, Name: "x", Domain: p.XDomain},
{Label: p.YAxisLabel, Prefix: p.YPrefix, Suffix: p.YSuffix, Name: "y", Domain: p.YDomain},
}
ch.Note = p.Note
ch.NoteOnEmpty = p.ShowNoteWhenEmpty
ch.BinSize = int(p.BinSize)
ch.LegendColorizeRows = p.LegendColorizeRows
ch.LegendHide = p.LegendHide
ch.LegendOpacity = float64(p.LegendOpacity)
ch.LegendOrientationThreshold = int(p.LegendOrientationThreshold)
case influxdb.HistogramViewProperties:
ch.Kind = chartKindHistogram
ch.Queries = convertQueries(p.Queries)
ch.Colors = convertColors(p.ViewColors)
ch.FillColumns = p.FillColumns
ch.XCol = p.XColumn
ch.Axes = []axis{{Label: p.XAxisLabel, Name: "x", Domain: p.XDomain}}
ch.Note = p.Note
ch.NoteOnEmpty = p.ShowNoteWhenEmpty
ch.BinCount = p.BinCount
ch.Position = p.Position
ch.LegendColorizeRows = p.LegendColorizeRows
ch.LegendHide = p.LegendHide
ch.LegendOpacity = float64(p.LegendOpacity)
ch.LegendOrientationThreshold = int(p.LegendOrientationThreshold)
case influxdb.MarkdownViewProperties:
ch.Kind = chartKindMarkdown
ch.Note = p.Note
case influxdb.LinePlusSingleStatProperties:
setCommon(chartKindSingleStatPlusLine, p.ViewColors, p.DecimalPlaces, p.Queries)
setNoteFixes(p.Note, p.ShowNoteWhenEmpty, p.Prefix, p.Suffix)
ch.StaticLegend = StaticLegend{}
setStaticLegend(p.StaticLegend)
ch.Axes = convertAxes(p.Axes)
ch.Shade = p.ShadeBelow
ch.HoverDimension = p.HoverDimension
ch.XCol = p.XColumn
ch.GenerateXAxisTicks = p.GenerateXAxisTicks
ch.XTotalTicks = p.XTotalTicks
ch.XTickStart = p.XTickStart
ch.XTickStep = p.XTickStep
ch.YCol = p.YColumn
ch.GenerateYAxisTicks = p.GenerateYAxisTicks
ch.YTotalTicks = p.YTotalTicks
ch.YTickStart = p.YTickStart
ch.YTickStep = p.YTickStep
ch.Position = p.Position
ch.LegendColorizeRows = p.LegendColorizeRows
ch.LegendHide = p.LegendHide
ch.LegendOpacity = float64(p.LegendOpacity)
ch.LegendOrientationThreshold = int(p.LegendOrientationThreshold)
case influxdb.SingleStatViewProperties:
setCommon(chartKindSingleStat, p.ViewColors, p.DecimalPlaces, p.Queries)
setNoteFixes(p.Note, p.ShowNoteWhenEmpty, p.Prefix, p.Suffix)
ch.TickPrefix = p.TickPrefix
ch.TickSuffix = p.TickSuffix
case influxdb.MosaicViewProperties:
ch.Kind = chartKindMosaic
ch.Queries = convertQueries(p.Queries)
ch.Colors = stringsToColors(p.ViewColors)
ch.HoverDimension = p.HoverDimension
ch.XCol = p.XColumn
ch.GenerateXAxisTicks = p.GenerateXAxisTicks
ch.XTotalTicks = p.XTotalTicks
ch.XTickStart = p.XTickStart
ch.XTickStep = p.XTickStep
ch.YLabelColumnSeparator = p.YLabelColumnSeparator
ch.YLabelColumns = p.YLabelColumns
ch.YSeriesColumns = p.YSeriesColumns
ch.Axes = []axis{
{Label: p.XAxisLabel, Prefix: p.XPrefix, Suffix: p.XSuffix, Name: "x", Domain: p.XDomain},
{Label: p.YAxisLabel, Prefix: p.YPrefix, Suffix: p.YSuffix, Name: "y", Domain: p.YDomain},
}
ch.Note = p.Note
ch.NoteOnEmpty = p.ShowNoteWhenEmpty
ch.LegendColorizeRows = p.LegendColorizeRows
ch.LegendHide = p.LegendHide
ch.LegendOpacity = float64(p.LegendOpacity)
ch.LegendOrientationThreshold = int(p.LegendOrientationThreshold)
case influxdb.ScatterViewProperties:
ch.Kind = chartKindScatter
ch.Queries = convertQueries(p.Queries)
ch.Colors = stringsToColors(p.ViewColors)
ch.XCol = p.XColumn
ch.GenerateXAxisTicks = p.GenerateXAxisTicks
ch.XTotalTicks = p.XTotalTicks
ch.XTickStart = p.XTickStart
ch.XTickStep = p.XTickStep
ch.YCol = p.YColumn
ch.GenerateYAxisTicks = p.GenerateYAxisTicks
ch.YTotalTicks = p.YTotalTicks
ch.YTickStart = p.YTickStart
ch.YTickStep = p.YTickStep
ch.Axes = []axis{
{Label: p.XAxisLabel, Prefix: p.XPrefix, Suffix: p.XSuffix, Name: "x", Domain: p.XDomain},
{Label: p.YAxisLabel, Prefix: p.YPrefix, Suffix: p.YSuffix, Name: "y", Domain: p.YDomain},
}
ch.Note = p.Note
ch.NoteOnEmpty = p.ShowNoteWhenEmpty
ch.LegendColorizeRows = p.LegendColorizeRows
ch.LegendHide = p.LegendHide
ch.LegendOpacity = float64(p.LegendOpacity)
ch.LegendOrientationThreshold = int(p.LegendOrientationThreshold)
case influxdb.TableViewProperties:
setCommon(chartKindTable, p.ViewColors, p.DecimalPlaces, p.Queries)
setNoteFixes(p.Note, p.ShowNoteWhenEmpty, "", "")
ch.TimeFormat = p.TimeFormat
ch.TableOptions = tableOptions{
VerticalTimeAxis: p.TableOptions.VerticalTimeAxis,
SortByField: p.TableOptions.SortBy.InternalName,
Wrapping: p.TableOptions.Wrapping,
FixFirstColumn: p.TableOptions.FixFirstColumn,
}
for _, fieldOpt := range p.FieldOptions {
ch.FieldOptions = append(ch.FieldOptions, fieldOption{
FieldName: fieldOpt.InternalName,
DisplayName: fieldOpt.DisplayName,
Visible: fieldOpt.Visible,
})
}
case influxdb.BandViewProperties:
setCommon(chartKindBand, p.ViewColors, influxdb.DecimalPlaces{}, p.Queries)
setNoteFixes(p.Note, p.ShowNoteWhenEmpty, "", "")
ch.StaticLegend = StaticLegend{}
setStaticLegend(p.StaticLegend)
ch.Axes = convertAxes(p.Axes)
ch.Geom = p.Geom
ch.HoverDimension = p.HoverDimension
ch.XCol = p.XColumn
ch.GenerateXAxisTicks = p.GenerateXAxisTicks
ch.XTotalTicks = p.XTotalTicks
ch.XTickStart = p.XTickStart
ch.XTickStep = p.XTickStep
ch.YCol = p.YColumn
ch.GenerateYAxisTicks = p.GenerateYAxisTicks
ch.YTotalTicks = p.YTotalTicks
ch.YTickStart = p.YTickStart
ch.YTickStep = p.YTickStep
ch.UpperColumn = p.UpperColumn
ch.MainColumn = p.MainColumn
ch.LowerColumn = p.LowerColumn
ch.LegendColorizeRows = p.LegendColorizeRows
ch.LegendHide = p.LegendHide
ch.LegendOpacity = float64(p.LegendOpacity)
ch.LegendOrientationThreshold = int(p.LegendOrientationThreshold)
case influxdb.XYViewProperties:
setCommon(chartKindXY, p.ViewColors, influxdb.DecimalPlaces{}, p.Queries)
setNoteFixes(p.Note, p.ShowNoteWhenEmpty, "", "")
ch.StaticLegend = StaticLegend{}
setStaticLegend(p.StaticLegend)
ch.Axes = convertAxes(p.Axes)
ch.Geom = p.Geom
ch.Shade = p.ShadeBelow
ch.HoverDimension = p.HoverDimension
ch.XCol = p.XColumn
ch.GenerateXAxisTicks = p.GenerateXAxisTicks
ch.XTotalTicks = p.XTotalTicks
ch.XTickStart = p.XTickStart
ch.XTickStep = p.XTickStep
ch.YCol = p.YColumn
ch.GenerateYAxisTicks = p.GenerateYAxisTicks
ch.YTotalTicks = p.YTotalTicks
ch.YTickStart = p.YTickStart
ch.YTickStep = p.YTickStep
ch.Position = p.Position
ch.LegendColorizeRows = p.LegendColorizeRows
ch.LegendHide = p.LegendHide
ch.LegendOpacity = float64(p.LegendOpacity)
ch.LegendOrientationThreshold = int(p.LegendOrientationThreshold)
}
sort.Slice(ch.Axes, func(i, j int) bool {
return ch.Axes[i].Name < ch.Axes[j].Name
})
return ch
}
func convertChartToResource(ch chart) Resource {
r := Resource{
fieldKind: ch.Kind.title(),
fieldName: ch.Name,
fieldChartHeight: ch.Height,
fieldChartWidth: ch.Width,
}
var qq []Resource
for _, q := range ch.Queries {
qq = append(qq, Resource{
fieldQuery: q.DashboardQuery(),
})
}
if len(qq) > 0 {
r[fieldChartQueries] = qq
}
if len(ch.Colors) > 0 {
r[fieldChartColors] = ch.Colors
}
if len(ch.Axes) > 0 {
r[fieldChartAxes] = ch.Axes
}
if len(ch.YLabelColumns) > 0 {
r[fieldChartYLabelColumns] = ch.YLabelColumns
}
if len(ch.YSeriesColumns) > 0 {
r[fieldChartYSeriesColumns] = ch.YSeriesColumns
}
if len(ch.UpperColumn) > 0 {
r[fieldChartUpperColumn] = ch.UpperColumn
}
if len(ch.MainColumn) > 0 {
r[fieldChartMainColumn] = ch.MainColumn
}
if len(ch.LowerColumn) > 0 {
r[fieldChartLowerColumn] = ch.LowerColumn
}
if ch.EnforceDecimals {
r[fieldChartDecimalPlaces] = ch.DecimalPlaces
}
if len(ch.FillColumns) > 0 {
r[fieldChartFillColumns] = ch.FillColumns
}
if len(ch.GenerateXAxisTicks) > 0 {
r[fieldChartGenerateXAxisTicks] = ch.GenerateXAxisTicks
}
if len(ch.GenerateYAxisTicks) > 0 {
r[fieldChartGenerateYAxisTicks] = ch.GenerateYAxisTicks
}
if ch.StaticLegend.HeightRatio >= 0 && ch.StaticLegend.WidthRatio >= 0 {
r[fieldChartStaticLegend] = ch.StaticLegend
}
if len(ch.GeoLayers) > 0 {
geoLayers := make([]Resource, 0, len(ch.GeoLayers))
for _, l := range ch.GeoLayers {
lRes := make(Resource)
geoLayers = append(geoLayers, lRes)
assignNonZeroStrings(lRes, map[string]string{
fieldChartGeoLayerType: l.Type,
fieldChartGeoLayerRadiusField: l.RadiusField,
fieldChartGeoLayerIntensityField: l.IntensityField,
fieldChartGeoLayerColorField: l.ColorField,
})
assignNonZeroInts(lRes, map[string]int{
fieldChartGeoLayerRadius: int(l.Radius),
fieldChartGeoLayerBlur: int(l.Blur),
fieldChartGeoLayerSpeed: int(l.Speed),
fieldChartGeoLayerTrackWidth: int(l.TrackWidth),
})
assignNonZeroBools(lRes, map[string]bool{
fieldChartGeoLayerRandomColors: l.RandomColors,
fieldChartGeoLayerIsClustered: l.IsClustered,
fieldChartGeoLayerInterpolateColors: l.InterpolateColors,
})
if len(l.ViewColors) > 0 {
lRes[fieldChartGeoLayerViewColors] = l.ViewColors
}
if l.RadiusDimension != nil {
lRes[fieldChartGeoLayerRadiusDimension] = l.RadiusDimension
}
if l.ColorDimension != nil {
lRes[fieldChartGeoLayerColorDimension] = l.ColorDimension
}
if l.IntensityDimension != nil {
lRes[fieldChartGeoLayerIntensityDimension] = l.IntensityDimension
}
}
r[fieldChartGeoLayers] = geoLayers
}
if zero := new(tableOptions); ch.TableOptions != *zero {
tRes := make(Resource)
assignNonZeroBools(tRes, map[string]bool{
fieldChartTableOptionVerticalTimeAxis: ch.TableOptions.VerticalTimeAxis,
fieldChartTableOptionFixFirstColumn: ch.TableOptions.FixFirstColumn,
})
assignNonZeroStrings(tRes, map[string]string{
fieldChartTableOptionSortBy: ch.TableOptions.SortByField,
fieldChartTableOptionWrapping: ch.TableOptions.Wrapping,
})
r[fieldChartTableOptions] = tRes
}
if len(ch.FieldOptions) > 0 {
fieldOpts := make([]Resource, 0, len(ch.FieldOptions))
for _, fo := range ch.FieldOptions {
fRes := make(Resource)
assignNonZeroBools(fRes, map[string]bool{
fieldChartFieldOptionVisible: fo.Visible,
})
assignNonZeroStrings(fRes, map[string]string{
fieldChartFieldOptionDisplayName: fo.DisplayName,
fieldChartFieldOptionFieldName: fo.FieldName,
})
fieldOpts = append(fieldOpts, fRes)
}
r[fieldChartFieldOptions] = fieldOpts
}
assignNonZeroBools(r, map[string]bool{
fieldChartNoteOnEmpty: ch.NoteOnEmpty,
fieldChartShade: ch.Shade,
fieldChartLegendColorizeRows: ch.LegendColorizeRows,
fieldChartLegendHide: ch.LegendHide,
fieldChartStaticLegendColorizeRows: ch.StaticLegend.ColorizeRows,
fieldChartStaticLegendShow: ch.StaticLegend.Show,
fieldChartGeoAllowPanAndZoom: ch.AllowPanAndZoom,
fieldChartGeoDetectCoordinateFields: ch.DetectCoordinateFields,
})
assignNonZeroStrings(r, map[string]string{
fieldChartNote: ch.Note,
fieldPrefix: ch.Prefix,
fieldSuffix: ch.Suffix,
fieldChartGeom: ch.Geom,
fieldChartXCol: ch.XCol,
fieldChartYCol: ch.YCol,
fieldChartPosition: ch.Position,
fieldChartTickPrefix: ch.TickPrefix,
fieldChartTickSuffix: ch.TickSuffix,
fieldChartTimeFormat: ch.TimeFormat,
fieldChartHoverDimension: ch.HoverDimension,
fieldChartYLabelColumnSeparator: ch.YLabelColumnSeparator,
fieldChartStaticLegendValueAxis: ch.StaticLegend.ValueAxis,
fieldChartGeoMapStyle: ch.MapStyle,
})
assignNonZeroInts(r, map[string]int{
fieldChartXPos: ch.XPos,
fieldChartXTotalTicks: ch.XTotalTicks,
fieldChartYPos: ch.YPos,
fieldChartYTotalTicks: ch.YTotalTicks,
fieldChartBinCount: ch.BinCount,
fieldChartBinSize: ch.BinSize,
fieldChartLegendOrientationThreshold: ch.LegendOrientationThreshold,
fieldChartStaticLegendOrientationThreshold: ch.StaticLegend.OrientationThreshold,
})
assignNonZeroFloats(r, map[string]float64{
fieldChartLegendOpacity: ch.LegendOpacity,
fieldChartStaticLegendOpacity: ch.StaticLegend.Opacity,
fieldChartStaticLegendHeightRatio: ch.StaticLegend.HeightRatio,
fieldChartStaticLegendWidthRatio: ch.StaticLegend.WidthRatio,
fieldChartXTickStart: ch.XTickStart,
fieldChartXTickStep: ch.XTickStep,
fieldChartYTickStart: ch.YTickStart,
fieldChartYTickStep: ch.YTickStep,
fieldChartGeoCenterLon: ch.Center.Lon,
fieldChartGeoCenterLat: ch.Center.Lat,
fieldChartGeoZoom: ch.Zoom,
})
return r
}
func convertAxis(name string, a influxdb.Axis) *axis {
return &axis{
Base: a.Base,
Label: a.Label,
Name: name,
Prefix: a.Prefix,
Scale: a.Scale,
Suffix: a.Suffix,
}
}
func convertAxes(iAxes map[string]influxdb.Axis) axes {
out := make(axes, 0, len(iAxes))
for name, a := range iAxes {
out = append(out, *convertAxis(name, a))
}
return out
}
func convertColors(iColors []influxdb.ViewColor) colors {
out := make(colors, 0, len(iColors))
for _, ic := range iColors {
out = append(out, &color{
ID: ic.ID,
Name: ic.Name,
Type: ic.Type,
Hex: ic.Hex,
Value: flt64Ptr(ic.Value),
})
}
return out
}
func convertQueries(iQueries []influxdb.DashboardQuery) queries {
out := make(queries, 0, len(iQueries))
for _, iq := range iQueries {
out = append(out, query{Query: strings.TrimSpace(iq.Text)})
}
return out
}
func convertGeoLayers(iLayers []influxdb.GeoLayer) geoLayers {
out := make(geoLayers, 0, len(iLayers))
for _, ic := range iLayers {
out = append(out, &geoLayer{
Type: ic.Type,
RadiusField: ic.RadiusField,
ColorField: ic.ColorField,
IntensityField: ic.IntensityField,
ViewColors: convertColors(ic.ViewColors),
Radius: ic.Radius,
Blur: ic.Blur,
RadiusDimension: convertAxis("radius", ic.RadiusDimension),
ColorDimension: convertAxis("color", ic.ColorDimension),
IntensityDimension: convertAxis("intensity", ic.IntensityDimension),
InterpolateColors: ic.InterpolateColors,
TrackWidth: ic.TrackWidth,
Speed: ic.Speed,
RandomColors: ic.RandomColors,
IsClustered: ic.IsClustered,
})
}
return out
}
// DashboardToObject converts an influxdb.Dashboard to an Object.
func DashboardToObject(name string, dash influxdb.Dashboard) Object {
if name == "" {
name = dash.Name
}
sort.Slice(dash.Cells, func(i, j int) bool {
ic, jc := dash.Cells[i], dash.Cells[j]
if ic.X == jc.X {
return ic.Y < jc.Y
}
return ic.X < jc.X
})
charts := make([]Resource, 0, len(dash.Cells))
for _, cell := range dash.Cells {
if cell.View == nil {
continue
}
ch := convertCellView(*cell)
if !ch.Kind.ok() {
continue
}
charts = append(charts, convertChartToResource(ch))
}
o := newObject(KindDashboard, name)
assignNonZeroStrings(o.Spec, map[string]string{
fieldDescription: dash.Description,
})
o.Spec[fieldDashCharts] = charts
return o
}
// LabelToObject converts an influxdb.Label to an Object.
func LabelToObject(name string, l influxdb.Label) Object {
if name == "" {
name = l.Name
}
o := newObject(KindLabel, name)
assignNonZeroStrings(o.Spec, map[string]string{
fieldDescription: l.Properties["description"],
fieldLabelColor: l.Properties["color"],
})
return o
}
// NotificationEndpointToObject converts an notification endpoint into a pkger Object.
func NotificationEndpointToObject(name string, e influxdb.NotificationEndpoint) Object {
if name == "" {
name = e.GetName()
}
o := newObject(KindNotificationEndpoint, name)
assignNonZeroStrings(o.Spec, map[string]string{
fieldDescription: e.GetDescription(),
fieldStatus: string(e.GetStatus()),
})
switch actual := e.(type) {
case *endpoint.HTTP:
o.Kind = KindNotificationEndpointHTTP
o.Spec[fieldNotificationEndpointHTTPMethod] = actual.Method
o.Spec[fieldNotificationEndpointURL] = actual.URL
o.Spec[fieldType] = actual.AuthMethod
assignNonZeroSecrets(o.Spec, map[string]influxdb.SecretField{
fieldNotificationEndpointPassword: actual.Password,
fieldNotificationEndpointToken: actual.Token,
fieldNotificationEndpointUsername: actual.Username,
})
case *endpoint.PagerDuty:
o.Kind = KindNotificationEndpointPagerDuty
o.Spec[fieldNotificationEndpointURL] = actual.ClientURL
assignNonZeroSecrets(o.Spec, map[string]influxdb.SecretField{
fieldNotificationEndpointRoutingKey: actual.RoutingKey,
})
case *endpoint.Slack:
o.Kind = KindNotificationEndpointSlack
o.Spec[fieldNotificationEndpointURL] = actual.URL
assignNonZeroSecrets(o.Spec, map[string]influxdb.SecretField{
fieldNotificationEndpointToken: actual.Token,
})
}
return o
}
// NotificationRuleToObject converts an notification rule into a pkger Object.
func NotificationRuleToObject(name, endpointPkgName string, iRule influxdb.NotificationRule) Object {
if name == "" {
name = iRule.GetName()
}
o := newObject(KindNotificationRule, name)
o.Spec[fieldNotificationRuleEndpointName] = endpointPkgName
assignNonZeroStrings(o.Spec, map[string]string{
fieldDescription: iRule.GetDescription(),
})
assignBase := func(base rule.Base) {
assignNonZeroFluxDurs(o.Spec, map[string]*notification.Duration{
fieldEvery: base.Every,
fieldOffset: base.Offset,
})
var tagRes []Resource
for _, tRule := range base.TagRules {
tagRes = append(tagRes, Resource{
fieldKey: tRule.Key,
fieldValue: tRule.Value,
fieldOperator: tRule.Operator.String(),
})
}
if len(tagRes) > 0 {
o.Spec[fieldNotificationRuleTagRules] = tagRes
}
var statusRuleRes []Resource
for _, sRule := range base.StatusRules {
sRes := Resource{
fieldNotificationRuleCurrentLevel: sRule.CurrentLevel.String(),
}
if sRule.PreviousLevel != nil {
sRes[fieldNotificationRulePreviousLevel] = sRule.PreviousLevel.String()
}
statusRuleRes = append(statusRuleRes, sRes)
}
if len(statusRuleRes) > 0 {
o.Spec[fieldNotificationRuleStatusRules] = statusRuleRes
}
}
switch t := iRule.(type) {
case *rule.HTTP:
assignBase(t.Base)
case *rule.PagerDuty:
assignBase(t.Base)
o.Spec[fieldNotificationRuleMessageTemplate] = t.MessageTemplate
case *rule.Slack:
assignBase(t.Base)
o.Spec[fieldNotificationRuleMessageTemplate] = t.MessageTemplate
assignNonZeroStrings(o.Spec, map[string]string{fieldNotificationRuleChannel: t.Channel})
}
return o
}
// regex used to rip out the hard coded task option stuffs
var taskFluxRegex = regexp.MustCompile(`option task = {(.|\n)*?}`)
// TaskToObject converts an influxdb.Task into a pkger.Object.
func TaskToObject(name string, t taskmodel.Task) Object {
if name == "" {
name = t.Name
}
query := strings.TrimSpace(taskFluxRegex.ReplaceAllString(t.Flux, ""))
o := newObject(KindTask, name)
assignNonZeroStrings(o.Spec, map[string]string{
fieldTaskCron: t.Cron,
fieldDescription: t.Description,
fieldEvery: t.Every,
fieldOffset: durToStr(t.Offset),
fieldQuery: strings.TrimSpace(query),
})
return o
}
// TelegrafToObject converts an influxdb.TelegrafConfig into a pkger.Object.
func TelegrafToObject(name string, t influxdb.TelegrafConfig) Object {
if name == "" {
name = t.Name
}
o := newObject(KindTelegraf, name)
assignNonZeroStrings(o.Spec, map[string]string{
fieldTelegrafConfig: t.Config,
fieldDescription: t.Description,
})
return o
}
// VariableToObject converts an influxdb.Variable to a pkger.Object.
func VariableToObject(name string, v influxdb.Variable) Object {
if name == "" {
name = v.Name
}
o := newObject(KindVariable, name)
assignNonZeroStrings(o.Spec, map[string]string{fieldDescription: v.Description})
if len(v.Selected) > 0 {
o.Spec[fieldVariableSelected] = v.Selected
}
args := v.Arguments
if args == nil {
return o
}
o.Spec[fieldType] = args.Type
switch args.Type {
case fieldArgTypeConstant:
vals, ok := args.Values.(influxdb.VariableConstantValues)
if ok {
o.Spec[fieldValues] = []string(vals)
}
case fieldArgTypeMap:
vals, ok := args.Values.(influxdb.VariableMapValues)
if ok {
o.Spec[fieldValues] = map[string]string(vals)
}
case fieldArgTypeQuery:
vals, ok := args.Values.(influxdb.VariableQueryValues)
if ok {
o.Spec[fieldLanguage] = vals.Language
o.Spec[fieldQuery] = strings.TrimSpace(vals.Query)
}
}
return o
}
func newObject(kind Kind, name string) Object {
return Object{
APIVersion: APIVersion,
Kind: kind,
Metadata: Resource{
// this timestamp is added to make the resource unique. Should also indicate
// to the end user that this is machine readable and the spec.name field is
// the one they want to edit when a name change is desired.
fieldName: strings.ToLower(idGenerator.ID().String()),
},
Spec: Resource{
fieldName: name,
},
}
}
func assignNonZeroFluxDurs(r Resource, m map[string]*notification.Duration) {
for field, dur := range m {
if dur == nil {
continue
}
if dur.TimeDuration() == 0 {
continue
}
r[field] = dur.TimeDuration().String()
}
}
func assignNonZeroBools(r Resource, m map[string]bool) {
for k, v := range m {
if v {
r[k] = v
}
}
}
func assignNonZeroInts(r Resource, m map[string]int) {
for k, v := range m {
if v != 0 {
r[k] = v
}
}
}
func assignNonZeroFloats(r Resource, m map[string]float64) {
for k, v := range m {
if v != 0 {
r[k] = v
}
}
}
func assignNonZeroStrings(r Resource, m map[string]string) {
for k, v := range m {
if v != "" {
r[k] = v
}
}
}
func assignNonZeroSecrets(r Resource, m map[string]influxdb.SecretField) {
for field, secret := range m {
if secret.Key == "" {
continue
}
r[field] = Resource{
fieldReferencesSecret: Resource{
fieldKey: secret.Key,
},
}
}
}
func stringsToColors(clrs []string) colors {
newColors := make(colors, 0)
for _, x := range clrs {
newColors = append(newColors, &color{Hex: x})
}
return newColors
}