github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/data/data.go (about) 1 // Copyright (c) 2021-2022, R.I. Pienaar and the Choria Project contributors 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package data 6 7 import ( 8 "context" 9 "fmt" 10 "strings" 11 "sync" 12 13 "github.com/choria-io/go-choria/internal/util" 14 "github.com/sirupsen/logrus" 15 16 "github.com/choria-io/go-choria/config" 17 "github.com/choria-io/go-choria/providers/data/ddl" 18 "github.com/choria-io/go-choria/server/agents" 19 ) 20 21 type Query any 22 type OutputItem any 23 24 type Creator struct { 25 F func(Framework) (Plugin, error) 26 Name string 27 } 28 29 type Plugin interface { 30 Run(context.Context, Query, agents.ServerInfoSource) (map[string]OutputItem, error) 31 DLL() (*ddl.DDL, error) 32 } 33 34 type Framework interface { 35 Configuration() *config.Config 36 Logger(string) *logrus.Entry 37 } 38 39 type Manager struct { 40 plugins map[string]*Creator 41 ddls map[string]*ddl.DDL 42 fw Framework 43 ctx context.Context 44 45 log *logrus.Entry 46 } 47 48 var ( 49 plugins map[string]*Creator 50 mu sync.Mutex 51 ) 52 53 func NewManager(ctx context.Context, fw Framework) (*Manager, error) { 54 m := &Manager{ 55 fw: fw, 56 plugins: make(map[string]*Creator), 57 ddls: make(map[string]*ddl.DDL), 58 log: fw.Logger("data_manager"), 59 ctx: ctx, 60 } 61 62 mu.Lock() 63 for k, p := range plugins { 64 pi, err := p.F(fw) 65 if err != nil { 66 m.log.Warnf("Could not create instance of data plugin %s", k) 67 continue 68 } 69 70 ddl, err := pi.DLL() 71 if err != nil { 72 m.log.Warnf("Could not load DDL for data plugin %s", k) 73 continue 74 } 75 m.plugins[k] = p 76 m.ddls[k] = ddl 77 m.log.Infof("Activated Data provider %s", k) 78 } 79 mu.Unlock() 80 81 return m, nil 82 } 83 84 func RegisterPlugin(name string, plugin *Creator) error { 85 mu.Lock() 86 defer mu.Unlock() 87 88 if plugins == nil { 89 plugins = make(map[string]*Creator) 90 } 91 92 _, ok := plugins[plugin.Name] 93 if ok { 94 return fmt.Errorf("data plugin %s is already registered", plugin.Name) 95 } 96 97 plugins[plugin.Name] = plugin 98 util.BuildInfo().RegisterDataProvider(name) 99 100 return nil 101 } 102 103 // DDLs is a list of DDLs for all known data plugins 104 func (m *Manager) DDLs() []*ddl.DDL { 105 res := []*ddl.DDL{} 106 for _, v := range m.ddls { 107 res = append(res, v) 108 } 109 110 return res 111 } 112 113 func (m *Manager) FuncMap(si agents.ServerInfoSource) (ddl.FuncMap, error) { 114 funcs := make(ddl.FuncMap) 115 116 for k, v := range m.ddls { 117 entry := ddl.FuncMapEntry{DDL: v, Name: v.Metadata.Name} 118 if v.Query == nil { 119 entry.F = m.arityZeroRunner(m.ctx, si, k, v, m.plugins[k]) 120 } else { 121 entry.F = m.arityOneRunner(m.ctx, si, k, v, m.plugins[k]) 122 } 123 funcs[k] = entry 124 } 125 126 return funcs, nil 127 } 128 129 func (m *Manager) arityZeroRunner(ctx context.Context, si agents.ServerInfoSource, name string, ddl *ddl.DDL, plugin *Creator) func() map[string]OutputItem { 130 return func() map[string]OutputItem { 131 f := m.arityOneRunner(ctx, si, name, ddl, plugin) 132 return f("") 133 } 134 } 135 136 func (m *Manager) arityOneRunner(ctx context.Context, si agents.ServerInfoSource, name string, ddl *ddl.DDL, plugin *Creator) func(q string) map[string]OutputItem { 137 return func(q string) map[string]OutputItem { 138 ctx, cancel := context.WithTimeout(ctx, ddl.Timeout()) 139 defer cancel() 140 141 i, err := plugin.F(m.fw) 142 if err != nil { 143 m.log.Errorf("Could not create an instance of data provider %s: %s", name, err) 144 return nil 145 } 146 147 result, err := i.Run(ctx, q, si) 148 if err != nil { 149 m.log.Errorf("Could not run data provider %s: %s", name, err) 150 return nil 151 } 152 153 return result 154 } 155 } 156 157 func (m *Manager) Execute(ctx context.Context, plugin string, query string, srv agents.ServerInfoSource) (map[string]OutputItem, error) { 158 dpc, ok := m.plugins[plugin] 159 if !ok { 160 return nil, fmt.Errorf("unknown data plugin %s", plugin) 161 } 162 163 dp, err := dpc.F(m.fw) 164 if err != nil { 165 return nil, fmt.Errorf("could not create instance of data plugin %s: %s", plugin, err) 166 } 167 168 pddl, ok := m.ddls[plugin] 169 if !ok { 170 return nil, fmt.Errorf("could not find DDL for plugin %s", plugin) 171 } 172 173 var ( 174 q any 175 ) 176 177 if pddl.Query != nil { 178 q, err = pddl.Query.ConvertStringValue(query) 179 if err != nil { 180 return nil, err 181 } 182 183 w, err := pddl.Query.ValidateValue(q) 184 if err != nil { 185 return nil, err 186 } 187 188 if len(w) > 0 { 189 return nil, fmt.Errorf("invalid query: %s", strings.Join(w, ", ")) 190 } 191 } 192 193 timeout, cancel := context.WithTimeout(ctx, pddl.Timeout()) 194 defer cancel() 195 196 return dp.Run(timeout, q, srv) 197 }