lang: core, funcs: Port some functions to CallableFunc API
Some modern features of our function engine and language might require this new API, so port what we can and figure out the rest later.
This commit is contained in:
@@ -102,12 +102,22 @@ func (obj *DateTimeFact) Stream(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := obj.Call(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.IntValue{ // seconds since 1970...
|
||||
V: time.Now().Unix(), // .UTC() not necessary
|
||||
}:
|
||||
case obj.init.Output <- result:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this fact and return the value if it is possible to do so at this time.
|
||||
func (obj *DateTimeFact) Call(ctx context.Context) (types.Value, error) {
|
||||
return &types.IntValue{ // seconds since 1970...
|
||||
V: time.Now().Unix(), // .UTC() not necessary
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -60,8 +60,9 @@ type AbsPathFunc struct {
|
||||
data *interfaces.FuncData
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
path *string // the active path
|
||||
result *string // last calculated output
|
||||
args []types.Value
|
||||
path *string // the active path
|
||||
result types.Value // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
@@ -128,43 +129,64 @@ func (obj *AbsPathFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
path := input.Struct()[absPathArgNamePath].Str()
|
||||
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.args = args
|
||||
|
||||
path := args[0].Str()
|
||||
// TODO: add validation for absolute path?
|
||||
if obj.path != nil && *obj.path == path {
|
||||
continue // nothing changed
|
||||
}
|
||||
obj.path = &path
|
||||
|
||||
p := strings.TrimSuffix(obj.data.Base, "/")
|
||||
if p == obj.data.Base { // didn't trim, so we fail
|
||||
// programming error
|
||||
return fmt.Errorf("no trailing slash on Base, got: `%s`", p)
|
||||
result, err := obj.Call(ctx, obj.args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result := p
|
||||
|
||||
if *obj.path == "" {
|
||||
result += "/" // add the above trailing slash back
|
||||
} else if !strings.HasPrefix(*obj.path, "/") {
|
||||
return fmt.Errorf("path was not absolute, got: `%s`", *obj.path)
|
||||
//result += "/" // be forgiving ?
|
||||
}
|
||||
result += *obj.path
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
// if the result is still the same, skip sending an update...
|
||||
if obj.result != nil && result.Cmp(obj.result) == nil {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
obj.result = result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *AbsPathFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
path := args[0].Str()
|
||||
|
||||
p := strings.TrimSuffix(obj.data.Base, "/")
|
||||
if p == obj.data.Base { // didn't trim, so we fail
|
||||
// programming error
|
||||
return nil, fmt.Errorf("no trailing slash on Base, got: `%s`", p)
|
||||
}
|
||||
result := p
|
||||
|
||||
if path == "" {
|
||||
result += "/" // add the above trailing slash back
|
||||
} else if !strings.HasPrefix(path, "/") {
|
||||
return nil, fmt.Errorf("path was not absolute, got: `%s`", path)
|
||||
//result += "/" // be forgiving ?
|
||||
}
|
||||
result += path
|
||||
|
||||
return &types.StrValue{
|
||||
V: result,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -61,8 +61,9 @@ type ReadFileFunc struct {
|
||||
data *interfaces.FuncData
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
filename *string // the active filename
|
||||
result *string // last calculated output
|
||||
args []types.Value
|
||||
filename *string // the active filename
|
||||
result types.Value // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
@@ -129,7 +130,13 @@ func (obj *ReadFileFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
filename := input.Struct()[readFileArgNameFilename].Str()
|
||||
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.args = args
|
||||
|
||||
filename := args[0].Str()
|
||||
// TODO: add validation for absolute path?
|
||||
// TODO: add check for empty string
|
||||
if obj.filename != nil && *obj.filename == filename {
|
||||
@@ -137,48 +144,61 @@ func (obj *ReadFileFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.filename = &filename
|
||||
|
||||
p := strings.TrimSuffix(obj.data.Base, "/")
|
||||
if p == obj.data.Base { // didn't trim, so we fail
|
||||
// programming error
|
||||
return fmt.Errorf("no trailing slash on Base, got: `%s`", p)
|
||||
}
|
||||
path := p
|
||||
|
||||
if !strings.HasPrefix(*obj.filename, "/") {
|
||||
return fmt.Errorf("filename was not absolute, got: `%s`", *obj.filename)
|
||||
//path += "/" // be forgiving ?
|
||||
}
|
||||
path += *obj.filename
|
||||
|
||||
fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system
|
||||
result, err := obj.Call(ctx, obj.args)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI)
|
||||
}
|
||||
// this is relative to the module dir the func is in!
|
||||
content, err := fs.ReadFile(path) // open the remote file system
|
||||
// We could use it directly, but it feels like less correct.
|
||||
//content, err := obj.data.Fs.ReadFile(path) // open the remote file system
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "can't read file `%s` (%s)", *obj.filename, path)
|
||||
return err
|
||||
}
|
||||
|
||||
result := string(content) // convert to string
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
// if the result is still the same, skip sending an update...
|
||||
if obj.result != nil && result.Cmp(obj.result) == nil {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
obj.result = result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
filename := args[0].Str()
|
||||
|
||||
p := strings.TrimSuffix(obj.data.Base, "/")
|
||||
if p == obj.data.Base { // didn't trim, so we fail
|
||||
// programming error
|
||||
return nil, fmt.Errorf("no trailing slash on Base, got: `%s`", p)
|
||||
}
|
||||
path := p
|
||||
|
||||
if !strings.HasPrefix(filename, "/") {
|
||||
return nil, fmt.Errorf("filename was not absolute, got: `%s`", filename)
|
||||
//path += "/" // be forgiving ?
|
||||
}
|
||||
path += filename
|
||||
|
||||
fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI)
|
||||
}
|
||||
// this is relative to the module dir the func is in!
|
||||
content, err := fs.ReadFile(path) // open the remote file system
|
||||
// We could use it directly, but it feels like less correct.
|
||||
//content, err := obj.data.Fs.ReadFile(path) // open the remote file system
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "can't read file `%s` (%s)", filename, path)
|
||||
}
|
||||
|
||||
return &types.StrValue{
|
||||
V: string(content), // convert to string
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -61,8 +61,9 @@ type ReadFileAbsFunc struct {
|
||||
data *interfaces.FuncData
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
filename *string // the active filename
|
||||
result *string // last calculated output
|
||||
args []types.Value
|
||||
filename *string // the active filename
|
||||
result types.Value // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
@@ -129,7 +130,13 @@ func (obj *ReadFileAbsFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
filename := input.Struct()[readfileArgNameFilename].Str()
|
||||
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.args = args
|
||||
|
||||
filename := args[0].Str()
|
||||
// TODO: add validation for absolute path?
|
||||
// TODO: add check for empty string
|
||||
if obj.filename != nil && *obj.filename == filename {
|
||||
@@ -137,34 +144,47 @@ func (obj *ReadFileAbsFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.filename = &filename
|
||||
|
||||
fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system
|
||||
result, err := obj.Call(ctx, obj.args)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI)
|
||||
}
|
||||
content, err := fs.ReadFile(*obj.filename) // open the remote file system
|
||||
// We could use it directly, but it feels like less correct.
|
||||
//content, err := obj.data.Fs.ReadFile(*obj.filename) // open the remote file system
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "can't read file `%s`", *obj.filename)
|
||||
return err
|
||||
}
|
||||
|
||||
result := string(content) // convert to string
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
// if the result is still the same, skip sending an update...
|
||||
if obj.result != nil && result.Cmp(obj.result) == nil {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
obj.result = result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *ReadFileAbsFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
filename := args[0].Str()
|
||||
|
||||
fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "can't load code from file system `%s`", obj.data.FsURI)
|
||||
}
|
||||
content, err := fs.ReadFile(filename) // open the remote file system
|
||||
// We could use it directly, but it feels like less correct.
|
||||
//content, err := obj.data.Fs.ReadFile(filename) // open the remote file system
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "can't read file `%s`", filename)
|
||||
}
|
||||
|
||||
return &types.StrValue{
|
||||
V: string(content), // convert to string
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ package coreexample
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
@@ -52,6 +53,7 @@ func init() {
|
||||
// function which you could specify an interval for.
|
||||
type FlipFlopFact struct {
|
||||
init *facts.Init
|
||||
mutex *sync.Mutex
|
||||
value bool
|
||||
}
|
||||
|
||||
@@ -77,6 +79,7 @@ func (obj *FlipFlopFact) Info() *facts.Info {
|
||||
// Init runs some startup code for this fact.
|
||||
func (obj *FlipFlopFact) Init(init *facts.Init) error {
|
||||
obj.init = init
|
||||
obj.mutex = &sync.Mutex{}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -100,14 +103,30 @@ func (obj *FlipFlopFact) Stream(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := obj.Call(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj.mutex.Lock()
|
||||
obj.value = !obj.value // flip it
|
||||
obj.mutex.Unlock()
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.BoolValue{ // flip
|
||||
V: obj.value,
|
||||
}:
|
||||
case obj.init.Output <- result:
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
obj.value = !obj.value // flip it
|
||||
}
|
||||
}
|
||||
|
||||
// Call this fact and return the value if it is possible to do so at this time.
|
||||
func (obj *FlipFlopFact) Call(ctx context.Context) (types.Value, error) {
|
||||
obj.mutex.Lock() // TODO: could be a read lock
|
||||
value := obj.value
|
||||
obj.mutex.Unlock()
|
||||
return &types.BoolValue{
|
||||
V: value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -66,11 +66,8 @@ type VUMeterFunc struct {
|
||||
init *interfaces.Init
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
symbol string
|
||||
multiplier int64
|
||||
peak float64
|
||||
|
||||
result *string // last calculated output
|
||||
args []types.Value
|
||||
result types.Value // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
@@ -172,9 +169,12 @@ func (obj *VUMeterFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
obj.symbol = input.Struct()[vuMeterArgNameSymbol].Str()
|
||||
obj.multiplier = input.Struct()[vuMeterArgNameMultiplier].Int()
|
||||
obj.peak = input.Struct()[vuMeterArgNamePeak].Float()
|
||||
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.args = args
|
||||
|
||||
once.Do(onceFunc)
|
||||
continue // we must wrap around and go in through goChan
|
||||
|
||||
@@ -185,66 +185,83 @@ func (obj *VUMeterFunc) Stream(ctx context.Context) error {
|
||||
continue // still waiting for input values
|
||||
}
|
||||
|
||||
// record for one second to a shared memory file
|
||||
// rec /dev/shm/mgmt_rec.wav trim 0 1 2>/dev/null
|
||||
args1 := []string{"/dev/shm/mgmt_rec.wav", "trim", "0", "1"}
|
||||
cmd1 := exec.Command("/usr/bin/rec", args1...)
|
||||
// XXX: arecord stopped working on newer linux...
|
||||
// arecord -d 1 /dev/shm/mgmt_rec.wav 2>/dev/null
|
||||
//args1 := []string{"-d", "1", "/dev/shm/mgmt_rec.wav"}
|
||||
//cmd1 := exec.Command("/usr/bin/arecord", args1...)
|
||||
cmd1.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
Pgid: 0,
|
||||
}
|
||||
// start the command
|
||||
if _, err := cmd1.Output(); err != nil {
|
||||
return errwrap.Wrapf(err, "cmd failed to run")
|
||||
}
|
||||
|
||||
// sox -t .wav /dev/shm/mgmt_rec.wav -n stat 2>&1 | grep "Maximum amplitude" | cut -d ':' -f 2
|
||||
args2 := []string{"-t", ".wav", "/dev/shm/mgmt_rec.wav", "-n", "stat"}
|
||||
cmd2 := exec.Command("/usr/bin/sox", args2...)
|
||||
cmd2.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
Pgid: 0,
|
||||
}
|
||||
|
||||
// start the command
|
||||
out, err := cmd2.CombinedOutput() // data comes on stderr
|
||||
result, err := obj.Call(ctx, obj.args)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "cmd failed to run")
|
||||
return err
|
||||
}
|
||||
|
||||
ratio, err := extract(out)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "failed to extract")
|
||||
}
|
||||
|
||||
result, err := visual(obj.symbol, int(obj.multiplier), obj.peak, ratio)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not generate visual")
|
||||
}
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
// if the result is still the same, skip sending an update...
|
||||
if obj.result != nil && result.Cmp(obj.result) == nil {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
obj.result = result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *VUMeterFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
symbol := args[0].Str()
|
||||
multiplier := args[1].Int()
|
||||
peak := args[2].Float()
|
||||
|
||||
// record for one second to a shared memory file
|
||||
// rec /dev/shm/mgmt_rec.wav trim 0 1 2>/dev/null
|
||||
args1 := []string{"/dev/shm/mgmt_rec.wav", "trim", "0", "1"}
|
||||
cmd1 := exec.CommandContext(ctx, "/usr/bin/rec", args1...)
|
||||
// XXX: arecord stopped working on newer linux...
|
||||
// arecord -d 1 /dev/shm/mgmt_rec.wav 2>/dev/null
|
||||
//args1 := []string{"-d", "1", "/dev/shm/mgmt_rec.wav"}
|
||||
//cmd1 := exec.CommandContext(ctx, "/usr/bin/arecord", args1...)
|
||||
cmd1.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
Pgid: 0,
|
||||
}
|
||||
// start the command
|
||||
if _, err := cmd1.Output(); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "cmd failed to run")
|
||||
}
|
||||
|
||||
// sox -t .wav /dev/shm/mgmt_rec.wav -n stat 2>&1 | grep "Maximum amplitude" | cut -d ':' -f 2
|
||||
args2 := []string{"-t", ".wav", "/dev/shm/mgmt_rec.wav", "-n", "stat"}
|
||||
cmd2 := exec.CommandContext(ctx, "/usr/bin/sox", args2...)
|
||||
cmd2.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
Pgid: 0,
|
||||
}
|
||||
|
||||
// start the command
|
||||
out, err := cmd2.CombinedOutput() // data comes on stderr
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "cmd failed to run")
|
||||
}
|
||||
|
||||
ratio, err := extract(out)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "failed to extract")
|
||||
}
|
||||
|
||||
result, err := visual(symbol, int(multiplier), peak, ratio)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not generate visual")
|
||||
}
|
||||
|
||||
return &types.StrValue{
|
||||
V: result,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newTicker() *time.Ticker {
|
||||
return time.NewTicker(time.Duration(1) * time.Second)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ type PrintfFunc struct {
|
||||
init *interfaces.Init
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
result *string // last calculated output
|
||||
result types.Value // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
@@ -325,40 +325,57 @@ func (obj *PrintfFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
format := input.Struct()[printfArgNameFormat].Str()
|
||||
values := []types.Value{}
|
||||
for _, name := range obj.Type.Ord {
|
||||
if name == printfArgNameFormat { // skip format arg
|
||||
continue
|
||||
}
|
||||
x := input.Struct()[name]
|
||||
values = append(values, x)
|
||||
}
|
||||
|
||||
result, err := compileFormatToString(format, values)
|
||||
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
|
||||
if err != nil {
|
||||
return err // no errwrap needed b/c helper func
|
||||
return err
|
||||
}
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
result, err := obj.Call(ctx, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if the result is still the same, skip sending an update...
|
||||
if obj.result != nil && result.Cmp(obj.result) == nil {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
obj.result = result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *PrintfFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
format := args[0].Str()
|
||||
|
||||
values := []types.Value{}
|
||||
for i, x := range args {
|
||||
if i == 0 { // skip format arg
|
||||
continue
|
||||
}
|
||||
values = append(values, x)
|
||||
}
|
||||
|
||||
result, err := compileFormatToString(format, values)
|
||||
if err != nil {
|
||||
return nil, err // no errwrap needed b/c helper func
|
||||
}
|
||||
return &types.StrValue{
|
||||
V: result,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// valueToString prints our values how we expect for printf.
|
||||
// FIXME: if this turns out to be useful, add it to the types package.
|
||||
func valueToString(value types.Value) string {
|
||||
|
||||
@@ -86,7 +86,7 @@ type TemplateFunc struct {
|
||||
init *interfaces.Init
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
result *string // last calculated output
|
||||
result types.Value // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
@@ -364,38 +364,54 @@ func (obj *TemplateFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
st := input.Struct()
|
||||
|
||||
tmpl := st[templateArgNameTemplate].Str()
|
||||
vars, exists := st[templateArgNameVars]
|
||||
if !exists {
|
||||
vars = nil
|
||||
}
|
||||
|
||||
result, err := obj.run(ctx, tmpl, vars)
|
||||
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
|
||||
if err != nil {
|
||||
return err // no errwrap needed b/c helper func
|
||||
return err
|
||||
}
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
result, err := obj.Call(ctx, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if the result is still the same, skip sending an update...
|
||||
if obj.result != nil && result.Cmp(obj.result) == nil {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
obj.result = result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *TemplateFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
tmpl := args[0].Str()
|
||||
|
||||
var vars types.Value // nil
|
||||
if len(args) == 2 {
|
||||
vars = args[1]
|
||||
}
|
||||
|
||||
result, err := obj.run(ctx, tmpl, vars)
|
||||
if err != nil {
|
||||
return nil, err // no errwrap needed b/c helper func
|
||||
}
|
||||
return &types.StrValue{
|
||||
V: result,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// safename renames the functions so they're valid inside the template. This is
|
||||
// a limitation of the template library, and it might be worth moving to a new
|
||||
// one.
|
||||
|
||||
@@ -179,6 +179,12 @@ func (obj *LookupFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
// programming error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := f.(interfaces.CallableFunc); !ok {
|
||||
// programming error
|
||||
return nil, fmt.Errorf("not a CallableFunc")
|
||||
}
|
||||
|
||||
bf, ok := f.(interfaces.BuildableFunc)
|
||||
if !ok {
|
||||
// programming error
|
||||
@@ -230,3 +236,13 @@ func (obj *LookupFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
return obj.fn.Stream(ctx)
|
||||
}
|
||||
|
||||
// Call returns the result of this function.
|
||||
func (obj *LookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
cf, ok := obj.fn.(interfaces.CallableFunc)
|
||||
if !ok {
|
||||
// programming error
|
||||
return nil, fmt.Errorf("not a CallableFunc")
|
||||
}
|
||||
return cf.Call(ctx, args)
|
||||
}
|
||||
|
||||
@@ -124,6 +124,12 @@ func (obj *LookupDefaultFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
// programming error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := f.(interfaces.CallableFunc); !ok {
|
||||
// programming error
|
||||
return nil, fmt.Errorf("not a CallableFunc")
|
||||
}
|
||||
|
||||
bf, ok := f.(interfaces.BuildableFunc)
|
||||
if !ok {
|
||||
// programming error
|
||||
@@ -175,3 +181,13 @@ func (obj *LookupDefaultFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
return obj.fn.Stream(ctx)
|
||||
}
|
||||
|
||||
// Call returns the result of this function.
|
||||
func (obj *LookupDefaultFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
cf, ok := obj.fn.(interfaces.CallableFunc)
|
||||
if !ok {
|
||||
// programming error
|
||||
return nil, fmt.Errorf("not a CallableFunc")
|
||||
}
|
||||
return cf.Call(ctx, args)
|
||||
}
|
||||
|
||||
@@ -62,12 +62,13 @@ type ReadFileFunc struct {
|
||||
init *interfaces.Init
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
filename *string // the active filename
|
||||
recWatcher *recwatch.RecWatcher
|
||||
events chan error // internal events
|
||||
wg *sync.WaitGroup
|
||||
|
||||
result *string // last calculated output
|
||||
args []types.Value
|
||||
filename *string // the active filename
|
||||
result types.Value // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
@@ -214,28 +215,49 @@ func (obj *ReadFileFunc) Stream(ctx context.Context) error {
|
||||
continue // still waiting for input values
|
||||
}
|
||||
|
||||
// read file...
|
||||
content, err := os.ReadFile(*obj.filename)
|
||||
args, err := interfaces.StructToCallableArgs(obj.last) // []types.Value, error)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "error reading file")
|
||||
return err
|
||||
}
|
||||
result := string(content) // convert to string
|
||||
obj.args = args
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
result, err := obj.Call(ctx, obj.args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if the result is still the same, skip sending an update...
|
||||
if obj.result != nil && result.Cmp(obj.result) == nil {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
obj.result = result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time.
|
||||
func (obj *ReadFileFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
filename := args[0].Str()
|
||||
|
||||
// read file...
|
||||
content, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "error reading file")
|
||||
}
|
||||
|
||||
return &types.StrValue{
|
||||
V: string(content), // convert to string
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -336,3 +336,27 @@ func (obj *StructLookupFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call returns the result of this function.
|
||||
func (obj *StructLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
st := args[0].(*types.StructValue)
|
||||
field := args[1].Str()
|
||||
|
||||
if field == "" {
|
||||
return nil, fmt.Errorf("received empty field")
|
||||
}
|
||||
// TODO: Is it a hack to grab this first value?
|
||||
if obj.field == "" {
|
||||
// This can happen at compile time too. Bonus!
|
||||
obj.field = field // store first field
|
||||
}
|
||||
if field != obj.field {
|
||||
return nil, fmt.Errorf("input field changed from: `%s`, to: `%s`", obj.field, field)
|
||||
}
|
||||
result, exists := st.Lookup(obj.field)
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("could not lookup field: `%s` in struct", field)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -342,3 +342,31 @@ func (obj *StructLookupOptionalFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call returns the result of this function.
|
||||
func (obj *StructLookupOptionalFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
st := args[0].(*types.StructValue)
|
||||
field := args[1].Str()
|
||||
optional := args[2]
|
||||
|
||||
if field == "" {
|
||||
return nil, fmt.Errorf("received empty field")
|
||||
}
|
||||
// TODO: Is it a hack to grab this first value?
|
||||
if obj.field == "" {
|
||||
// This can happen at compile time too. Bonus!
|
||||
obj.field = field // store first field
|
||||
}
|
||||
if field != obj.field {
|
||||
return nil, fmt.Errorf("input field changed from: `%s`, to: `%s`", obj.field, field)
|
||||
}
|
||||
|
||||
// We know the result of this lookup statically at compile time, but for
|
||||
// simplicity we check each time here anyways. Maybe one day there will
|
||||
// be a fancy reason why this might vary over time.
|
||||
val, exists := st.Lookup(obj.field)
|
||||
if !exists {
|
||||
return optional, nil
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@ func init() {
|
||||
|
||||
// CPUCountFact is a fact that returns the current CPU count.
|
||||
type CPUCountFact struct {
|
||||
init *facts.Init
|
||||
init *facts.Init
|
||||
result types.Value // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this fact. This is needed so this struct can
|
||||
@@ -109,8 +110,6 @@ func (obj CPUCountFact) Stream(ctx context.Context) error {
|
||||
closeChan := make(chan struct{}) // channel to unblock selects in goroutine
|
||||
defer close(closeChan)
|
||||
|
||||
var once bool // did we send at least once?
|
||||
|
||||
// wait for kernel to poke us about new device changes on the system
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
@@ -134,16 +133,10 @@ func (obj CPUCountFact) Stream(ctx context.Context) error {
|
||||
|
||||
startChan := make(chan struct{})
|
||||
close(startChan) // trigger the first event
|
||||
var cpuCount, newCount int64 = 0, -1
|
||||
for {
|
||||
select {
|
||||
case <-startChan:
|
||||
startChan = nil // disable
|
||||
newCount, err = getCPUCount()
|
||||
if err != nil {
|
||||
obj.init.Logf("Could not get initial CPU count. Setting to zero.")
|
||||
}
|
||||
// TODO: would we rather error instead of sending zero?
|
||||
|
||||
case event, ok := <-eventChan:
|
||||
if !ok {
|
||||
@@ -155,34 +148,46 @@ func (obj CPUCountFact) Stream(ctx context.Context) error {
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("received uevent SEQNUM: %s", event.uevent.Data["SEQNUM"])
|
||||
}
|
||||
if isCPUEvent(event.uevent) {
|
||||
newCount, err = getCPUCount()
|
||||
if err != nil {
|
||||
obj.init.Logf("could not getCPUCount: %e", err)
|
||||
continue
|
||||
}
|
||||
if !isCPUEvent(event.uevent) {
|
||||
continue
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
if once && newCount == cpuCount {
|
||||
continue
|
||||
result, err := obj.Call(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cpuCount = newCount
|
||||
|
||||
// if the result is still the same, skip sending an update...
|
||||
if obj.result != nil && result.Cmp(obj.result) == nil {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = result // store new result
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.IntValue{
|
||||
V: cpuCount,
|
||||
}:
|
||||
once = true
|
||||
// send
|
||||
case obj.init.Output <- result:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this fact and return the value if it is possible to do so at this time.
|
||||
func (obj *CPUCountFact) Call(ctx context.Context) (types.Value, error) {
|
||||
count, err := getCPUCount() // TODO: ctx?
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not get CPU count")
|
||||
}
|
||||
|
||||
return &types.IntValue{
|
||||
V: int64(count),
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// getCPUCount looks in sysfs to get the number of CPUs that are online.
|
||||
func getCPUCount() (int64, error) {
|
||||
dat, err := os.ReadFile("/sys/devices/system/cpu/online")
|
||||
|
||||
@@ -103,23 +103,32 @@ func (obj *LoadFact) Stream(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
x1, x5, x15, err := load()
|
||||
result, err := obj.Call(ctx)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not read load values")
|
||||
}
|
||||
|
||||
st := types.NewStruct(types.NewType(loadSignature))
|
||||
for k, v := range map[string]float64{"x1": x1, "x5": x5, "x15": x15} {
|
||||
if err := st.Set(k, &types.FloatValue{V: v}); err != nil {
|
||||
return errwrap.Wrapf(err, "struct could not set key: `%s`", k)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- st:
|
||||
// send
|
||||
case obj.init.Output <- result:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this fact and return the value if it is possible to do so at this time.
|
||||
func (obj *LoadFact) Call(ctx context.Context) (types.Value, error) {
|
||||
x1, x5, x15, err := load()
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not read load values")
|
||||
}
|
||||
|
||||
st := types.NewStruct(types.NewType(loadSignature))
|
||||
for k, v := range map[string]float64{"x1": x1, "x5": x5, "x15": x15} {
|
||||
if err := st.Set(k, &types.FloatValue{V: v}); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "struct could not set key: `%s`", k)
|
||||
}
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
@@ -90,16 +90,27 @@ func (obj *UptimeFact) Stream(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
uptime, err := uptime()
|
||||
result, err := obj.Call(ctx)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not read uptime value")
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.IntValue{V: uptime}:
|
||||
// send
|
||||
case obj.init.Output <- result:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this fact and return the value if it is possible to do so at this time.
|
||||
func (obj *UptimeFact) Call(ctx context.Context) (types.Value, error) {
|
||||
uptime, err := uptime() // TODO: add ctx?
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not read uptime value")
|
||||
}
|
||||
|
||||
return &types.IntValue{
|
||||
V: uptime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ package coretest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
@@ -50,6 +51,9 @@ func init() {
|
||||
// FastCountFact is a fact that counts up as fast as possible from zero forever.
|
||||
type FastCountFact struct {
|
||||
init *facts.Init
|
||||
|
||||
mutex *sync.Mutex
|
||||
count int
|
||||
}
|
||||
|
||||
// String returns a simple name for this fact. This is needed so this struct can
|
||||
@@ -74,6 +78,7 @@ func (obj *FastCountFact) Info() *facts.Info {
|
||||
// Init runs some startup code for this fact.
|
||||
func (obj *FastCountFact) Init(init *facts.Init) error {
|
||||
obj.init = init
|
||||
obj.mutex = &sync.Mutex{}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -81,16 +86,32 @@ func (obj *FastCountFact) Init(init *facts.Init) error {
|
||||
func (obj *FastCountFact) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // always signal when we're done
|
||||
|
||||
count := int64(0)
|
||||
|
||||
// streams must generate an initial event on startup
|
||||
for {
|
||||
result, err := obj.Call(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
obj.mutex.Lock()
|
||||
obj.count++
|
||||
obj.mutex.Unlock()
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.IntValue{V: count}:
|
||||
count++
|
||||
case obj.init.Output <- result:
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call this fact and return the value if it is possible to do so at this time.
|
||||
func (obj *FastCountFact) Call(ctx context.Context) (types.Value, error) {
|
||||
obj.mutex.Lock() // TODO: could be a read lock
|
||||
count := obj.count
|
||||
obj.mutex.Unlock()
|
||||
return &types.IntValue{
|
||||
V: int64(count),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -243,13 +243,24 @@ func (obj *OneInstanceFact) Init(init *facts.Init) error {
|
||||
func (obj *OneInstanceFact) Stream(ctx context.Context) error {
|
||||
obj.init.Logf("Stream of `%s` @ %p", obj.Name, obj)
|
||||
defer close(obj.init.Output) // always signal when we're done
|
||||
|
||||
result, err := obj.Call(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: msg,
|
||||
}:
|
||||
case obj.init.Output <- result:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Call this fact and return the value if it is possible to do so at this time.
|
||||
func (obj *OneInstanceFact) Call(ctx context.Context) (types.Value, error) {
|
||||
return &types.StrValue{
|
||||
V: msg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ func init() {
|
||||
funcs.ModuleRegister(ModuleName, GetFloatFuncName, func() interfaces.Func { return &GetFunc{Type: types.TypeFloat} })
|
||||
}
|
||||
|
||||
var _ interfaces.CallableFunc = &GetFunc{}
|
||||
|
||||
// GetFunc is special function which looks up the stored `Any` field in the
|
||||
// value resource that it gets it from. If it is initialized with a fixed Type
|
||||
// field, then it becomes a statically typed version that can only return keys
|
||||
@@ -88,7 +90,8 @@ type GetFunc struct {
|
||||
|
||||
init *interfaces.Init
|
||||
|
||||
key string
|
||||
key string
|
||||
args []types.Value
|
||||
|
||||
last types.Value
|
||||
result types.Value // last calculated output
|
||||
@@ -229,7 +232,13 @@ func (obj *GetFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
key := input.Struct()[getArgNameKey].Str()
|
||||
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.args = args
|
||||
|
||||
key := args[0].Str()
|
||||
if key == "" {
|
||||
return fmt.Errorf("can't use an empty key")
|
||||
}
|
||||
@@ -263,7 +272,7 @@ func (obj *GetFunc) Stream(ctx context.Context) error {
|
||||
// return errwrap.Wrapf(err, "channel watch failed on `%s`", obj.key)
|
||||
//}
|
||||
|
||||
result, err := obj.getValue(ctx) // get the value...
|
||||
result, err := obj.Call(ctx, obj.args) // get the value...
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -287,8 +296,12 @@ func (obj *GetFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// getValue gets the value we're looking for.
|
||||
func (obj *GetFunc) getValue(ctx context.Context) (types.Value, error) {
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time. This was previously getValue which gets the value
|
||||
// we're looking for.
|
||||
func (obj *GetFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
key := args[0].Str()
|
||||
|
||||
typ, exists := obj.Info().Sig.Out.Map[getFieldNameValue] // type of value field
|
||||
if !exists || typ == nil {
|
||||
// programming error
|
||||
@@ -303,9 +316,9 @@ func (obj *GetFunc) getValue(ctx context.Context) (types.Value, error) {
|
||||
// step that might be needed if the value started out empty...
|
||||
// TODO: We could even add a stored: bool field in the returned struct!
|
||||
isReady := true // assume true
|
||||
val, err := obj.init.Local.ValueGet(ctx, obj.key)
|
||||
val, err := obj.init.Local.ValueGet(ctx, key)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", obj.key)
|
||||
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", key)
|
||||
}
|
||||
if val == nil { // val doesn't exist
|
||||
isReady = false
|
||||
@@ -324,7 +337,7 @@ func (obj *GetFunc) getValue(ctx context.Context) (types.Value, error) {
|
||||
// an str for example, this error happens... Do we want
|
||||
// to: (1) coerce? -- no; (2) error? -- yep for now; (3)
|
||||
// improve type unification? -- if it's possible, yes.
|
||||
return nil, errwrap.Wrapf(err, "type mismatch, check type in Value[%s]", obj.key)
|
||||
return nil, errwrap.Wrapf(err, "type mismatch, check type in Value[%s]", key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,12 +55,15 @@ func init() {
|
||||
funcs.ModuleRegister(ModuleName, GetValFuncName, func() interfaces.Func { return &GetValFunc{} })
|
||||
}
|
||||
|
||||
var _ interfaces.CallableFunc = &GetValFunc{}
|
||||
|
||||
// GetValFunc is special function which returns the value of a given key in the
|
||||
// exposed world.
|
||||
type GetValFunc struct {
|
||||
init *interfaces.Init
|
||||
|
||||
key string
|
||||
key string
|
||||
args []types.Value
|
||||
|
||||
last types.Value
|
||||
result types.Value // last calculated output
|
||||
@@ -132,7 +135,13 @@ func (obj *GetValFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
key := input.Struct()[getValArgNameKey].Str()
|
||||
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.args = args
|
||||
|
||||
key := args[0].Str()
|
||||
if key == "" {
|
||||
return fmt.Errorf("can't use an empty key")
|
||||
}
|
||||
@@ -169,7 +178,7 @@ func (obj *GetValFunc) Stream(ctx context.Context) error {
|
||||
return errwrap.Wrapf(err, "channel watch failed on `%s`", obj.key)
|
||||
}
|
||||
|
||||
result, err := obj.getValue(ctx) // get the value...
|
||||
result, err := obj.Call(ctx, obj.args) // get the value...
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -193,14 +202,17 @@ func (obj *GetValFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// getValue gets the value we're looking for.
|
||||
func (obj *GetValFunc) getValue(ctx context.Context) (types.Value, error) {
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time. This was previously getValue which gets the value
|
||||
// we're looking for.
|
||||
func (obj *GetValFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
key := args[0].Str()
|
||||
exists := true // assume true
|
||||
val, err := obj.init.World.StrGet(ctx, obj.key)
|
||||
val, err := obj.init.World.StrGet(ctx, key)
|
||||
if err != nil && obj.init.World.StrIsNotExist(err) {
|
||||
exists = false // val doesn't exist
|
||||
} else if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", obj.key)
|
||||
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", key)
|
||||
}
|
||||
|
||||
s := &types.StrValue{V: val}
|
||||
|
||||
@@ -51,12 +51,15 @@ func init() {
|
||||
funcs.ModuleRegister(ModuleName, KVLookupFuncName, func() interfaces.Func { return &KVLookupFunc{} })
|
||||
}
|
||||
|
||||
var _ interfaces.CallableFunc = &KVLookupFunc{}
|
||||
|
||||
// KVLookupFunc is special function which returns all the values of a given key
|
||||
// in the exposed world. It is similar to exchange, but it does not set a key.
|
||||
type KVLookupFunc struct {
|
||||
init *interfaces.Init
|
||||
|
||||
namespace string
|
||||
args []types.Value
|
||||
|
||||
last types.Value
|
||||
result types.Value // last calculated output
|
||||
@@ -127,7 +130,13 @@ func (obj *KVLookupFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
namespace := input.Struct()[kvLookupArgNameNamespace].Str()
|
||||
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.args = args
|
||||
|
||||
namespace := args[0].Str()
|
||||
if namespace == "" {
|
||||
return fmt.Errorf("can't use an empty namespace")
|
||||
}
|
||||
@@ -145,7 +154,7 @@ func (obj *KVLookupFunc) Stream(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := obj.buildMap(ctx) // build the map...
|
||||
result, err := obj.Call(ctx, obj.args) // build the map...
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -174,7 +183,7 @@ func (obj *KVLookupFunc) Stream(ctx context.Context) error {
|
||||
return errwrap.Wrapf(err, "channel watch failed on `%s`", obj.namespace)
|
||||
}
|
||||
|
||||
result, err := obj.buildMap(ctx) // build the map...
|
||||
result, err := obj.Call(ctx, obj.args) // build the map...
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -198,11 +207,14 @@ func (obj *KVLookupFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// buildMap builds the result map which we'll need. It uses struct variables.
|
||||
func (obj *KVLookupFunc) buildMap(ctx context.Context) (types.Value, error) {
|
||||
keyMap, err := obj.init.World.StrMapGet(ctx, obj.namespace)
|
||||
// Call this function with the input args and return the value if it is possible
|
||||
// to do so at this time. This was previously buildMap, which builds the result
|
||||
// map which we'll need. It uses struct variables.
|
||||
func (obj *KVLookupFunc) Call(ctx context.Context, args []types.Value) (types.Value, error) {
|
||||
namespace := args[0].Str()
|
||||
keyMap, err := obj.init.World.StrMapGet(ctx, namespace)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", obj.namespace)
|
||||
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", namespace)
|
||||
}
|
||||
|
||||
d := types.NewMap(obj.Info().Sig.Out)
|
||||
|
||||
@@ -88,6 +88,8 @@ type ScheduleFunc struct {
|
||||
|
||||
init *interfaces.Init
|
||||
|
||||
args []types.Value
|
||||
|
||||
namespace string
|
||||
scheduler *scheduler.Result
|
||||
|
||||
@@ -303,7 +305,15 @@ func (obj *ScheduleFunc) Stream(ctx context.Context) error {
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
namespace := input.Struct()[scheduleArgNameNamespace].Str()
|
||||
args, err := interfaces.StructToCallableArgs(input) // []types.Value, error)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.args = args
|
||||
|
||||
namespace := args[0].Str()
|
||||
|
||||
//namespace := input.Struct()[scheduleArgNameNamespace].Str()
|
||||
if namespace == "" {
|
||||
return fmt.Errorf("can't use an empty namespace")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user