1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,305 @@
package starlark
import (
"fmt"
"sort"
"time"
"go.starlark.net/starlark"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/metric"
)
func newMetric(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var (
name starlark.String
tags, fields starlark.Value
)
if err := starlark.UnpackArgs("Metric", args, kwargs, "name", &name, "tags?", &tags, "fields?", &fields); err != nil {
return nil, err
}
allFields, err := toFields(fields)
if err != nil {
return nil, err
}
allTags, err := toTags(tags)
if err != nil {
return nil, err
}
m := metric.New(string(name), allTags, allFields, time.Now())
return &Metric{metric: m}, nil
}
func toString(value starlark.Value, errorMsg string) (string, error) {
if value, ok := value.(starlark.String); ok {
return string(value), nil
}
return "", fmt.Errorf(errorMsg, value)
}
func items(value starlark.Value, errorMsg string) ([]starlark.Tuple, error) {
if iter, ok := value.(starlark.IterableMapping); ok {
return iter.Items(), nil
}
return nil, fmt.Errorf(errorMsg, value)
}
func deepcopy(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var sm *Metric
var track bool
if err := starlark.UnpackArgs("deepcopy", args, kwargs, "source", &sm, "track?", &track); err != nil {
return nil, err
}
// In case we copy a tracking metric but do not want to track the result,
// we have to strip the tracking information. This can be done by unwrapping
// the metric.
if tm, ok := sm.metric.(telegraf.TrackingMetric); ok && !track {
return &Metric{metric: tm.Unwrap().Copy()}, nil
}
// Copy the whole metric including potential tracking information
return &Metric{metric: sm.metric.Copy()}, nil
}
// catch(f) evaluates f() and returns its evaluation error message
// if it failed or None if it succeeded.
func catch(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var fn starlark.Callable
if err := starlark.UnpackArgs("catch", args, kwargs, "fn", &fn); err != nil {
return nil, err
}
if _, err := starlark.Call(thread, fn, nil, nil); err != nil {
//nolint:nilerr // nil returned on purpose, error put inside starlark.Value
return starlark.String(err.Error()), nil
}
return starlark.None, nil
}
type builtinMethod func(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error)
func builtinAttr(recv starlark.Value, name string, methods map[string]builtinMethod) (starlark.Value, error) {
method := methods[name]
if method == nil {
return starlark.None, fmt.Errorf("no such method %q", name)
}
// Allocate a closure over 'method'.
impl := func(_ *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
return method(b, args, kwargs)
}
return starlark.NewBuiltin(name, impl).BindReceiver(recv), nil
}
func builtinAttrNames(methods map[string]builtinMethod) []string {
names := make([]string, 0, len(methods))
for name := range methods {
names = append(names, name)
}
sort.Strings(names)
return names
}
// --- dictionary methods ---
// https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·clear
func dictClear(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
type HasClear interface {
Clear() error
}
return starlark.None, b.Receiver().(HasClear).Clear()
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·pop
func dictPop(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var k, d starlark.Value
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &k, &d); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
type HasDelete interface {
Delete(k starlark.Value) (starlark.Value, bool, error)
}
if v, found, err := b.Receiver().(HasDelete).Delete(k); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err) // dict is frozen or key is unhashable
} else if found {
return v, nil
} else if d != nil {
return d, nil
}
return starlark.None, fmt.Errorf("%s: missing key", b.Name())
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·popitem
func dictPopitem(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
type HasPopItem interface {
PopItem() (starlark.Value, error)
}
return b.Receiver().(HasPopItem).PopItem()
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·get
func dictGet(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var key, dflt starlark.Value
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &key, &dflt); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
if v, ok, err := b.Receiver().(starlark.Mapping).Get(key); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
} else if ok {
return v, nil
} else if dflt != nil {
return dflt, nil
}
return starlark.None, nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·setdefault
func dictSetdefault(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
var key, dflt starlark.Value = nil, starlark.None
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &key, &dflt); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
recv := b.Receiver().(starlark.HasSetKey)
v, found, err := recv.Get(key)
if err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
if !found {
v = dflt
if err := recv.SetKey(key, dflt); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
}
return v, nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·update
func dictUpdate(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
// Unpack the arguments
if len(args) > 1 {
return nil, fmt.Errorf("update: got %d arguments, want at most 1", len(args))
}
// Get the target
dict := b.Receiver().(starlark.HasSetKey)
if len(args) == 1 {
switch updates := args[0].(type) {
case starlark.IterableMapping:
// Iterate over dict's key/value pairs, not just keys.
for _, item := range updates.Items() {
if err := dict.SetKey(item[0], item[1]); err != nil {
return nil, err // dict is frozen
}
}
default:
// all other sequences
iter := starlark.Iterate(updates)
if iter == nil {
return nil, fmt.Errorf("got %s, want iterable", updates.Type())
}
defer iter.Done()
var pair starlark.Value
for i := 0; iter.Next(&pair); i++ {
iterErr := func() error {
iter2 := starlark.Iterate(pair)
if iter2 == nil {
return fmt.Errorf("dictionary update sequence element #%d is not iterable (%s)", i, pair.Type())
}
defer iter2.Done()
length := starlark.Len(pair)
if length < 0 {
return fmt.Errorf("dictionary update sequence element #%d has unknown length (%s)", i, pair.Type())
} else if length != 2 {
return fmt.Errorf("dictionary update sequence element #%d has length %d, want 2", i, length)
}
var k, v starlark.Value
iter2.Next(&k)
iter2.Next(&v)
return dict.SetKey(k, v)
}()
if iterErr != nil {
return nil, iterErr
}
}
}
}
// Then add the kwargs.
before := starlark.Len(dict)
for _, pair := range kwargs {
if err := dict.SetKey(pair[0], pair[1]); err != nil {
return nil, err // dict is frozen
}
}
// In the common case, each kwarg will add another dict entry.
// If that's not so, check whether it is because there was a duplicate kwarg.
if starlark.Len(dict) < before+len(kwargs) {
keys := make(map[starlark.String]bool, len(kwargs))
for _, kv := range kwargs {
k := kv[0].(starlark.String)
if keys[k] {
return nil, fmt.Errorf("duplicate keyword arg: %v", k)
}
keys[k] = true
}
}
return starlark.None, nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·items
func dictItems(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
items := b.Receiver().(starlark.IterableMapping).Items()
res := make([]starlark.Value, 0, len(items))
for _, item := range items {
res = append(res, item) // convert [2]starlark.Value to starlark.Value
}
return starlark.NewList(res), nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·keys
func dictKeys(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
items := b.Receiver().(starlark.IterableMapping).Items()
res := make([]starlark.Value, 0, len(items))
for _, item := range items {
res = append(res, item[0])
}
return starlark.NewList(res), nil
}
// https://github.com/google/starlark-go/blob/master/doc/spec.md#dict·update
func dictValues(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 0); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
items := b.Receiver().(starlark.IterableMapping).Items()
res := make([]starlark.Value, 0, len(items))
for _, item := range items {
res = append(res, item[1])
}
return starlark.NewList(res), nil
}

View file

@ -0,0 +1,308 @@
package starlark
import (
"errors"
"fmt"
"reflect"
"strings"
"go.starlark.net/starlark"
"github.com/influxdata/telegraf"
)
// FieldDict is a starlark.Value for the metric fields. It is heavily based on the
// starlark.Dict.
type FieldDict struct {
*Metric
}
func (d FieldDict) String() string {
buf := new(strings.Builder)
buf.WriteString("{")
sep := ""
for _, item := range d.Items() {
k, v := item[0], item[1]
buf.WriteString(sep)
buf.WriteString(k.String())
buf.WriteString(": ")
buf.WriteString(v.String())
sep = ", "
}
buf.WriteString("}")
return buf.String()
}
func (FieldDict) Type() string {
return "Fields"
}
func (d FieldDict) Freeze() {
// Disable linter check as the frozen variable is modified despite
// passing a value instead of a pointer, because `FieldDict` holds
// a pointer to the underlying metric containing the `frozen` field.
//revive:disable:modifies-value-receiver
d.frozen = true
}
func (d FieldDict) Truth() starlark.Bool {
return len(d.metric.FieldList()) != 0
}
func (FieldDict) Hash() (uint32, error) {
return 0, errors.New("not hashable")
}
// AttrNames implements the starlark.HasAttrs interface.
func (FieldDict) AttrNames() []string {
return builtinAttrNames(FieldDictMethods)
}
// Attr implements the starlark.HasAttrs interface.
func (d FieldDict) Attr(name string) (starlark.Value, error) {
return builtinAttr(d, name, FieldDictMethods)
}
var FieldDictMethods = map[string]builtinMethod{
"clear": dictClear,
"get": dictGet,
"items": dictItems,
"keys": dictKeys,
"pop": dictPop,
"popitem": dictPopitem,
"setdefault": dictSetdefault,
"update": dictUpdate,
"values": dictValues,
}
// Get implements the starlark.Mapping interface.
func (d FieldDict) Get(key starlark.Value) (v starlark.Value, found bool, err error) {
if k, ok := key.(starlark.String); ok {
gv, found := d.metric.GetField(k.GoString())
if !found {
return starlark.None, false, nil
}
v, err := asStarlarkValue(gv)
if err != nil {
return starlark.None, false, err
}
return v, true, nil
}
return starlark.None, false, errors.New("key must be of type 'str'")
}
// SetKey implements the starlark.HasSetKey interface to support map update
// using x[k]=v syntax, like a dictionary.
func (d FieldDict) SetKey(k, v starlark.Value) error {
if d.fieldIterCount > 0 {
return errors.New("cannot insert during iteration")
}
key, ok := k.(starlark.String)
if !ok {
return errors.New("field key must be of type 'str'")
}
gv, err := asGoValue(v)
if err != nil {
return err
}
d.metric.AddField(key.GoString(), gv)
return nil
}
// Items implements the starlark.IterableMapping interface.
func (d FieldDict) Items() []starlark.Tuple {
items := make([]starlark.Tuple, 0, len(d.metric.FieldList()))
for _, field := range d.metric.FieldList() {
key := starlark.String(field.Key)
sv, err := asStarlarkValue(field.Value)
if err != nil {
continue
}
pair := starlark.Tuple{key, sv}
items = append(items, pair)
}
return items
}
func (d FieldDict) Clear() error {
if d.fieldIterCount > 0 {
return errors.New("cannot delete during iteration")
}
keys := make([]string, 0, len(d.metric.FieldList()))
for _, field := range d.metric.FieldList() {
keys = append(keys, field.Key)
}
for _, key := range keys {
d.metric.RemoveField(key)
}
return nil
}
func (d FieldDict) PopItem() (starlark.Value, error) {
if d.fieldIterCount > 0 {
return nil, errors.New("cannot delete during iteration")
}
if len(d.metric.FieldList()) == 0 {
return nil, errors.New("popitem(): field dictionary is empty")
}
field := d.metric.FieldList()[0]
k := field.Key
v := field.Value
d.metric.RemoveField(k)
sk := starlark.String(k)
sv, err := asStarlarkValue(v)
if err != nil {
return nil, errors.New("could not convert to starlark value")
}
return starlark.Tuple{sk, sv}, nil
}
func (d FieldDict) Delete(k starlark.Value) (v starlark.Value, found bool, err error) {
if d.fieldIterCount > 0 {
return nil, false, errors.New("cannot delete during iteration")
}
if key, ok := k.(starlark.String); ok {
value, ok := d.metric.GetField(key.GoString())
if ok {
d.metric.RemoveField(key.GoString())
sv, err := asStarlarkValue(value)
return sv, ok, err
}
return starlark.None, false, nil
}
return starlark.None, false, errors.New("key must be of type 'str'")
}
// Iterate implements the starlark.Iterator interface.
func (d FieldDict) Iterate() starlark.Iterator {
d.fieldIterCount++
return &FieldIterator{Metric: d.Metric, fields: d.metric.FieldList()}
}
type FieldIterator struct {
*Metric
fields []*telegraf.Field
}
// Next implements the starlark.Iterator interface.
func (i *FieldIterator) Next(p *starlark.Value) bool {
if len(i.fields) == 0 {
return false
}
field := i.fields[0]
i.fields = i.fields[1:]
*p = starlark.String(field.Key)
return true
}
// Done implements the starlark.Iterator interface.
func (i *FieldIterator) Done() {
i.fieldIterCount--
}
// AsStarlarkValue converts a field value to a starlark.Value.
func asStarlarkValue(value interface{}) (starlark.Value, error) {
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.Slice:
length := v.Len()
array := make([]starlark.Value, 0, length)
for i := 0; i < length; i++ {
sVal, err := asStarlarkValue(v.Index(i).Interface())
if err != nil {
return starlark.None, err
}
array = append(array, sVal)
}
return starlark.NewList(array), nil
case reflect.Map:
dict := starlark.NewDict(v.Len())
iter := v.MapRange()
for iter.Next() {
sKey, err := asStarlarkValue(iter.Key().Interface())
if err != nil {
return starlark.None, err
}
sValue, err := asStarlarkValue(iter.Value().Interface())
if err != nil {
return starlark.None, err
}
if err := dict.SetKey(sKey, sValue); err != nil {
return starlark.None, err
}
}
return dict, nil
case reflect.Float32, reflect.Float64:
return starlark.Float(v.Float()), nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return starlark.MakeInt64(v.Int()), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return starlark.MakeUint64(v.Uint()), nil
case reflect.String:
return starlark.String(v.String()), nil
case reflect.Bool:
return starlark.Bool(v.Bool()), nil
}
return nil, fmt.Errorf("invalid type %T", value)
}
// AsGoValue converts a starlark.Value to a field value.
func asGoValue(value interface{}) (interface{}, error) {
switch v := value.(type) {
case starlark.Float:
return float64(v), nil
case starlark.Int:
n, ok := v.Int64()
if !ok {
return nil, fmt.Errorf("cannot represent integer %v as int64", v)
}
return n, nil
case starlark.String:
return string(v), nil
case starlark.Bool:
return bool(v), nil
}
return nil, fmt.Errorf("invalid starlark type %T", value)
}
// ToFields converts a starlark.Value to a map of values.
func toFields(value starlark.Value) (map[string]interface{}, error) {
if value == nil {
return nil, nil
}
items, err := items(value, "The type %T is unsupported as type of collection of fields")
if err != nil {
return nil, err
}
result := make(map[string]interface{}, len(items))
for _, item := range items {
key, err := toString(item[0], "The type %T is unsupported as type of key for fields")
if err != nil {
return nil, err
}
value, err := asGoValue(item[1])
if err != nil {
return nil, err
}
result[key] = value
}
return result, nil
}

View file

@ -0,0 +1,48 @@
package starlark
import (
"errors"
"fmt"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
"github.com/influxdata/telegraf"
)
// Builds a module that defines all the supported logging functions which will log using the provided logger
func LogModule(logger telegraf.Logger) *starlarkstruct.Module {
var logFunc = func(_ *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
return log(b, args, kwargs, logger)
}
return &starlarkstruct.Module{
Name: "log",
Members: starlark.StringDict{
"debug": starlark.NewBuiltin("log.debug", logFunc),
"info": starlark.NewBuiltin("log.info", logFunc),
"warn": starlark.NewBuiltin("log.warn", logFunc),
"error": starlark.NewBuiltin("log.error", logFunc),
},
}
}
// Logs the provided message according to the level chosen
func log(b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple, logger telegraf.Logger) (starlark.Value, error) {
var msg starlark.String
if err := starlark.UnpackPositionalArgs(b.Name(), args, kwargs, 1, &msg); err != nil {
return starlark.None, fmt.Errorf("%s: %w", b.Name(), err)
}
switch b.Name() {
case "log.debug":
logger.Debug(string(msg))
case "log.info":
logger.Info(string(msg))
case "log.warn":
logger.Warn(string(msg))
case "log.error":
logger.Error(string(msg))
default:
return nil, errors.New("method " + b.Name() + " is unknown")
}
return starlark.None, nil
}

View file

@ -0,0 +1,156 @@
package starlark
import (
"errors"
"fmt"
"strings"
"time"
"go.starlark.net/starlark"
"github.com/influxdata/telegraf"
)
type Metric struct {
ID telegraf.TrackingID
metric telegraf.Metric
tagIterCount int
fieldIterCount int
frozen bool
}
// Wrap updates the starlark.Metric to wrap a new telegraf.Metric.
func (m *Metric) Wrap(metric telegraf.Metric) {
if tm, ok := metric.(telegraf.TrackingMetric); ok {
m.ID = tm.TrackingID()
}
m.metric = metric
m.tagIterCount = 0
m.fieldIterCount = 0
m.frozen = false
}
// Unwrap removes the telegraf.Metric from the startlark.Metric.
func (m *Metric) Unwrap() telegraf.Metric {
return m.metric
}
// String returns the starlark representation of the Metric.
//
// The String function is called by both the repr() and str() functions, and so
// it behaves more like the repr function would in Python.
func (m *Metric) String() string {
buf := new(strings.Builder)
buf.WriteString("Metric(")
buf.WriteString(m.Name().String())
buf.WriteString(", tags=")
buf.WriteString(m.Tags().String())
buf.WriteString(", fields=")
buf.WriteString(m.Fields().String())
buf.WriteString(", time=")
buf.WriteString(m.Time().String())
buf.WriteString(")")
if m.ID != 0 {
fmt.Fprintf(buf, "[tracking ID=%v]", m.ID)
}
return buf.String()
}
func (*Metric) Type() string {
return "Metric"
}
func (m *Metric) Freeze() {
m.frozen = true
}
func (*Metric) Truth() starlark.Bool {
return true
}
func (*Metric) Hash() (uint32, error) {
return 0, errors.New("not hashable")
}
// AttrNames implements the starlark.HasAttrs interface.
func (*Metric) AttrNames() []string {
return []string{"name", "tags", "fields", "time"}
}
// Attr implements the starlark.HasAttrs interface.
func (m *Metric) Attr(name string) (starlark.Value, error) {
switch name {
case "name":
return m.Name(), nil
case "tags":
return m.Tags(), nil
case "fields":
return m.Fields(), nil
case "time":
return m.Time(), nil
default:
// Returning nil, nil indicates "no such field or method"
return nil, nil
}
}
// SetField implements the starlark.HasSetField interface.
func (m *Metric) SetField(name string, value starlark.Value) error {
if m.frozen {
return errors.New("cannot modify frozen metric")
}
switch name {
case "name":
return m.SetName(value)
case "time":
return m.SetTime(value)
case "tags":
return errors.New("cannot set tags")
case "fields":
return errors.New("cannot set fields")
default:
return starlark.NoSuchAttrError(
fmt.Sprintf("cannot assign to field %q", name))
}
}
func (m *Metric) Name() starlark.String {
return starlark.String(m.metric.Name())
}
func (m *Metric) SetName(value starlark.Value) error {
if str, ok := value.(starlark.String); ok {
m.metric.SetName(str.GoString())
return nil
}
return errors.New("type error")
}
func (m *Metric) Tags() TagDict {
return TagDict{m}
}
func (m *Metric) Fields() FieldDict {
return FieldDict{m}
}
func (m *Metric) Time() starlark.Int {
return starlark.MakeInt64(m.metric.Time().UnixNano())
}
func (m *Metric) SetTime(value starlark.Value) error {
switch v := value.(type) {
case starlark.Int:
ns, ok := v.Int64()
if !ok {
return errors.New("type error: unrepresentable time")
}
tm := time.Unix(0, ns)
m.metric.SetTime(tm)
return nil
default:
return errors.New("type error")
}
}

View file

@ -0,0 +1,282 @@
package starlark
import (
"bytes"
"encoding/gob"
"errors"
"fmt"
"strings"
"go.starlark.net/lib/json"
"go.starlark.net/lib/math"
"go.starlark.net/lib/time"
"go.starlark.net/starlark"
"go.starlark.net/syntax"
"github.com/influxdata/telegraf"
)
type Common struct {
Source string `toml:"source"`
Script string `toml:"script"`
Constants map[string]interface{} `toml:"constants"`
Log telegraf.Logger `toml:"-"`
StarlarkLoadFunc func(module string, logger telegraf.Logger) (starlark.StringDict, error)
thread *starlark.Thread
builtins starlark.StringDict
globals starlark.StringDict
functions map[string]*starlark.Function
parameters map[string]starlark.Tuple
state *starlark.Dict
}
func (s *Common) GetState() interface{} {
// Return the actual byte-type instead of nil allowing the persister
// to guess instantiate variable of the appropriate type
if s.state == nil {
return make([]byte, 0)
}
// Convert the starlark dict into a golang dictionary for serialization
state := make(map[string]interface{}, s.state.Len())
items := s.state.Items()
for _, item := range items {
if len(item) != 2 {
// We do expect key-value pairs in the state so there should be
// two items.
s.Log.Errorf("state item %+v does not contain a key-value pair", item)
continue
}
k, ok := item.Index(0).(starlark.String)
if !ok {
s.Log.Errorf("state item %+v has invalid key type %T", item, item.Index(0))
continue
}
v, err := asGoValue(item.Index(1))
if err != nil {
s.Log.Errorf("state item %+v value cannot be converted: %v", item, err)
continue
}
state[k.GoString()] = v
}
// Do a binary GOB encoding to preserve types
var buf bytes.Buffer
if err := gob.NewEncoder(&buf).Encode(state); err != nil {
s.Log.Errorf("encoding state failed: %v", err)
return make([]byte, 0)
}
return buf.Bytes()
}
func (s *Common) SetState(state interface{}) error {
data, ok := state.([]byte)
if !ok {
return fmt.Errorf("unexpected type %T for state", state)
}
if len(data) == 0 {
return nil
}
// Decode the binary GOB encoding
var dict map[string]interface{}
if err := gob.NewDecoder(bytes.NewBuffer(data)).Decode(&dict); err != nil {
return fmt.Errorf("decoding state failed: %w", err)
}
// Convert the golang dict back to starlark types
s.state = starlark.NewDict(len(dict))
for k, v := range dict {
sv, err := asStarlarkValue(v)
if err != nil {
return fmt.Errorf("value %v of state item %q cannot be set: %w", v, k, err)
}
if err := s.state.SetKey(starlark.String(k), sv); err != nil {
return fmt.Errorf("state item %q cannot be set: %w", k, err)
}
}
s.builtins["state"] = s.state
return s.InitProgram()
}
func (s *Common) Init() error {
if s.Source == "" && s.Script == "" {
return errors.New("one of source or script must be set")
}
if s.Source != "" && s.Script != "" {
return errors.New("both source or script cannot be set")
}
s.builtins = starlark.StringDict{}
s.builtins["Metric"] = starlark.NewBuiltin("Metric", newMetric)
s.builtins["deepcopy"] = starlark.NewBuiltin("deepcopy", deepcopy)
s.builtins["catch"] = starlark.NewBuiltin("catch", catch)
if err := s.addConstants(&s.builtins); err != nil {
return err
}
// Initialize the program
if err := s.InitProgram(); err != nil {
// Try again with a declared state. This might be necessary for
// state persistence.
s.state = starlark.NewDict(0)
s.builtins["state"] = s.state
if serr := s.InitProgram(); serr != nil {
return err
}
}
s.functions = make(map[string]*starlark.Function)
s.parameters = make(map[string]starlark.Tuple)
return nil
}
func (s *Common) InitProgram() error {
// Load the program. In case of an error we can try to insert the state
// which can be used implicitly e.g. when persisting states
program, err := s.sourceProgram(s.builtins)
if err != nil {
return err
}
// Execute source
s.thread = &starlark.Thread{
Print: func(_ *starlark.Thread, msg string) { s.Log.Debug(msg) },
Load: func(_ *starlark.Thread, module string) (starlark.StringDict, error) {
return s.StarlarkLoadFunc(module, s.Log)
},
}
globals, err := program.Init(s.thread, s.builtins)
if err != nil {
return err
}
// In case the program declares a global "state" we should insert it to
// avoid warnings about inserting into a frozen variable
if _, found := globals["state"]; found {
globals["state"] = starlark.NewDict(0)
}
// Freeze the global state. This prevents modifications to the processor
// state and prevents scripts from containing errors storing tracking
// metrics. Tasks that require global state will not be possible due to
// this, so maybe we should relax this in the future.
globals.Freeze()
s.globals = globals
return nil
}
func (s *Common) GetParameters(name string) (starlark.Tuple, bool) {
parameters, found := s.parameters[name]
return parameters, found
}
func (s *Common) AddFunction(name string, params ...starlark.Value) error {
globalFn, found := s.globals[name]
if !found {
return fmt.Errorf("%s is not defined", name)
}
fn, found := globalFn.(*starlark.Function)
if !found {
return fmt.Errorf("%s is not a function", name)
}
if fn.NumParams() != len(params) {
return fmt.Errorf("%s function must take %d parameter(s)", name, len(params))
}
p := make(starlark.Tuple, len(params))
copy(p, params)
s.functions[name] = fn
s.parameters[name] = params
return nil
}
// Add all the constants defined in the plugin as constants of the script
func (s *Common) addConstants(builtins *starlark.StringDict) error {
for key, val := range s.Constants {
if key == "state" {
return errors.New("'state' constant uses reserved name")
}
sVal, err := asStarlarkValue(val)
if err != nil {
return fmt.Errorf("converting type %T failed: %w", val, err)
}
(*builtins)[key] = sVal
}
return nil
}
func (s *Common) sourceProgram(builtins starlark.StringDict) (*starlark.Program, error) {
var src interface{}
if s.Source != "" {
src = s.Source
}
// AllowFloat - obsolete, no effect
// AllowNestedDef - always on https://github.com/google/starlark-go/pull/328
// AllowLambda - always on https://github.com/google/starlark-go/pull/328
options := syntax.FileOptions{
Recursion: true,
GlobalReassign: true,
Set: true,
}
_, program, err := starlark.SourceProgramOptions(&options, s.Script, src, builtins.Has)
return program, err
}
// Call calls the function corresponding to the given name.
func (s *Common) Call(name string) (starlark.Value, error) {
fn, ok := s.functions[name]
if !ok {
return nil, fmt.Errorf("function %q does not exist", name)
}
args, ok := s.parameters[name]
if !ok {
return nil, fmt.Errorf("params for function %q do not exist", name)
}
return starlark.Call(s.thread, fn, args, nil)
}
func (s *Common) LogError(err error) {
var evalErr *starlark.EvalError
if errors.As(err, &evalErr) {
for _, line := range strings.Split(evalErr.Backtrace(), "\n") {
s.Log.Error(line)
}
} else {
s.Log.Error(err)
}
}
func LoadFunc(module string, logger telegraf.Logger) (starlark.StringDict, error) {
switch module {
case "json.star":
return starlark.StringDict{
"json": json.Module,
}, nil
case "logging.star":
return starlark.StringDict{
"log": LogModule(logger),
}, nil
case "math.star":
return starlark.StringDict{
"math": math.Module,
}, nil
case "time.star":
return starlark.StringDict{
"time": time.Module,
}, nil
default:
return nil, errors.New("module " + module + " is not available")
}
}

View file

@ -0,0 +1,226 @@
package starlark
import (
"errors"
"strings"
"go.starlark.net/starlark"
"github.com/influxdata/telegraf"
)
// TagDict is a starlark.Value for the metric tags. It is heavily based on the
// starlark.Dict.
type TagDict struct {
*Metric
}
func (d TagDict) String() string {
buf := new(strings.Builder)
buf.WriteString("{")
sep := ""
for _, item := range d.Items() {
k, v := item[0], item[1]
buf.WriteString(sep)
buf.WriteString(k.String())
buf.WriteString(": ")
buf.WriteString(v.String())
sep = ", "
}
buf.WriteString("}")
return buf.String()
}
func (TagDict) Type() string {
return "Tags"
}
func (d TagDict) Freeze() {
// Disable linter check as the frozen variable is modified despite
// passing a value instead of a pointer, because `TagDict` holds
// a pointer to the underlying metric containing the `frozen` field.
//revive:disable:modifies-value-receiver
d.frozen = true
}
func (d TagDict) Truth() starlark.Bool {
return len(d.metric.TagList()) != 0
}
func (TagDict) Hash() (uint32, error) {
return 0, errors.New("not hashable")
}
// AttrNames implements the starlark.HasAttrs interface.
func (TagDict) AttrNames() []string {
return builtinAttrNames(TagDictMethods)
}
// Attr implements the starlark.HasAttrs interface.
func (d TagDict) Attr(name string) (starlark.Value, error) {
return builtinAttr(d, name, TagDictMethods)
}
var TagDictMethods = map[string]builtinMethod{
"clear": dictClear,
"get": dictGet,
"items": dictItems,
"keys": dictKeys,
"pop": dictPop,
"popitem": dictPopitem,
"setdefault": dictSetdefault,
"update": dictUpdate,
"values": dictValues,
}
// Get implements the starlark.Mapping interface.
func (d TagDict) Get(key starlark.Value) (v starlark.Value, found bool, err error) {
if k, ok := key.(starlark.String); ok {
gv, found := d.metric.GetTag(k.GoString())
if !found {
return starlark.None, false, nil
}
return starlark.String(gv), true, err
}
return starlark.None, false, errors.New("key must be of type 'str'")
}
// SetKey implements the starlark.HasSetKey interface to support map update
// using x[k]=v syntax, like a dictionary.
func (d TagDict) SetKey(k, v starlark.Value) error {
if d.tagIterCount > 0 {
return errors.New("cannot insert during iteration")
}
key, ok := k.(starlark.String)
if !ok {
return errors.New("tag key must be of type 'str'")
}
value, ok := v.(starlark.String)
if !ok {
return errors.New("tag value must be of type 'str'")
}
d.metric.AddTag(key.GoString(), value.GoString())
return nil
}
// Items implements the starlark.IterableMapping interface.
func (d TagDict) Items() []starlark.Tuple {
items := make([]starlark.Tuple, 0, len(d.metric.TagList()))
for _, tag := range d.metric.TagList() {
key := starlark.String(tag.Key)
value := starlark.String(tag.Value)
pair := starlark.Tuple{key, value}
items = append(items, pair)
}
return items
}
func (d TagDict) Clear() error {
if d.tagIterCount > 0 {
return errors.New("cannot delete during iteration")
}
keys := make([]string, 0, len(d.metric.TagList()))
for _, tag := range d.metric.TagList() {
keys = append(keys, tag.Key)
}
for _, key := range keys {
d.metric.RemoveTag(key)
}
return nil
}
func (d TagDict) PopItem() (v starlark.Value, err error) {
if d.tagIterCount > 0 {
return nil, errors.New("cannot delete during iteration")
}
for _, tag := range d.metric.TagList() {
k := tag.Key
v := tag.Value
d.metric.RemoveTag(k)
sk := starlark.String(k)
sv := starlark.String(v)
return starlark.Tuple{sk, sv}, nil
}
return nil, errors.New("popitem(): tag dictionary is empty")
}
func (d TagDict) Delete(k starlark.Value) (v starlark.Value, found bool, err error) {
if d.tagIterCount > 0 {
return nil, false, errors.New("cannot delete during iteration")
}
if key, ok := k.(starlark.String); ok {
value, ok := d.metric.GetTag(key.GoString())
if ok {
d.metric.RemoveTag(key.GoString())
v := starlark.String(value)
return v, ok, err
}
return starlark.None, false, nil
}
return starlark.None, false, errors.New("key must be of type 'str'")
}
// Iterate implements the starlark.Iterator interface.
func (d TagDict) Iterate() starlark.Iterator {
d.tagIterCount++
return &TagIterator{Metric: d.Metric, tags: d.metric.TagList()}
}
type TagIterator struct {
*Metric
tags []*telegraf.Tag
}
// Next implements the starlark.Iterator interface.
func (i *TagIterator) Next(p *starlark.Value) bool {
if len(i.tags) == 0 {
return false
}
tag := i.tags[0]
i.tags = i.tags[1:]
*p = starlark.String(tag.Key)
return true
}
// Done implements the starlark.Iterator interface.
func (i *TagIterator) Done() {
i.tagIterCount--
}
// ToTags converts a starlark.Value to a map of string.
func toTags(value starlark.Value) (map[string]string, error) {
if value == nil {
return nil, nil
}
items, err := items(value, "The type %T is unsupported as type of collection of tags")
if err != nil {
return nil, err
}
result := make(map[string]string, len(items))
for _, item := range items {
key, err := toString(item[0], "The type %T is unsupported as type of key for tags")
if err != nil {
return nil, err
}
value, err := toString(item[1], "The type %T is unsupported as type of value for tags")
if err != nil {
return nil, err
}
result[key] = value
}
return result, nil
}