package pkger import ( "reflect" "sort" "github.com/influxdata/influxdb/v2" "github.com/influxdata/influxdb/v2/notification/rule" ) type stateCoordinator struct { mBuckets map[string]*stateBucket mChecks map[string]*stateCheck mDashboards map[string]*stateDashboard mEndpoints map[string]*stateEndpoint mLabels map[string]*stateLabel mRules map[string]*stateRule mTasks map[string]*stateTask mTelegrafs map[string]*stateTelegraf mVariables map[string]*stateVariable labelMappings []stateLabelMapping labelMappingsToRemove []stateLabelMappingForRemoval } func newStateCoordinator(template *Template, acts resourceActions) *stateCoordinator { state := stateCoordinator{ mBuckets: make(map[string]*stateBucket), mChecks: make(map[string]*stateCheck), mDashboards: make(map[string]*stateDashboard), mEndpoints: make(map[string]*stateEndpoint), mLabels: make(map[string]*stateLabel), mRules: make(map[string]*stateRule), mTasks: make(map[string]*stateTask), mTelegrafs: make(map[string]*stateTelegraf), mVariables: make(map[string]*stateVariable), } // labels are done first to validate dependencies are accounted for. // when a label is skipped by an action, this will still be accurate // for hte individual labels, and cascades to the resources that are // associated to a label. for _, l := range template.labels() { if acts.skipResource(KindLabel, l.MetaName()) { continue } state.mLabels[l.MetaName()] = &stateLabel{ parserLabel: l, stateStatus: StateStatusNew, } } for _, b := range template.buckets() { if acts.skipResource(KindBucket, b.MetaName()) { continue } state.mBuckets[b.MetaName()] = &stateBucket{ parserBkt: b, stateStatus: StateStatusNew, labelAssociations: state.templateToStateLabels(b.labels), } } for _, c := range template.checks() { if acts.skipResource(KindCheck, c.MetaName()) { continue } state.mChecks[c.MetaName()] = &stateCheck{ parserCheck: c, stateStatus: StateStatusNew, labelAssociations: state.templateToStateLabels(c.labels), } } for _, d := range template.dashboards() { if acts.skipResource(KindDashboard, d.MetaName()) { continue } state.mDashboards[d.MetaName()] = &stateDashboard{ parserDash: d, stateStatus: StateStatusNew, labelAssociations: state.templateToStateLabels(d.labels), } } for _, e := range template.notificationEndpoints() { if acts.skipResource(KindNotificationEndpoint, e.MetaName()) { continue } state.mEndpoints[e.MetaName()] = &stateEndpoint{ parserEndpoint: e, stateStatus: StateStatusNew, labelAssociations: state.templateToStateLabels(e.labels), } } for _, r := range template.notificationRules() { if acts.skipResource(KindNotificationRule, r.MetaName()) { continue } state.mRules[r.MetaName()] = &stateRule{ parserRule: r, stateStatus: StateStatusNew, labelAssociations: state.templateToStateLabels(r.labels), } } for _, task := range template.tasks() { if acts.skipResource(KindTask, task.MetaName()) { continue } state.mTasks[task.MetaName()] = &stateTask{ parserTask: task, stateStatus: StateStatusNew, labelAssociations: state.templateToStateLabels(task.labels), } } for _, tele := range template.telegrafs() { if acts.skipResource(KindTelegraf, tele.MetaName()) { continue } state.mTelegrafs[tele.MetaName()] = &stateTelegraf{ parserTelegraf: tele, stateStatus: StateStatusNew, labelAssociations: state.templateToStateLabels(tele.labels), } } for _, v := range template.variables() { if acts.skipResource(KindVariable, v.MetaName()) { continue } state.mVariables[v.MetaName()] = &stateVariable{ parserVar: v, stateStatus: StateStatusNew, labelAssociations: state.templateToStateLabels(v.labels), } } return &state } func (s *stateCoordinator) buckets() []*stateBucket { out := make([]*stateBucket, 0, len(s.mBuckets)) for _, v := range s.mBuckets { out = append(out, v) } return out } func (s *stateCoordinator) checks() []*stateCheck { out := make([]*stateCheck, 0, len(s.mChecks)) for _, v := range s.mChecks { out = append(out, v) } return out } func (s *stateCoordinator) dashboards() []*stateDashboard { out := make([]*stateDashboard, 0, len(s.mDashboards)) for _, d := range s.mDashboards { out = append(out, d) } return out } func (s *stateCoordinator) endpoints() []*stateEndpoint { out := make([]*stateEndpoint, 0, len(s.mEndpoints)) for _, e := range s.mEndpoints { out = append(out, e) } return out } func (s *stateCoordinator) labels() []*stateLabel { out := make([]*stateLabel, 0, len(s.mLabels)) for _, v := range s.mLabels { out = append(out, v) } return out } func (s *stateCoordinator) rules() []*stateRule { out := make([]*stateRule, 0, len(s.mRules)) for _, r := range s.mRules { out = append(out, r) } return out } func (s *stateCoordinator) tasks() []*stateTask { out := make([]*stateTask, 0, len(s.mTasks)) for _, t := range s.mTasks { out = append(out, t) } return out } func (s *stateCoordinator) telegrafConfigs() []*stateTelegraf { out := make([]*stateTelegraf, 0, len(s.mTelegrafs)) for _, t := range s.mTelegrafs { out = append(out, t) } return out } func (s *stateCoordinator) variables() []*stateVariable { out := make([]*stateVariable, 0, len(s.mVariables)) for _, v := range s.mVariables { out = append(out, v) } return out } func (s *stateCoordinator) diff() Diff { var diff Diff for _, b := range s.mBuckets { diff.Buckets = append(diff.Buckets, b.diffBucket()) } sort.Slice(diff.Buckets, func(i, j int) bool { return diff.Buckets[i].MetaName < diff.Buckets[j].MetaName }) for _, c := range s.mChecks { diff.Checks = append(diff.Checks, c.diffCheck()) } sort.Slice(diff.Checks, func(i, j int) bool { return diff.Checks[i].MetaName < diff.Checks[j].MetaName }) for _, d := range s.mDashboards { diff.Dashboards = append(diff.Dashboards, d.diffDashboard()) } sort.Slice(diff.Dashboards, func(i, j int) bool { return diff.Dashboards[i].MetaName < diff.Dashboards[j].MetaName }) for _, e := range s.mEndpoints { diff.NotificationEndpoints = append(diff.NotificationEndpoints, e.diffEndpoint()) } sort.Slice(diff.NotificationEndpoints, func(i, j int) bool { return diff.NotificationEndpoints[i].MetaName < diff.NotificationEndpoints[j].MetaName }) for _, l := range s.mLabels { diff.Labels = append(diff.Labels, l.diffLabel()) } sort.Slice(diff.Labels, func(i, j int) bool { return diff.Labels[i].MetaName < diff.Labels[j].MetaName }) for _, r := range s.mRules { diff.NotificationRules = append(diff.NotificationRules, r.diffRule()) } sort.Slice(diff.NotificationRules, func(i, j int) bool { return diff.NotificationRules[i].MetaName < diff.NotificationRules[j].MetaName }) for _, t := range s.mTasks { diff.Tasks = append(diff.Tasks, t.diffTask()) } sort.Slice(diff.Tasks, func(i, j int) bool { return diff.Tasks[i].MetaName < diff.Tasks[j].MetaName }) for _, t := range s.mTelegrafs { diff.Telegrafs = append(diff.Telegrafs, t.diffTelegraf()) } sort.Slice(diff.Telegrafs, func(i, j int) bool { return diff.Telegrafs[i].MetaName < diff.Telegrafs[j].MetaName }) for _, v := range s.mVariables { diff.Variables = append(diff.Variables, v.diffVariable()) } sort.Slice(diff.Variables, func(i, j int) bool { return diff.Variables[i].MetaName < diff.Variables[j].MetaName }) for _, m := range s.labelMappings { diff.LabelMappings = append(diff.LabelMappings, m.diffLabelMapping()) } for _, m := range s.labelMappingsToRemove { diff.LabelMappings = append(diff.LabelMappings, m.diffLabelMapping()) } sort.Slice(diff.LabelMappings, func(i, j int) bool { n, m := diff.LabelMappings[i], diff.LabelMappings[j] if n.ResType < m.ResType { return true } if n.ResType > m.ResType { return false } if n.ResMetaName < m.ResMetaName { return true } if n.ResMetaName > m.ResMetaName { return false } return n.LabelName < m.LabelName }) return diff } func (s *stateCoordinator) summary() Summary { var sum Summary for _, v := range s.mBuckets { if IsRemoval(v.stateStatus) { continue } sum.Buckets = append(sum.Buckets, v.summarize()) } sort.Slice(sum.Buckets, func(i, j int) bool { return sum.Buckets[i].MetaName < sum.Buckets[j].MetaName }) for _, c := range s.mChecks { if IsRemoval(c.stateStatus) { continue } sum.Checks = append(sum.Checks, c.summarize()) } sort.Slice(sum.Checks, func(i, j int) bool { return sum.Checks[i].MetaName < sum.Checks[j].MetaName }) for _, d := range s.mDashboards { if IsRemoval(d.stateStatus) { continue } sum.Dashboards = append(sum.Dashboards, d.summarize()) } sort.Slice(sum.Dashboards, func(i, j int) bool { return sum.Dashboards[i].MetaName < sum.Dashboards[j].MetaName }) for _, e := range s.mEndpoints { if IsRemoval(e.stateStatus) { continue } sum.NotificationEndpoints = append(sum.NotificationEndpoints, e.summarize()) } sort.Slice(sum.NotificationEndpoints, func(i, j int) bool { return sum.NotificationEndpoints[i].MetaName < sum.NotificationEndpoints[j].MetaName }) for _, v := range s.mLabels { if IsRemoval(v.stateStatus) { continue } sum.Labels = append(sum.Labels, v.summarize()) } sort.Slice(sum.Labels, func(i, j int) bool { return sum.Labels[i].MetaName < sum.Labels[j].MetaName }) for _, v := range s.mRules { if IsRemoval(v.stateStatus) { continue } sum.NotificationRules = append(sum.NotificationRules, v.summarize()) } sort.Slice(sum.NotificationRules, func(i, j int) bool { return sum.NotificationRules[i].MetaName < sum.NotificationRules[j].MetaName }) for _, t := range s.mTasks { if IsRemoval(t.stateStatus) { continue } sum.Tasks = append(sum.Tasks, t.summarize()) } sort.Slice(sum.Tasks, func(i, j int) bool { return sum.Tasks[i].MetaName < sum.Tasks[j].MetaName }) for _, t := range s.mTelegrafs { if IsRemoval(t.stateStatus) { continue } sum.TelegrafConfigs = append(sum.TelegrafConfigs, t.summarize()) } sort.Slice(sum.TelegrafConfigs, func(i, j int) bool { return sum.TelegrafConfigs[i].MetaName < sum.TelegrafConfigs[j].MetaName }) for _, v := range s.mVariables { if IsRemoval(v.stateStatus) { continue } sum.Variables = append(sum.Variables, v.summarize()) } sort.Slice(sum.Variables, func(i, j int) bool { return sum.Variables[i].MetaName < sum.Variables[j].MetaName }) for _, v := range s.labelMappings { sum.LabelMappings = append(sum.LabelMappings, v.summarize()) } sort.Slice(sum.LabelMappings, func(i, j int) bool { n, m := sum.LabelMappings[i], sum.LabelMappings[j] if n.ResourceType != m.ResourceType { return n.ResourceType < m.ResourceType } if n.ResourceMetaName != m.ResourceMetaName { return n.ResourceMetaName < m.ResourceMetaName } return n.LabelName < m.LabelName }) return sum } func (s *stateCoordinator) getLabelByMetaName(metaName string) (*stateLabel, bool) { l, ok := s.mLabels[metaName] return l, ok } func (s *stateCoordinator) templateToStateLabels(labels []*label) []*stateLabel { var out []*stateLabel for _, l := range labels { stLabel, found := s.getLabelByMetaName(l.MetaName()) if !found { continue } out = append(out, stLabel) } return out } func (s *stateCoordinator) addStackState(stack Stack) { reconcilers := []func([]StackResource){ s.reconcileStackResources, s.reconcileLabelMappings, s.reconcileNotificationDependencies, } for _, reconcileFn := range reconcilers { reconcileFn(stack.LatestEvent().Resources) } } func (s *stateCoordinator) reconcileStackResources(stackResources []StackResource) { for _, r := range stackResources { if !s.Contains(r.Kind, r.MetaName) { s.addObjectForRemoval(r.Kind, r.MetaName, r.ID) continue } s.setObjectID(r.Kind, r.MetaName, r.ID) } } func (s *stateCoordinator) reconcileLabelMappings(stackResources []StackResource) { mLabelMetaNameToID := make(map[string]influxdb.ID) for _, r := range stackResources { if r.Kind.is(KindLabel) { mLabelMetaNameToID[r.MetaName] = r.ID } } for _, r := range stackResources { labels := s.labelAssociations(r.Kind, r.MetaName) if len(r.Associations) == 0 { continue } // if associations agree => do nothing // if associations are new (in state not in stack) => do nothing // if associations are not in state and in stack => add them for removal mStackAss := make(map[StackResourceAssociation]struct{}) for _, ass := range r.Associations { if ass.Kind.is(KindLabel) { mStackAss[ass] = struct{}{} } } for _, l := range labels { // we want to keep associations that are from previous application and are not changing delete(mStackAss, StackResourceAssociation{ Kind: KindLabel, MetaName: l.parserLabel.MetaName(), }) } // all associations that are in the stack but not in the // state fall into here and are marked for removal. for assForRemoval := range mStackAss { s.labelMappingsToRemove = append(s.labelMappingsToRemove, stateLabelMappingForRemoval{ LabelMetaName: assForRemoval.MetaName, LabelID: mLabelMetaNameToID[assForRemoval.MetaName], ResourceID: r.ID, ResourceMetaName: r.MetaName, ResourceType: r.Kind.ResourceType(), }) } } } func (s *stateCoordinator) reconcileNotificationDependencies(stackResources []StackResource) { for _, r := range stackResources { if r.Kind.is(KindNotificationRule) { for _, ass := range r.Associations { if ass.Kind.is(KindNotificationEndpoint) { s.mRules[r.MetaName].associatedEndpoint = s.mEndpoints[ass.MetaName] break } } } } } func (s *stateCoordinator) get(k Kind, metaName string) (interface{}, bool) { switch k { case KindBucket: v, ok := s.mBuckets[metaName] return v, ok case KindCheck, KindCheckDeadman, KindCheckThreshold: v, ok := s.mChecks[metaName] return v, ok case KindDashboard: v, ok := s.mDashboards[metaName] return v, ok case KindLabel: v, ok := s.mLabels[metaName] return v, ok case KindNotificationEndpoint, KindNotificationEndpointHTTP, KindNotificationEndpointPagerDuty, KindNotificationEndpointSlack: v, ok := s.mEndpoints[metaName] return v, ok case KindNotificationRule: v, ok := s.mRules[metaName] return v, ok case KindTask: v, ok := s.mTasks[metaName] return v, ok case KindTelegraf: v, ok := s.mTelegrafs[metaName] return v, ok case KindVariable: v, ok := s.mVariables[metaName] return v, ok default: return nil, false } } func (s *stateCoordinator) labelAssociations(k Kind, metaName string) []*stateLabel { v, _ := s.get(k, metaName) labeler, ok := v.(interface { labels() []*stateLabel }) if !ok { return nil } return labeler.labels() } func (s *stateCoordinator) Contains(k Kind, metaName string) bool { _, ok := s.get(k, metaName) return ok } // setObjectID sets the id for the resource graphed from the object the key identifies. func (s *stateCoordinator) setObjectID(k Kind, metaName string, id influxdb.ID) { idSetFn, ok := s.getObjectIDSetter(k, metaName) if !ok { return } idSetFn(id) } // addObjectForRemoval sets the id for the resource graphed from the object the key identifies. // The metaName and kind are used as the unique identifier, when calling this it will // overwrite any existing value if one exists. If desired, check for the value by using // the Contains method. func (s *stateCoordinator) addObjectForRemoval(k Kind, metaName string, id influxdb.ID) { newIdentity := identity{ name: &references{val: metaName}, } switch k { case KindBucket: s.mBuckets[metaName] = &stateBucket{ id: id, parserBkt: &bucket{identity: newIdentity}, stateStatus: StateStatusRemove, } case KindCheck, KindCheckDeadman, KindCheckThreshold: s.mChecks[metaName] = &stateCheck{ id: id, parserCheck: &check{identity: newIdentity}, stateStatus: StateStatusRemove, } case KindDashboard: s.mDashboards[metaName] = &stateDashboard{ id: id, parserDash: &dashboard{identity: newIdentity}, stateStatus: StateStatusRemove, } case KindLabel: s.mLabels[metaName] = &stateLabel{ id: id, parserLabel: &label{identity: newIdentity}, stateStatus: StateStatusRemove, } case KindNotificationEndpoint, KindNotificationEndpointHTTP, KindNotificationEndpointPagerDuty, KindNotificationEndpointSlack: s.mEndpoints[metaName] = &stateEndpoint{ id: id, parserEndpoint: ¬ificationEndpoint{identity: newIdentity}, stateStatus: StateStatusRemove, } case KindNotificationRule: s.mRules[metaName] = &stateRule{ id: id, parserRule: ¬ificationRule{identity: newIdentity}, stateStatus: StateStatusRemove, } case KindTask: s.mTasks[metaName] = &stateTask{ id: id, parserTask: &task{identity: newIdentity}, stateStatus: StateStatusRemove, } case KindTelegraf: s.mTelegrafs[metaName] = &stateTelegraf{ id: id, parserTelegraf: &telegraf{identity: newIdentity}, stateStatus: StateStatusRemove, } case KindVariable: s.mVariables[metaName] = &stateVariable{ id: id, parserVar: &variable{identity: newIdentity}, stateStatus: StateStatusRemove, } } } func (s *stateCoordinator) getObjectIDSetter(k Kind, metaName string) (func(influxdb.ID), bool) { switch k { case KindBucket: r, ok := s.mBuckets[metaName] return func(id influxdb.ID) { r.id = id r.stateStatus = StateStatusExists }, ok case KindCheck, KindCheckDeadman, KindCheckThreshold: r, ok := s.mChecks[metaName] return func(id influxdb.ID) { r.id = id r.stateStatus = StateStatusExists }, ok case KindDashboard: r, ok := s.mDashboards[metaName] return func(id influxdb.ID) { r.id = id r.stateStatus = StateStatusExists }, ok case KindLabel: r, ok := s.mLabels[metaName] return func(id influxdb.ID) { r.id = id r.stateStatus = StateStatusExists }, ok case KindNotificationEndpoint, KindNotificationEndpointHTTP, KindNotificationEndpointPagerDuty, KindNotificationEndpointSlack: r, ok := s.mEndpoints[metaName] return func(id influxdb.ID) { r.id = id r.stateStatus = StateStatusExists }, ok case KindNotificationRule: r, ok := s.mRules[metaName] return func(id influxdb.ID) { r.id = id r.stateStatus = StateStatusExists }, ok case KindTask: r, ok := s.mTasks[metaName] return func(id influxdb.ID) { r.id = id r.stateStatus = StateStatusExists }, ok case KindTelegraf: r, ok := s.mTelegrafs[metaName] return func(id influxdb.ID) { r.id = id r.stateStatus = StateStatusExists }, ok case KindVariable: r, ok := s.mVariables[metaName] return func(id influxdb.ID) { r.id = id r.stateStatus = StateStatusExists }, ok default: return nil, false } } type stateIdentity struct { id influxdb.ID name string metaName string resourceType influxdb.ResourceType stateStatus StateStatus } func (s stateIdentity) exists() bool { return IsExisting(s.stateStatus) } type stateBucket struct { id, orgID influxdb.ID stateStatus StateStatus labelAssociations []*stateLabel parserBkt *bucket existing *influxdb.Bucket } func (b *stateBucket) diffBucket() DiffBucket { diff := DiffBucket{ DiffIdentifier: DiffIdentifier{ Kind: KindBucket, ID: SafeID(b.ID()), StateStatus: b.stateStatus, MetaName: b.parserBkt.MetaName(), }, New: DiffBucketValues{ Name: b.parserBkt.Name(), Description: b.parserBkt.Description, RetentionRules: b.parserBkt.RetentionRules, }, } if e := b.existing; e != nil { diff.Old = &DiffBucketValues{ Name: e.Name, Description: e.Description, } if e.RetentionPeriod > 0 { diff.Old.RetentionRules = retentionRules{newRetentionRule(e.RetentionPeriod)} } } return diff } func stateToSummaryLabels(labels []*stateLabel) []SummaryLabel { out := make([]SummaryLabel, 0, len(labels)) for _, l := range labels { out = append(out, l.summarize()) } return out } func (b *stateBucket) summarize() SummaryBucket { sum := b.parserBkt.summarize() sum.ID = SafeID(b.ID()) sum.OrgID = SafeID(b.orgID) sum.LabelAssociations = stateToSummaryLabels(b.labelAssociations) return sum } func (b *stateBucket) ID() influxdb.ID { if !IsNew(b.stateStatus) && b.existing != nil { return b.existing.ID } return b.id } func (b *stateBucket) resourceType() influxdb.ResourceType { return KindBucket.ResourceType() } func (b *stateBucket) labels() []*stateLabel { return b.labelAssociations } func (b *stateBucket) stateIdentity() stateIdentity { return stateIdentity{ id: b.ID(), name: b.parserBkt.Name(), metaName: b.parserBkt.MetaName(), resourceType: b.resourceType(), stateStatus: b.stateStatus, } } func (b *stateBucket) shouldApply() bool { return IsRemoval(b.stateStatus) || b.existing == nil || b.parserBkt.Description != b.existing.Description || b.parserBkt.Name() != b.existing.Name || b.parserBkt.RetentionRules.RP() != b.existing.RetentionPeriod } type stateCheck struct { id, orgID influxdb.ID stateStatus StateStatus labelAssociations []*stateLabel parserCheck *check existing influxdb.Check } func (c *stateCheck) ID() influxdb.ID { if !IsNew(c.stateStatus) && c.existing != nil { return c.existing.GetID() } return c.id } func (c *stateCheck) labels() []*stateLabel { return c.labelAssociations } func (c *stateCheck) resourceType() influxdb.ResourceType { return KindCheck.ResourceType() } func (c *stateCheck) stateIdentity() stateIdentity { return stateIdentity{ id: c.ID(), name: c.parserCheck.Name(), metaName: c.parserCheck.MetaName(), resourceType: c.resourceType(), stateStatus: c.stateStatus, } } func (c *stateCheck) diffCheck() DiffCheck { diff := DiffCheck{ DiffIdentifier: DiffIdentifier{ ID: SafeID(c.ID()), StateStatus: c.stateStatus, MetaName: c.parserCheck.MetaName(), }, } newCheck := c.summarize() diff.Kind = newCheck.Kind if newCheck.Check != nil { diff.New.Check = newCheck.Check } if c.existing != nil { diff.Old = &DiffCheckValues{ Check: c.existing, } } return diff } func (c *stateCheck) summarize() SummaryCheck { sum := c.parserCheck.summarize() if sum.Check == nil { return sum } sum.Check.SetID(c.id) sum.Check.SetOrgID(c.orgID) sum.LabelAssociations = stateToSummaryLabels(c.labelAssociations) return sum } type stateDashboard struct { id, orgID influxdb.ID stateStatus StateStatus labelAssociations []*stateLabel parserDash *dashboard existing *influxdb.Dashboard } func (d *stateDashboard) ID() influxdb.ID { if !IsNew(d.stateStatus) && d.existing != nil { return d.existing.ID } return d.id } func (d *stateDashboard) labels() []*stateLabel { return d.labelAssociations } func (d *stateDashboard) resourceType() influxdb.ResourceType { return KindDashboard.ResourceType() } func (d *stateDashboard) stateIdentity() stateIdentity { return stateIdentity{ id: d.ID(), name: d.parserDash.Name(), metaName: d.parserDash.MetaName(), resourceType: d.resourceType(), stateStatus: d.stateStatus, } } func (d *stateDashboard) diffDashboard() DiffDashboard { diff := DiffDashboard{ DiffIdentifier: DiffIdentifier{ Kind: KindDashboard, ID: SafeID(d.ID()), StateStatus: d.stateStatus, MetaName: d.parserDash.MetaName(), }, New: DiffDashboardValues{ Name: d.parserDash.Name(), Desc: d.parserDash.Description, Charts: make([]DiffChart, 0, len(d.parserDash.Charts)), }, } for _, c := range d.parserDash.Charts { diff.New.Charts = append(diff.New.Charts, DiffChart{ Properties: c.properties(), Height: c.Height, Width: c.Width, }) } if d.existing == nil { return diff } oldDiff := DiffDashboardValues{ Name: d.existing.Name, Desc: d.existing.Description, Charts: make([]DiffChart, 0, len(d.existing.Cells)), } for _, c := range d.existing.Cells { var props influxdb.ViewProperties if c.View != nil { props = c.View.Properties } oldDiff.Charts = append(oldDiff.Charts, DiffChart{ Properties: props, XPosition: int(c.X), YPosition: int(c.Y), Height: int(c.H), Width: int(c.W), }) } diff.Old = &oldDiff return diff } func (d *stateDashboard) summarize() SummaryDashboard { sum := d.parserDash.summarize() sum.ID = SafeID(d.ID()) sum.OrgID = SafeID(d.orgID) sum.LabelAssociations = stateToSummaryLabels(d.labelAssociations) return sum } type stateLabel struct { id, orgID influxdb.ID stateStatus StateStatus parserLabel *label existing *influxdb.Label } func (l *stateLabel) diffLabel() DiffLabel { diff := DiffLabel{ DiffIdentifier: DiffIdentifier{ Kind: KindLabel, ID: SafeID(l.ID()), StateStatus: l.stateStatus, MetaName: l.parserLabel.MetaName(), }, New: DiffLabelValues{ Name: l.parserLabel.Name(), Description: l.parserLabel.Description, Color: l.parserLabel.Color, }, } if e := l.existing; e != nil { diff.Old = &DiffLabelValues{ Name: e.Name, Description: e.Properties["description"], Color: e.Properties["color"], } } return diff } func (l *stateLabel) summarize() SummaryLabel { sum := l.parserLabel.summarize() sum.ID = SafeID(l.ID()) sum.OrgID = SafeID(l.orgID) return sum } func (l *stateLabel) ID() influxdb.ID { if !IsNew(l.stateStatus) && l.existing != nil { return l.existing.ID } return l.id } func (l *stateLabel) Name() string { return l.parserLabel.Name() } func (l *stateLabel) MetaName() string { return l.parserLabel.MetaName() } func (l *stateLabel) shouldApply() bool { return IsRemoval(l.stateStatus) || l.existing == nil || l.parserLabel.Description != l.existing.Properties["description"] || l.parserLabel.Name() != l.existing.Name || l.parserLabel.Color != l.existing.Properties["color"] } func (l *stateLabel) toInfluxLabel() influxdb.Label { return influxdb.Label{ ID: l.ID(), OrgID: l.orgID, Name: l.parserLabel.Name(), Properties: l.properties(), } } func (l *stateLabel) properties() map[string]string { return map[string]string{ "color": l.parserLabel.Color, "description": l.parserLabel.Description, } } type stateLabelMapping struct { status StateStatus resource interface { stateIdentity() stateIdentity } label *stateLabel } func (lm stateLabelMapping) diffLabelMapping() DiffLabelMapping { ident := lm.resource.stateIdentity() return DiffLabelMapping{ StateStatus: lm.status, ResType: ident.resourceType, ResID: SafeID(ident.id), ResMetaName: ident.metaName, ResName: ident.name, LabelID: SafeID(lm.label.ID()), LabelMetaName: lm.label.parserLabel.MetaName(), LabelName: lm.label.parserLabel.Name(), } } func (lm stateLabelMapping) summarize() SummaryLabelMapping { ident := lm.resource.stateIdentity() return SummaryLabelMapping{ Status: lm.status, ResourceID: SafeID(ident.id), ResourceMetaName: ident.metaName, ResourceName: ident.name, ResourceType: ident.resourceType, LabelMetaName: lm.label.parserLabel.MetaName(), LabelName: lm.label.parserLabel.Name(), LabelID: SafeID(lm.label.ID()), } } func stateLabelMappingToInfluxLabelMapping(mapping stateLabelMapping) influxdb.LabelMapping { ident := mapping.resource.stateIdentity() return influxdb.LabelMapping{ LabelID: mapping.label.ID(), ResourceID: ident.id, ResourceType: ident.resourceType, } } type stateLabelMappingForRemoval struct { LabelID influxdb.ID LabelMetaName string ResourceID influxdb.ID ResourceMetaName string ResourceType influxdb.ResourceType } func (m *stateLabelMappingForRemoval) diffLabelMapping() DiffLabelMapping { return DiffLabelMapping{ StateStatus: StateStatusRemove, ResType: m.ResourceType, ResID: SafeID(m.ResourceID), ResMetaName: m.ResourceMetaName, LabelID: SafeID(m.LabelID), LabelMetaName: m.LabelMetaName, } } type stateEndpoint struct { id, orgID influxdb.ID stateStatus StateStatus labelAssociations []*stateLabel parserEndpoint *notificationEndpoint existing influxdb.NotificationEndpoint } func (e *stateEndpoint) ID() influxdb.ID { if !IsNew(e.stateStatus) && e.existing != nil { return e.existing.GetID() } return e.id } func (e *stateEndpoint) diffEndpoint() DiffNotificationEndpoint { diff := DiffNotificationEndpoint{ DiffIdentifier: DiffIdentifier{ ID: SafeID(e.ID()), StateStatus: e.stateStatus, MetaName: e.parserEndpoint.MetaName(), }, } sum := e.summarize() diff.Kind = sum.Kind if sum.NotificationEndpoint != nil { diff.New.NotificationEndpoint = sum.NotificationEndpoint } if e.existing != nil { diff.Old = &DiffNotificationEndpointValues{ NotificationEndpoint: e.existing, } } return diff } func (e *stateEndpoint) labels() []*stateLabel { return e.labelAssociations } func (e *stateEndpoint) resourceType() influxdb.ResourceType { return KindNotificationEndpoint.ResourceType() } func (e *stateEndpoint) stateIdentity() stateIdentity { return stateIdentity{ id: e.ID(), name: e.parserEndpoint.Name(), metaName: e.parserEndpoint.MetaName(), resourceType: e.resourceType(), stateStatus: e.stateStatus, } } func (e *stateEndpoint) summarize() SummaryNotificationEndpoint { sum := e.parserEndpoint.summarize() if sum.NotificationEndpoint == nil { return sum } if e.ID() != 0 { sum.NotificationEndpoint.SetID(e.ID()) } if e.orgID != 0 { sum.NotificationEndpoint.SetOrgID(e.orgID) } sum.LabelAssociations = stateToSummaryLabels(e.labelAssociations) return sum } type stateRule struct { id, orgID influxdb.ID stateStatus StateStatus labelAssociations []*stateLabel associatedEndpoint *stateEndpoint parserRule *notificationRule existing influxdb.NotificationRule } func (r *stateRule) ID() influxdb.ID { if !IsNew(r.stateStatus) && r.existing != nil { return r.existing.GetID() } return r.id } func (r *stateRule) endpointAssociation() StackResourceAssociation { if r.associatedEndpoint == nil { return StackResourceAssociation{} } return StackResourceAssociation{ Kind: KindNotificationEndpoint, MetaName: r.endpointTemplateName(), } } func (r *stateRule) diffRule() DiffNotificationRule { sum := DiffNotificationRule{ DiffIdentifier: DiffIdentifier{ Kind: KindNotificationRule, ID: SafeID(r.ID()), StateStatus: r.stateStatus, MetaName: r.parserRule.MetaName(), }, New: DiffNotificationRuleValues{ Name: r.parserRule.Name(), Description: r.parserRule.description, EndpointName: r.endpointTemplateName(), EndpointID: SafeID(r.endpointID()), EndpointType: r.endpointType(), Every: r.parserRule.every.String(), Offset: r.parserRule.offset.String(), MessageTemplate: r.parserRule.msgTemplate, StatusRules: toSummaryStatusRules(r.parserRule.statusRules), TagRules: toSummaryTagRules(r.parserRule.tagRules), }, } if r.existing == nil { return sum } sum.Old = &DiffNotificationRuleValues{ Name: r.existing.GetName(), Description: r.existing.GetDescription(), EndpointName: r.existing.GetName(), EndpointID: SafeID(r.existing.GetEndpointID()), EndpointType: r.existing.Type(), } assignBase := func(b rule.Base) { if b.Every != nil { sum.Old.Every = b.Every.TimeDuration().String() } if b.Offset != nil { sum.Old.Offset = b.Offset.TimeDuration().String() } for _, tr := range b.TagRules { sum.Old.TagRules = append(sum.Old.TagRules, SummaryTagRule{ Key: tr.Key, Value: tr.Value, Operator: tr.Operator.String(), }) } for _, sr := range b.StatusRules { sRule := SummaryStatusRule{CurrentLevel: sr.CurrentLevel.String()} if sr.PreviousLevel != nil { sRule.PreviousLevel = sr.PreviousLevel.String() } sum.Old.StatusRules = append(sum.Old.StatusRules, sRule) } } switch p := r.existing.(type) { case *rule.HTTP: assignBase(p.Base) case *rule.Slack: assignBase(p.Base) sum.Old.MessageTemplate = p.MessageTemplate case *rule.PagerDuty: assignBase(p.Base) sum.Old.MessageTemplate = p.MessageTemplate } return sum } func (r *stateRule) endpointID() influxdb.ID { if r.associatedEndpoint != nil { return r.associatedEndpoint.ID() } return 0 } func (r *stateRule) endpointTemplateName() string { if r.associatedEndpoint != nil && r.associatedEndpoint.parserEndpoint != nil { return r.associatedEndpoint.parserEndpoint.MetaName() } return "" } func (r *stateRule) endpointType() string { if r.associatedEndpoint != nil { return r.associatedEndpoint.parserEndpoint.kind.String() } return "" } func (r *stateRule) labels() []*stateLabel { return r.labelAssociations } func (r *stateRule) resourceType() influxdb.ResourceType { return KindNotificationRule.ResourceType() } func (r *stateRule) stateIdentity() stateIdentity { return stateIdentity{ id: r.ID(), name: r.parserRule.Name(), metaName: r.parserRule.MetaName(), resourceType: r.resourceType(), stateStatus: r.stateStatus, } } func (r *stateRule) summarize() SummaryNotificationRule { sum := r.parserRule.summarize() sum.ID = SafeID(r.id) sum.EndpointID = SafeID(r.associatedEndpoint.ID()) sum.EndpointMetaName = r.associatedEndpoint.parserEndpoint.MetaName() sum.EndpointType = r.associatedEndpoint.parserEndpoint.kind.String() sum.LabelAssociations = stateToSummaryLabels(r.labelAssociations) return sum } func (r *stateRule) toInfluxRule() influxdb.NotificationRule { influxRule := r.parserRule.toInfluxRule() if r.ID() > 0 { influxRule.SetID(r.ID()) } if r.orgID > 0 { influxRule.SetOrgID(r.orgID) } switch e := influxRule.(type) { case *rule.HTTP: e.EndpointID = r.associatedEndpoint.ID() case *rule.PagerDuty: e.EndpointID = r.associatedEndpoint.ID() case *rule.Slack: e.EndpointID = r.associatedEndpoint.ID() } return influxRule } type stateTask struct { id, orgID influxdb.ID stateStatus StateStatus labelAssociations []*stateLabel parserTask *task existing *influxdb.Task } func (t *stateTask) ID() influxdb.ID { if !IsNew(t.stateStatus) && t.existing != nil { return t.existing.ID } return t.id } func (t *stateTask) diffTask() DiffTask { diff := DiffTask{ DiffIdentifier: DiffIdentifier{ Kind: KindTask, ID: SafeID(t.ID()), StateStatus: t.stateStatus, MetaName: t.parserTask.MetaName(), }, New: DiffTaskValues{ Name: t.parserTask.Name(), Cron: t.parserTask.cron, Description: t.parserTask.description, Every: durToStr(t.parserTask.every), Offset: durToStr(t.parserTask.offset), Query: t.parserTask.query, Status: t.parserTask.Status(), }, } if t.existing == nil { return diff } diff.Old = &DiffTaskValues{ Name: t.existing.Name, Cron: t.existing.Cron, Description: t.existing.Description, Every: t.existing.Every, Offset: t.existing.Offset.String(), Query: t.existing.Flux, Status: influxdb.Status(t.existing.Status), } return diff } func (t *stateTask) labels() []*stateLabel { return t.labelAssociations } func (t *stateTask) resourceType() influxdb.ResourceType { return influxdb.TasksResourceType } func (t *stateTask) stateIdentity() stateIdentity { return stateIdentity{ id: t.ID(), name: t.parserTask.Name(), metaName: t.parserTask.MetaName(), resourceType: t.resourceType(), stateStatus: t.stateStatus, } } func (t *stateTask) summarize() SummaryTask { sum := t.parserTask.summarize() sum.ID = SafeID(t.id) sum.LabelAssociations = stateToSummaryLabels(t.labelAssociations) return sum } type stateTelegraf struct { id, orgID influxdb.ID stateStatus StateStatus labelAssociations []*stateLabel parserTelegraf *telegraf existing *influxdb.TelegrafConfig } func (t *stateTelegraf) ID() influxdb.ID { if !IsNew(t.stateStatus) && t.existing != nil { return t.existing.ID } return t.id } func (t *stateTelegraf) diffTelegraf() DiffTelegraf { return DiffTelegraf{ DiffIdentifier: DiffIdentifier{ Kind: KindTelegraf, ID: SafeID(t.ID()), StateStatus: t.stateStatus, MetaName: t.parserTelegraf.MetaName(), }, New: t.parserTelegraf.config, Old: t.existing, } } func (t *stateTelegraf) labels() []*stateLabel { return t.labelAssociations } func (t *stateTelegraf) resourceType() influxdb.ResourceType { return influxdb.TelegrafsResourceType } func (t *stateTelegraf) stateIdentity() stateIdentity { return stateIdentity{ id: t.ID(), name: t.parserTelegraf.Name(), metaName: t.parserTelegraf.MetaName(), resourceType: t.resourceType(), stateStatus: t.stateStatus, } } func (t *stateTelegraf) summarize() SummaryTelegraf { sum := t.parserTelegraf.summarize() sum.TelegrafConfig.ID = t.id sum.TelegrafConfig.OrgID = t.orgID sum.LabelAssociations = stateToSummaryLabels(t.labelAssociations) return sum } type stateVariable struct { id, orgID influxdb.ID stateStatus StateStatus labelAssociations []*stateLabel parserVar *variable existing *influxdb.Variable } func (v *stateVariable) ID() influxdb.ID { if !IsNew(v.stateStatus) && v.existing != nil { return v.existing.ID } return v.id } func (v *stateVariable) diffVariable() DiffVariable { diff := DiffVariable{ DiffIdentifier: DiffIdentifier{ Kind: KindVariable, ID: SafeID(v.ID()), StateStatus: v.stateStatus, MetaName: v.parserVar.MetaName(), }, New: DiffVariableValues{ Name: v.parserVar.Name(), Description: v.parserVar.Description, Args: v.parserVar.influxVarArgs(), }, } if iv := v.existing; iv != nil { diff.Old = &DiffVariableValues{ Name: iv.Name, Description: iv.Description, Args: iv.Arguments, } } return diff } func (v *stateVariable) labels() []*stateLabel { return v.labelAssociations } func (v *stateVariable) resourceType() influxdb.ResourceType { return KindVariable.ResourceType() } func (v *stateVariable) shouldApply() bool { return IsRemoval(v.stateStatus) || v.existing == nil || v.existing.Description != v.parserVar.Description || !reflect.DeepEqual(v.existing.Selected, v.parserVar.Selected()) || v.existing.Arguments == nil || !reflect.DeepEqual(v.existing.Arguments, v.parserVar.influxVarArgs()) } func (v *stateVariable) stateIdentity() stateIdentity { return stateIdentity{ id: v.ID(), name: v.parserVar.Name(), metaName: v.parserVar.MetaName(), resourceType: v.resourceType(), stateStatus: v.stateStatus, } } func (v *stateVariable) summarize() SummaryVariable { sum := v.parserVar.summarize() sum.ID = SafeID(v.ID()) sum.OrgID = SafeID(v.orgID) sum.LabelAssociations = stateToSummaryLabels(v.labelAssociations) return sum } // IsNew identifies state status as new to the platform. func IsNew(status StateStatus) bool { // defaulting zero value to identify as new return status == StateStatusNew || status == "" } // IsExisting identifies state status as existing in the platform. func IsExisting(status StateStatus) bool { return status == StateStatusExists } // IsRemoval identifies state status as existing resource that will be removed // from the platform. func IsRemoval(status StateStatus) bool { return status == StateStatusRemove } type resourceActions struct { skipKinds map[Kind]bool skipResources map[ActionSkipResource]bool } func (r resourceActions) skipResource(k Kind, metaName string) bool { key := ActionSkipResource{ Kind: k, MetaName: metaName, } return r.skipResources[key] || r.skipKinds[k] }