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  }