/
plugin.go
188 lines (157 loc) · 5.76 KB
/
plugin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Copyright 2020 New Relic Corporation. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package agent
import (
goContext "context"
"fmt"
"github.com/newrelic/infrastructure-agent/internal/agent/instrumentation"
"sync"
"time"
"github.com/newrelic/infrastructure-agent/pkg/log"
"github.com/sirupsen/logrus"
"github.com/newrelic/infrastructure-agent/internal/agent/metadata"
"github.com/newrelic/infrastructure-agent/pkg/entity"
"github.com/newrelic/infrastructure-agent/pkg/plugins/ids"
)
// Plugin describes the interface all agent plugins implement
type Plugin interface {
Run()
Id() ids.PluginID
IsExternal() bool
GetExternalPluginName() string
LogInfo()
ScheduleHealthCheck()
}
// Killable defines the behaviour of a plugin that can be externally terminated.
type Killable interface {
// Kill terminates the receiver of the function.
Kill()
}
// PluginCommon contains attributes and methods available to all plugins
type PluginCommon struct {
ID ids.PluginID // the "ID" is the path we write the json to
Context AgentContext // a reference to the calling agent context
External bool // If the plugin is an external plugin
ExternalPluginName string // The external plugin name. Ex: com.newrelic.nginx
// Returns all the information related to the plugin, including
// runtime environment variables
DetailedLogFields func() logrus.Fields
LogFields logrus.Fields // fields to include when logging about the plugin
HealthCheckCh chan struct{} // notifies the plugin to execute a health check
decorations map[string]interface{}
once sync.Once
}
func NewExternalPluginCommon(id ids.PluginID, ctx AgentContext, name string) PluginCommon {
return PluginCommon{
ID: id,
Context: ctx,
External: true,
ExternalPluginName: name,
HealthCheckCh: make(chan struct{}, 1),
}
}
// Anything implementing the sortable interface must implement a
// method to return a string Sort key
type Sortable interface {
SortKey() string
}
type PluginInventoryDataset []Sortable // PluginInventoryDataset is a slice of sortable things
// PluginInventoryDataset also implements the sort.Sort interface
func (pd PluginInventoryDataset) Len() int { return len(pd) }
func (pd PluginInventoryDataset) Swap(i, j int) { pd[i], pd[j] = pd[j], pd[i] }
func (pd PluginInventoryDataset) Less(i, j int) bool { return pd[i].SortKey() < pd[j].SortKey() }
// PluginOutput contains metadata about the inventory provided by Plugins, which will be used for its later addition
// to the delta store
type PluginOutput struct {
Id ids.PluginID
Entity entity.Entity
Data PluginInventoryDataset
NotApplicable bool
}
func NewPluginOutput(id ids.PluginID, entity entity.Entity, data PluginInventoryDataset) PluginOutput {
return PluginOutput{Id: id, Entity: entity, Data: data}
}
func NewNotApplicableOutput(id ids.PluginID) PluginOutput {
return PluginOutput{Id: id, NotApplicable: true}
}
// Id is the accessor for the id field
func (pc *PluginCommon) Id() ids.PluginID {
return pc.ID
}
// IsExternal is the accessor for the External field
func (pc *PluginCommon) IsExternal() bool {
return pc.External
}
// LogInfo retrieves logs the plugin name for internal plugins, and
// for the external plugins it logs the data specified in the log fields.
func (pc *PluginCommon) LogInfo() {
if pc.IsExternal() {
log.WithFieldsF(pc.DetailedLogFields).Info("Integration info")
} else {
log.WithPlugin(pc.Id().String()).Info("Agent plugin")
}
}
// GetExternalPluginName is the accessor for the ExternalPluginName field
func (pc *PluginCommon) GetExternalPluginName() string {
return pc.ExternalPluginName
}
type PluginEmitter interface {
EmitInventory(data PluginInventoryDataset, entity entity.Entity)
EmitEvent(eventData map[string]interface{}, entityKey entity.Key)
}
// EmitInventory sends data collected by the plugin to the agent
func (pc *PluginCommon) EmitInventory(data PluginInventoryDataset, entity entity.Entity) {
_, txn := instrumentation.SelfInstrumentation.StartTransaction(goContext.Background(), "plugin.emit_inventory")
txn.AddAttribute("plugin_id", fmt.Sprintf("%s:%s", pc.ID.Category, pc.ID.Term))
defer txn.End()
pc.Context.SendData(NewPluginOutput(pc.ID, entity, data))
}
func (pc *PluginCommon) EmitEvent(eventData map[string]interface{}, entityKey entity.Key) {
pc.decorateEvent(eventData)
pc.Context.SendEvent(mapEvent(eventData), entityKey)
}
func (pc *PluginCommon) gatherDecorations() {
pc.once.Do(func() {
cfg := pc.Context.Config()
if cfg != nil && cfg.K8sIntegration {
pc.decorations = metadata.GatherK8sMetadata()
}
})
}
func (pc *PluginCommon) decorateEvent(eventData map[string]interface{}) {
if eventData["timestamp"] == nil {
eventData["timestamp"] = time.Now().Unix()
}
pc.gatherDecorations()
for k, v := range pc.decorations {
eventData[k] = v
}
}
// Unregister tells the agent that this plugin cannot run
func (pc *PluginCommon) Unregister() {
pc.Context.Unregister(pc.Id())
}
func (pc *PluginCommon) ScheduleHealthCheck() {
if !pc.IsExternal() {
return
}
// The health check channel has a size of 1 so if writing to it blocks
// it means a health check has already been scheduled.
select {
case pc.HealthCheckCh <- struct{}{}:
log.WithFields(pc.LogFields).Info("Integration health check scheduled")
default:
log.WithFields(pc.LogFields).Info("Integration health check already requested")
}
}
// mapEvent allows the eventDataMap to fulfill the Event interface
type mapEvent map[string]interface{}
func (m mapEvent) Timestamp(timestamp int64) {
m["timestamp"] = timestamp
}
func (m mapEvent) Type(eventType string) {
m["eventType"] = eventType
}
func (m mapEvent) Entity(key entity.Key) {
m["entityKey"] = key
}