lang: Unnested the core package from the functions dir
The core package could contain non-functions, so we might as well move it upwards.
This commit is contained in:
1
lang/funcs/core/.gitignore
vendored
1
lang/funcs/core/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
generated_funcs.go
|
||||
@@ -1,43 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ConcatFuncName is the name this function is registered as.
|
||||
ConcatFuncName = funcs.ConcatFuncName
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.Register(ConcatFuncName, &types.FuncValue{
|
||||
T: types.NewType("func(a str, b str) str"),
|
||||
V: Concat,
|
||||
})
|
||||
}
|
||||
|
||||
// Concat concatenates two strings together.
|
||||
func Concat(input []types.Value) (types.Value, error) {
|
||||
return &types.StrValue{
|
||||
V: input[0].Str() + input[1].Str(),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package convert
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "convert"
|
||||
)
|
||||
@@ -1,40 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "format_bool", &types.FuncValue{
|
||||
T: types.NewType("func(a bool) str"),
|
||||
V: FormatBool,
|
||||
})
|
||||
}
|
||||
|
||||
// FormatBool converts a boolean to a string representation that can be consumed
|
||||
// by ParseBool. This value will be `"true"` or `"false"`.
|
||||
func FormatBool(input []types.Value) (types.Value, error) {
|
||||
return &types.StrValue{
|
||||
V: strconv.FormatBool(input[0].Bool()),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "parse_bool", &types.FuncValue{
|
||||
T: types.NewType("func(a str) bool"),
|
||||
V: ParseBool,
|
||||
})
|
||||
}
|
||||
|
||||
// ParseBool parses a bool string and returns a boolean. It errors if you pass
|
||||
// it an invalid value. Valid values match what is accepted by the golang
|
||||
// strconv.ParseBool function. It's recommended to use the strings `true` or
|
||||
// `false` if you are undecided about what string representation to choose.
|
||||
func ParseBool(input []types.Value) (types.Value, error) {
|
||||
s := input[0].Str()
|
||||
b, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid bool: `%s`", s)
|
||||
}
|
||||
return &types.BoolValue{
|
||||
V: b,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "to_float", &types.FuncValue{
|
||||
T: types.NewType("func(a int) float"),
|
||||
V: ToFloat,
|
||||
})
|
||||
}
|
||||
|
||||
// ToFloat converts an integer to a float.
|
||||
func ToFloat(input []types.Value) (types.Value, error) {
|
||||
return &types.FloatValue{
|
||||
V: float64(input[0].Int()),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func testToFloat(t *testing.T, input int64, expected float64) {
|
||||
got, err := ToFloat([]types.Value{&types.IntValue{V: input}})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if got.Float() != expected {
|
||||
t.Errorf("invalid output, expected %v, got %v", expected, got.Float())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestToFloat1(t *testing.T) {
|
||||
testToFloat(t, 2, 2.0)
|
||||
}
|
||||
|
||||
func TestToFloat2(t *testing.T) {
|
||||
testToFloat(t, 7, 7.0)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "to_int", &types.FuncValue{
|
||||
T: types.NewType("func(a float) int"),
|
||||
V: ToInt,
|
||||
})
|
||||
}
|
||||
|
||||
// ToInt converts a float to an integer.
|
||||
func ToInt(input []types.Value) (types.Value, error) {
|
||||
return &types.IntValue{
|
||||
V: int64(input[0].Float()),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func testToInt(t *testing.T, input float64, expected int64) {
|
||||
|
||||
got, err := ToInt([]types.Value{&types.FloatValue{V: input}})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if got.Int() != expected {
|
||||
t.Errorf("invalid output, expected %v, got %v", expected, got)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestToInt1(t *testing.T) {
|
||||
testToInt(t, 2.09, 2)
|
||||
|
||||
}
|
||||
|
||||
func TestToInt2(t *testing.T) {
|
||||
testToInt(t, 7.8, 7)
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
|
||||
// import so the funcs register
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/convert"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/datetime"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/deploy"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/example"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/example/nested"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/fmt"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/iter"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/math"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/net"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/os"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/regexp"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/strings"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/sys"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/test"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/value"
|
||||
_ "github.com/purpleidea/mgmt/lang/funcs/core/world"
|
||||
)
|
||||
|
||||
// TODO: Instead of doing this one-level embed, we could give each package an
|
||||
// API that it calls to "register" the private embed.FS that it wants to share.
|
||||
|
||||
//go:embed */*.mcl
|
||||
var mcl embed.FS
|
||||
|
||||
// AssetNames returns a flattened list of embedded .mcl file paths.
|
||||
func AssetNames() ([]string, error) {
|
||||
fileSystem := mcl
|
||||
paths := []string{}
|
||||
if err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() { // skip the dirs
|
||||
return nil
|
||||
}
|
||||
paths = append(paths, path)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
// Asset returns the contents of an embedded .mcl file.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
return mcl.ReadFile(name)
|
||||
}
|
||||
@@ -1,810 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !root
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kylelemons/godebug/pretty"
|
||||
)
|
||||
|
||||
func TestPureFuncExec0(t *testing.T) {
|
||||
type test struct { // an individual test
|
||||
name string
|
||||
funcname string
|
||||
args []types.Value
|
||||
fail bool
|
||||
expect types.Value
|
||||
}
|
||||
testCases := []test{}
|
||||
|
||||
//{
|
||||
// testCases = append(testCases, test{
|
||||
// name: "",
|
||||
// funcname: "",
|
||||
// args: []types.Value{
|
||||
// },
|
||||
// fail: false,
|
||||
// expect: nil,
|
||||
// })
|
||||
//}
|
||||
{
|
||||
testCases = append(testCases, test{
|
||||
name: "strings.to_lower 0",
|
||||
funcname: "strings.to_lower",
|
||||
args: []types.Value{
|
||||
&types.StrValue{
|
||||
V: "HELLO",
|
||||
},
|
||||
},
|
||||
fail: false,
|
||||
expect: &types.StrValue{
|
||||
V: "hello",
|
||||
},
|
||||
})
|
||||
}
|
||||
{
|
||||
testCases = append(testCases, test{
|
||||
name: "datetime.now fail",
|
||||
funcname: "datetime.now",
|
||||
args: nil,
|
||||
fail: true,
|
||||
expect: nil,
|
||||
})
|
||||
}
|
||||
// TODO: run unification in PureFuncExec if it makes sense to do so...
|
||||
//{
|
||||
// testCases = append(testCases, test{
|
||||
// name: "len 0",
|
||||
// funcname: "len",
|
||||
// args: []types.Value{
|
||||
// &types.StrValue{
|
||||
// V: "Hello, world!",
|
||||
// },
|
||||
// },
|
||||
// fail: false,
|
||||
// expect: &types.IntValue{
|
||||
// V: 13,
|
||||
// },
|
||||
// })
|
||||
//}
|
||||
|
||||
names := []string{}
|
||||
for index, tc := range testCases { // run all the tests
|
||||
if tc.name == "" {
|
||||
t.Errorf("test #%d: not named", index)
|
||||
continue
|
||||
}
|
||||
if util.StrInList(tc.name, names) {
|
||||
t.Errorf("test #%d: duplicate sub test name of: %s", index, tc.name)
|
||||
continue
|
||||
}
|
||||
names = append(names, tc.name)
|
||||
|
||||
//if index != 3 { // hack to run a subset (useful for debugging)
|
||||
//if (index != 20 && index != 21) {
|
||||
//if tc.name != "nil" {
|
||||
// continue
|
||||
//}
|
||||
|
||||
t.Run(fmt.Sprintf("test #%d (%s)", index, tc.name), func(t *testing.T) {
|
||||
name, funcname, args, fail, expect := tc.name, tc.funcname, tc.args, tc.fail, tc.expect
|
||||
|
||||
t.Logf("\n\ntest #%d (%s) ----------------\n\n", index, name)
|
||||
|
||||
f, err := funcs.Lookup(funcname)
|
||||
if err != nil {
|
||||
t.Errorf("test #%d: func lookup failed with: %+v", index, err)
|
||||
return
|
||||
}
|
||||
|
||||
result, err := funcs.PureFuncExec(f, args)
|
||||
|
||||
if !fail && err != nil {
|
||||
t.Errorf("test #%d: func failed with: %+v", index, err)
|
||||
return
|
||||
}
|
||||
if fail && err == nil {
|
||||
t.Errorf("test #%d: func passed, expected fail", index)
|
||||
return
|
||||
}
|
||||
if !fail && result == nil {
|
||||
t.Errorf("test #%d: func output was nil", index)
|
||||
return
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(result, expect) {
|
||||
return
|
||||
}
|
||||
|
||||
// double check because DeepEqual is different since the func exists
|
||||
diff := pretty.Compare(result, expect)
|
||||
if diff == "" { // bonus
|
||||
return
|
||||
}
|
||||
t.Errorf("test #%d: result did not match expected", index)
|
||||
// TODO: consider making our own recursive print function
|
||||
t.Logf("test #%d: actual: \n\n%s\n", index, spew.Sdump(result))
|
||||
t.Logf("test #%d: expected: \n\n%s", index, spew.Sdump(expect))
|
||||
|
||||
// more details, for tricky cases:
|
||||
diffable := &pretty.Config{
|
||||
Diffable: true,
|
||||
IncludeUnexported: true,
|
||||
//PrintStringers: false,
|
||||
//PrintTextMarshalers: false,
|
||||
//SkipZeroFields: false,
|
||||
}
|
||||
t.Logf("test #%d: actual: \n\n%s\n", index, diffable.Sprint(result))
|
||||
t.Logf("test #%d: expected: \n\n%s", index, diffable.Sprint(expect))
|
||||
t.Logf("test #%d: diff:\n%s", index, diff)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Step is used for the timeline in tests.
|
||||
type Step interface {
|
||||
Action() error
|
||||
Expect() error
|
||||
}
|
||||
|
||||
type manualStep struct {
|
||||
action func() error
|
||||
expect func() error
|
||||
|
||||
exit chan struct{} // exit signal, set by test harness
|
||||
argch chan []types.Value // send new inputs, set by test harness
|
||||
valueptrch chan int // incoming values, set by test harness
|
||||
results []types.Value // all values, set by test harness
|
||||
}
|
||||
|
||||
func (obj *manualStep) Action() error {
|
||||
return obj.action()
|
||||
}
|
||||
func (obj *manualStep) Expect() error {
|
||||
return obj.expect()
|
||||
}
|
||||
|
||||
// NewManualStep creates a new manual step with an action and an expect test.
|
||||
func NewManualStep(action, expect func() error) Step {
|
||||
return &manualStep{
|
||||
action: action,
|
||||
expect: expect,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSendInputs sends a list of inputs to the running function to populate it.
|
||||
// If you send the wrong input signature, then you'll cause a failure. Testing
|
||||
// this kind of failure is not a goal of these tests, since the unification code
|
||||
// is meant to guarantee we always send the correct type signature.
|
||||
func NewSendInputs(inputs []types.Value) Step {
|
||||
return &sendInputsStep{
|
||||
inputs: inputs,
|
||||
}
|
||||
}
|
||||
|
||||
type sendInputsStep struct {
|
||||
inputs []types.Value
|
||||
|
||||
exit chan struct{} // exit signal, set by test harness
|
||||
argch chan []types.Value // send new inputs, set by test harness
|
||||
//valueptrch chan int // incoming values, set by test harness
|
||||
//results []types.Value // all values, set by test harness
|
||||
}
|
||||
|
||||
func (obj *sendInputsStep) Action() error {
|
||||
select {
|
||||
case obj.argch <- obj.inputs:
|
||||
return nil
|
||||
case <-obj.exit:
|
||||
return fmt.Errorf("exit called")
|
||||
}
|
||||
}
|
||||
|
||||
func (obj *sendInputsStep) Expect() error { return nil }
|
||||
|
||||
// NewWaitForNSeconds waits this many seconds for new values from the stream. It
|
||||
// can timeout if it gets bored of waiting.
|
||||
func NewWaitForNSeconds(number int, timeout int) Step {
|
||||
return &waitAmountStep{
|
||||
timer: number, // timer seconds
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// NewWaitForNValues waits for this many values from the stream. It can timeout
|
||||
// if it gets bored of waiting. If you request more values than can be produced,
|
||||
// then it will block indefinitely if there's no timeout.
|
||||
func NewWaitForNValues(number int, timeout int) Step {
|
||||
return &waitAmountStep{
|
||||
count: number, // count values
|
||||
timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// waitAmountStep waits for either a count of N values, or a timer of N seconds,
|
||||
// or both. It also accepts a timeout which will cause it to error.
|
||||
// TODO: have the timeout timer be overall instead of per step!
|
||||
type waitAmountStep struct {
|
||||
count int // nth count (set to a negative value to disable)
|
||||
timer int // seconds (set to a negative value to disable)
|
||||
timeout int // seconds to fail after
|
||||
|
||||
exit chan struct{} // exit signal, set by test harness
|
||||
//argch chan []types.Value // send new inputs, set by test harness
|
||||
valueptrch chan int // incoming values, set by test harness
|
||||
results []types.Value // all values, set by test harness
|
||||
}
|
||||
|
||||
func (obj *waitAmountStep) Action() error {
|
||||
count := 0
|
||||
ticked := false // did we get the timer event?
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
ticker := util.TimeAfterOrBlockCtx(ctx, obj.timer)
|
||||
if obj.timer < 0 { // disable timer
|
||||
ticked = true
|
||||
}
|
||||
|
||||
for {
|
||||
if count >= obj.count { // got everything we wanted
|
||||
if ticked {
|
||||
break
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-obj.exit:
|
||||
return fmt.Errorf("exit called")
|
||||
|
||||
case <-util.TimeAfterOrBlock(obj.timeout):
|
||||
// TODO: make this overall instead of re-running it each time
|
||||
return fmt.Errorf("waited too long for a value")
|
||||
|
||||
case n, ok := <-obj.valueptrch: // read output
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected close")
|
||||
}
|
||||
count++
|
||||
_ = n // this is the index of the value we're at
|
||||
|
||||
case <-ticker: // received the timer event
|
||||
ticker = nil
|
||||
ticked = true
|
||||
if obj.count > -1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (obj *waitAmountStep) Expect() error { return nil }
|
||||
|
||||
// NewRangeExpect passes in an expect function which will receive the entire
|
||||
// range of values ever received. This stream (list) of values can be matched on
|
||||
// however you like.
|
||||
func NewRangeExpect(fn func([]types.Value) error) Step {
|
||||
return &rangeExpectStep{
|
||||
fn: fn,
|
||||
}
|
||||
}
|
||||
|
||||
type rangeExpectStep struct {
|
||||
fn func([]types.Value) error
|
||||
|
||||
// TODO: we could pass exit to the expect fn if we wanted in the future
|
||||
exit chan struct{} // exit signal, set by test harness
|
||||
//argch chan []types.Value // send new inputs, set by test harness
|
||||
//valueptrch chan int // incoming values, set by test harness
|
||||
results []types.Value // all values, set by test harness
|
||||
}
|
||||
|
||||
func (obj *rangeExpectStep) Action() error { return nil }
|
||||
|
||||
func (obj *rangeExpectStep) Expect() error {
|
||||
results := []types.Value{}
|
||||
for _, v := range obj.results { // copy
|
||||
value := v.Copy()
|
||||
results = append(results, value)
|
||||
}
|
||||
return obj.fn(results) // run with a copy
|
||||
}
|
||||
|
||||
// vog is a helper function to produce mcl values from golang equivalents that
|
||||
// is only safe in tests because it panics on error.
|
||||
func vog(i interface{}) types.Value {
|
||||
v, err := types.ValueOfGolang(i)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unexpected error in vog: %+v", err))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// rcopy is a helper to copy a list of types.Value structs.
|
||||
func rcopy(input []types.Value) []types.Value {
|
||||
result := []types.Value{}
|
||||
for i := range input {
|
||||
x := input[i].Copy()
|
||||
result = append(result, x)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// TestLiveFuncExec0 runs a live execution timeline on a function stream. It is
|
||||
// very useful for testing function streams.
|
||||
// FIXME: if the function returns a different type than what is specified by its
|
||||
// signature, we might block instead of returning a useful error.
|
||||
func TestLiveFuncExec0(t *testing.T) {
|
||||
type args struct {
|
||||
argv []types.Value
|
||||
next func() // specifies we're ready for the next set of inputs
|
||||
}
|
||||
|
||||
type test struct { // an individual test
|
||||
name string
|
||||
hostname string // in case we want to simulate a hostname
|
||||
funcname string
|
||||
|
||||
// TODO: this could be a generator that keeps pushing out steps until it's done!
|
||||
timeline []Step
|
||||
expect func() error // function to check for expected state
|
||||
startup func() error // function to run as startup
|
||||
cleanup func() error // function to run as cleanup
|
||||
}
|
||||
|
||||
timeout := -1 // default timeout (block) if not specified elsewhere
|
||||
testCases := []test{}
|
||||
{
|
||||
count := 5
|
||||
timeline := []Step{
|
||||
NewWaitForNValues(count, timeout), // get 5 values
|
||||
// pass in a custom validation function
|
||||
NewRangeExpect(func(args []types.Value) error {
|
||||
//fmt.Printf("range: %+v\n", args) // debugging
|
||||
if len(args) < count {
|
||||
return fmt.Errorf("no args found")
|
||||
}
|
||||
// check for increasing ints (ideal delta == 1)
|
||||
x := args[0].Int()
|
||||
for i := 1; i < count; i++ {
|
||||
if args[i].Int()-x < 1 {
|
||||
return fmt.Errorf("range jumps: %+v", args)
|
||||
}
|
||||
if args[i].Int()-x != 1 {
|
||||
// if this fails, travis is just slow
|
||||
return fmt.Errorf("timing error: %+v", args)
|
||||
}
|
||||
x = args[i].Int()
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
//NewWaitForNSeconds(5, timeout), // not needed
|
||||
}
|
||||
|
||||
testCases = append(testCases, test{
|
||||
name: "simple func",
|
||||
hostname: "", // not needed for this func
|
||||
funcname: "datetime.now",
|
||||
timeline: timeline,
|
||||
expect: func() error { return nil },
|
||||
startup: func() error { return nil },
|
||||
cleanup: func() error { return nil },
|
||||
})
|
||||
}
|
||||
{
|
||||
timeline := []Step{
|
||||
NewSendInputs([]types.Value{
|
||||
vog("helloXworld"),
|
||||
vog("X"), // split by this
|
||||
}),
|
||||
|
||||
NewWaitForNValues(1, timeout), // more than 1 blocks here
|
||||
|
||||
// pass in a custom validation function
|
||||
NewRangeExpect(func(args []types.Value) error {
|
||||
//fmt.Printf("range: %+v\n", args) // debugging
|
||||
if c := len(args); c != 1 {
|
||||
return fmt.Errorf("wrong args count, got: %d", c)
|
||||
}
|
||||
if args[0].Type().Kind != types.KindList {
|
||||
return fmt.Errorf("expected list, got: %+v", args[0])
|
||||
}
|
||||
if err := vog([]string{"hello", "world"}).Cmp(args[0]); err != nil {
|
||||
return errwrap.Wrapf(err, "got different expected value: %+v", args[0])
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
|
||||
testCases = append(testCases, test{
|
||||
name: "simple pure func",
|
||||
hostname: "", // not needed for this func
|
||||
funcname: "strings.split",
|
||||
timeline: timeline,
|
||||
expect: func() error { return nil },
|
||||
startup: func() error { return nil },
|
||||
cleanup: func() error { return nil },
|
||||
})
|
||||
}
|
||||
{
|
||||
p := "/tmp/somefiletoread"
|
||||
content := "hello world!\n"
|
||||
timeline := []Step{
|
||||
NewSendInputs([]types.Value{
|
||||
vog(p),
|
||||
}),
|
||||
|
||||
NewWaitForNValues(1, timeout), // more than 1 blocks here
|
||||
NewWaitForNSeconds(5, 10), // wait longer just to be sure
|
||||
|
||||
// pass in a custom validation function
|
||||
NewRangeExpect(func(args []types.Value) error {
|
||||
//fmt.Printf("range: %+v\n", args) // debugging
|
||||
if c := len(args); c != 1 {
|
||||
return fmt.Errorf("wrong args count, got: %d", c)
|
||||
}
|
||||
if args[0].Type().Kind != types.KindStr {
|
||||
return fmt.Errorf("expected str, got: %+v", args[0])
|
||||
}
|
||||
if err := vog(content).Cmp(args[0]); err != nil {
|
||||
return errwrap.Wrapf(err, "got different expected value: %+v", args[0])
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
}
|
||||
|
||||
testCases = append(testCases, test{
|
||||
name: "readfile",
|
||||
hostname: "", // not needed for this func
|
||||
funcname: "os.readfile",
|
||||
timeline: timeline,
|
||||
expect: func() error { return nil },
|
||||
startup: func() error { return ioutil.WriteFile(p, []byte(content), 0666) },
|
||||
cleanup: func() error { return os.Remove(p) },
|
||||
})
|
||||
}
|
||||
names := []string{}
|
||||
for index, tc := range testCases { // run all the tests
|
||||
if tc.name == "" {
|
||||
t.Errorf("test #%d: not named", index)
|
||||
continue
|
||||
}
|
||||
if util.StrInList(tc.name, names) {
|
||||
t.Errorf("test #%d: duplicate sub test name of: %s", index, tc.name)
|
||||
continue
|
||||
}
|
||||
names = append(names, tc.name)
|
||||
t.Run(fmt.Sprintf("test #%d (%s)", index, tc.name), func(t *testing.T) {
|
||||
hostname, funcname, timeline, expect, startup, cleanup := tc.hostname, tc.funcname, tc.timeline, tc.expect, tc.startup, tc.cleanup
|
||||
|
||||
t.Logf("\n\ntest #%d: func: %+v\n", index, funcname)
|
||||
defer t.Logf("test #%d: done!", index)
|
||||
|
||||
handle, err := funcs.Lookup(funcname) // get function...
|
||||
if err != nil {
|
||||
t.Errorf("test #%d: func lookup failed with: %+v", index, err)
|
||||
return
|
||||
}
|
||||
// run validate!
|
||||
if err := handle.Validate(); err != nil {
|
||||
t.Errorf("test #%d: FAIL", index)
|
||||
t.Errorf("test #%d: could not validate Func: %+v", index, err)
|
||||
return
|
||||
}
|
||||
sig := handle.Info().Sig
|
||||
if sig.Kind != types.KindFunc {
|
||||
t.Errorf("test #%d: FAIL", index)
|
||||
t.Errorf("test #%d: must be kind func: %+v", index, err)
|
||||
return
|
||||
}
|
||||
|
||||
input := make(chan types.Value) // we close this when we're done
|
||||
output := make(chan types.Value) // we create it, func closes it
|
||||
|
||||
debug := testing.Verbose() // set via the -test.v flag to `go test`
|
||||
logf := func(format string, v ...interface{}) {
|
||||
t.Logf(fmt.Sprintf("test #%d: func: ", index)+format, v...)
|
||||
}
|
||||
init := &interfaces.Init{
|
||||
Hostname: hostname,
|
||||
Input: input,
|
||||
Output: output,
|
||||
World: nil, // TODO: add me somehow!
|
||||
Debug: debug,
|
||||
Logf: logf,
|
||||
}
|
||||
|
||||
t.Logf("test #%d: running startup()", index)
|
||||
if err := startup(); err != nil {
|
||||
t.Errorf("test #%d: FAIL", index)
|
||||
t.Errorf("test #%d: could not startup: %+v", index, err)
|
||||
return
|
||||
}
|
||||
|
||||
// run init
|
||||
t.Logf("test #%d: running Init", index)
|
||||
if err := handle.Init(init); err != nil {
|
||||
t.Errorf("test #%d: FAIL", index)
|
||||
t.Errorf("test #%d: could not init func: %+v", index, err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
t.Logf("test #%d: running cleanup()", index)
|
||||
if err := cleanup(); err != nil {
|
||||
t.Errorf("test #%d: FAIL", index)
|
||||
t.Errorf("test #%d: could not cleanup: %+v", index, err)
|
||||
}
|
||||
}()
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait() // if we return early
|
||||
|
||||
argch := make(chan []types.Value)
|
||||
errch := make(chan error)
|
||||
close1 := make(chan struct{})
|
||||
close2 := make(chan struct{})
|
||||
kill1 := make(chan struct{})
|
||||
kill2 := make(chan struct{})
|
||||
//kill3 := make(chan struct{}) // future use
|
||||
exit := make(chan struct{})
|
||||
|
||||
mutex := &sync.RWMutex{}
|
||||
results := []types.Value{} // all values received so far
|
||||
valueptrch := make(chan int) // which Nth value are we at?
|
||||
killTimeline := make(chan struct{}) // ask timeline to exit
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// wait for close signals
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer close(errch) // last one turns out the lights
|
||||
select {
|
||||
case <-close1:
|
||||
}
|
||||
select {
|
||||
case <-close2:
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for kill signals
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
select {
|
||||
case <-exit:
|
||||
case <-kill1:
|
||||
case <-kill2:
|
||||
//case <-kill3: // future use
|
||||
}
|
||||
close(killTimeline) // main kill signal for tl
|
||||
}()
|
||||
|
||||
// run the stream
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer close(close1)
|
||||
if debug {
|
||||
logf("Running func")
|
||||
}
|
||||
err := handle.Stream(ctx) // sends to output chan
|
||||
t.Logf("test #%d: stream exited with: %+v", index, err)
|
||||
if debug {
|
||||
logf("Exiting func")
|
||||
}
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
// we closed with an error...
|
||||
select {
|
||||
case errch <- errwrap.Wrapf(err, "problem streaming func"):
|
||||
}
|
||||
}()
|
||||
|
||||
// read from incoming args and send to input channel
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer close(close2)
|
||||
defer close(input) // close input when done
|
||||
if argch == nil { // no args
|
||||
return
|
||||
}
|
||||
si := &types.Type{
|
||||
// input to functions are structs
|
||||
Kind: types.KindStruct,
|
||||
Map: handle.Info().Sig.Map,
|
||||
Ord: handle.Info().Sig.Ord,
|
||||
}
|
||||
t.Logf("test #%d: func has sig: %s", index, si)
|
||||
|
||||
// TODO: should this be a select with an exit signal?
|
||||
for args := range argch { // chan
|
||||
st := types.NewStruct(si)
|
||||
count := 0
|
||||
for i, arg := range args {
|
||||
//name := util.NumToAlpha(i) // assume (incorrectly) for now...
|
||||
name := handle.Info().Sig.Ord[i] // better
|
||||
if err := st.Set(name, arg); err != nil { // populate struct
|
||||
select {
|
||||
case errch <- errwrap.Wrapf(err, "struct set failure"):
|
||||
}
|
||||
close(kill1) // unblock tl and cause fail
|
||||
return
|
||||
}
|
||||
count++
|
||||
}
|
||||
if count != len(si.Map) { // expect this number
|
||||
select {
|
||||
case errch <- fmt.Errorf("struct field count is wrong"):
|
||||
}
|
||||
close(kill1) // unblock tl and cause fail
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("test #%d: send to func: %s", index, args)
|
||||
select {
|
||||
case input <- st: // send to function (must not block)
|
||||
case <-close1: // unblock the input send in case stream closed
|
||||
select {
|
||||
case errch <- fmt.Errorf("stream closed early"):
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// run timeline
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
t.Logf("test #%d: executing timeline", index)
|
||||
defer wg.Done()
|
||||
Timeline:
|
||||
for ix, step := range timeline {
|
||||
select {
|
||||
case <-killTimeline:
|
||||
break Timeline
|
||||
default:
|
||||
// pass
|
||||
}
|
||||
|
||||
mutex.RLock()
|
||||
// magic setting of important values...
|
||||
if s, ok := step.(*manualStep); ok {
|
||||
s.exit = killTimeline // kill signal
|
||||
s.argch = argch // send inputs here
|
||||
s.valueptrch = valueptrch // receive value ptr
|
||||
s.results = rcopy(results) // all results as array
|
||||
}
|
||||
if s, ok := step.(*sendInputsStep); ok {
|
||||
s.exit = killTimeline
|
||||
s.argch = argch
|
||||
//s.valueptrch = valueptrch
|
||||
//s.results = rcopy(results)
|
||||
}
|
||||
if s, ok := step.(*waitAmountStep); ok {
|
||||
s.exit = killTimeline
|
||||
//s.argch = argch
|
||||
s.valueptrch = valueptrch
|
||||
s.results = rcopy(results)
|
||||
}
|
||||
if s, ok := step.(*rangeExpectStep); ok {
|
||||
s.exit = killTimeline
|
||||
//s.argch = argch
|
||||
//s.valueptrch = valueptrch
|
||||
s.results = rcopy(results)
|
||||
}
|
||||
mutex.RUnlock()
|
||||
|
||||
t.Logf("test #%d: step(%d)...", index, ix)
|
||||
if err := step.Action(); err != nil {
|
||||
t.Errorf("test #%d: FAIL", index)
|
||||
t.Errorf("test #%d: step(%d) action failed: %s", index, ix, err.Error())
|
||||
break
|
||||
}
|
||||
if err := step.Expect(); err != nil {
|
||||
t.Errorf("test #%d: FAIL", index)
|
||||
t.Errorf("test #%d: step(%d) expect failed: %s", index, ix, err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Logf("test #%d: timeline finished", index)
|
||||
close(argch)
|
||||
|
||||
t.Logf("test #%d: running cancel", index)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
// read everything
|
||||
counter := 0
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case value, ok := <-output: // read from channel
|
||||
if !ok {
|
||||
output = nil
|
||||
continue Loop // only exit via errch closing!
|
||||
}
|
||||
t.Logf("test #%d: got from func: %s", index, value)
|
||||
// check return type
|
||||
if err := handle.Info().Sig.Out.Cmp(value.Type()); err != nil {
|
||||
t.Errorf("test #%d: FAIL", index)
|
||||
t.Errorf("test #%d: unexpected return type from func: %+v", index, err)
|
||||
close(kill2)
|
||||
continue Loop // only exit via errch closing!
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
results = append(results, value) // save value
|
||||
mutex.Unlock()
|
||||
counter++
|
||||
|
||||
case err, ok := <-errch: // handle possible errors
|
||||
if !ok {
|
||||
break Loop
|
||||
}
|
||||
t.Errorf("test #%d: FAIL", index)
|
||||
t.Errorf("test #%d: error: %+v", index, err)
|
||||
continue Loop // only exit via errch closing!
|
||||
}
|
||||
|
||||
// send events to our timeline
|
||||
select {
|
||||
case valueptrch <- counter: // TODO: send value?
|
||||
|
||||
// TODO: add this sort of thing, but don't block everyone who doesn't read
|
||||
//case <-time.After(time.Duration(globalStepReadTimeout) * time.Second):
|
||||
// t.Errorf("test #%d: FAIL", index)
|
||||
// t.Errorf("test #%d: timeline receiver was too slow for value", index)
|
||||
// t.Errorf("test #%d: got(%d): %+v", index, counter, results[counter])
|
||||
// close(kill3) // shut everything down
|
||||
// continue Loop // only exit via errch closing!
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("test #%d: waiting for shutdown", index)
|
||||
close(exit)
|
||||
wg.Wait()
|
||||
|
||||
if err := expect(); err != nil {
|
||||
t.Errorf("test #%d: FAIL", index)
|
||||
t.Errorf("test #%d: expect failed: %s", index, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// all done!
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coredatetime
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "datetime"
|
||||
)
|
||||
@@ -1,50 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coredatetime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "format", &types.FuncValue{
|
||||
T: types.NewType("func(a int, b str) str"),
|
||||
V: Format,
|
||||
})
|
||||
}
|
||||
|
||||
// Format returns returns a textual representation of the input time. The format
|
||||
// has to be defined like specified by the golang "time" package. The time is
|
||||
// the number of seconds since the epoch, and matches what comes from our Now
|
||||
// function. Golang documentation: https://golang.org/pkg/time/#Time.Format
|
||||
func Format(input []types.Value) (types.Value, error) {
|
||||
epochDelta := input[0].Int()
|
||||
if epochDelta < 0 {
|
||||
return nil, fmt.Errorf("epoch delta must be positive")
|
||||
}
|
||||
format := input[1].Str()
|
||||
|
||||
v := time.Unix(epochDelta, 0).Format(format)
|
||||
return &types.StrValue{
|
||||
V: v,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !darwin
|
||||
|
||||
package coredatetime
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
inputVal := &types.IntValue{V: 1443158163}
|
||||
inputFormat := &types.StrValue{V: "2006"}
|
||||
|
||||
val, err := Format([]types.Value{inputVal, inputFormat})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if val.String() != `"2015"` {
|
||||
t.Errorf("invalid output, expected %s, got %s", `"2015"`, val.String())
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coredatetime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "hour", &types.FuncValue{
|
||||
T: types.NewType("func(a int) int"),
|
||||
V: Hour,
|
||||
})
|
||||
}
|
||||
|
||||
// Hour returns the hour of the day corresponding to the input time. The time is
|
||||
// the number of seconds since the epoch, and matches what comes from our Now
|
||||
// function.
|
||||
func Hour(input []types.Value) (types.Value, error) {
|
||||
epochDelta := input[0].Int()
|
||||
if epochDelta < 0 {
|
||||
return nil, fmt.Errorf("epoch delta must be positive")
|
||||
}
|
||||
|
||||
hour := time.Unix(epochDelta, 0).Hour()
|
||||
return &types.IntValue{
|
||||
V: int64(hour),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coredatetime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// NowFuncName is the name this fact is registered as. It's still a Func
|
||||
// Name because this is the name space the fact is actually using.
|
||||
NowFuncName = "now"
|
||||
)
|
||||
|
||||
func init() {
|
||||
facts.ModuleRegister(ModuleName, NowFuncName, func() facts.Fact { return &DateTimeFact{} }) // must register the fact and name
|
||||
}
|
||||
|
||||
// DateTimeFact is a fact which returns the current date and time.
|
||||
type DateTimeFact struct {
|
||||
init *facts.Init
|
||||
}
|
||||
|
||||
// String returns a simple name for this fact. This is needed so this struct can
|
||||
// satisfy the pgraph.Vertex interface.
|
||||
func (obj *DateTimeFact) String() string {
|
||||
return NowFuncName
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal facts that users can use directly.
|
||||
//func (obj *DateTimeFact) Validate() error {
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *DateTimeFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Output: types.NewType("int"),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this fact.
|
||||
func (obj *DateTimeFact) Init(init *facts.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this fact has over time.
|
||||
func (obj *DateTimeFact) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // always signal when we're done
|
||||
// XXX: this might be an interesting fact to write because:
|
||||
// 1) will the sleeps from the ticker be in sync with the second ticker?
|
||||
// 2) if we care about a less precise interval (eg: minute changes) can
|
||||
// we set this up so it doesn't tick as often? -- Yes (make this a function or create a limit function to wrap this)
|
||||
// 3) is it best to have a delta timer that wakes up before it's needed
|
||||
// and calculates how much longer to sleep for?
|
||||
ticker := time.NewTicker(time.Duration(1) * time.Second)
|
||||
|
||||
// streams must generate an initial event on startup
|
||||
startChan := make(chan struct{}) // start signal
|
||||
close(startChan) // kick it off!
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-startChan: // kick the loop once at start
|
||||
startChan = nil // disable
|
||||
case <-ticker.C: // received the timer event
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.IntValue{ // seconds since 1970...
|
||||
V: time.Now().Unix(), // .UTC() not necessary
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coredatetime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// FIXME: consider renaming this to printf, and add in a format string?
|
||||
simple.ModuleRegister(ModuleName, "print", &types.FuncValue{
|
||||
T: types.NewType("func(a int) str"),
|
||||
V: func(input []types.Value) (types.Value, error) {
|
||||
epochDelta := input[0].Int()
|
||||
if epochDelta < 0 {
|
||||
return nil, fmt.Errorf("epoch delta must be positive")
|
||||
}
|
||||
return &types.StrValue{
|
||||
V: time.Unix(epochDelta, 0).String(),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coredatetime
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "weekday", &types.FuncValue{
|
||||
T: types.NewType("func(a int) str"),
|
||||
V: Weekday,
|
||||
})
|
||||
}
|
||||
|
||||
// Weekday returns the lowercased day of the week corresponding to the input
|
||||
// time. The time is the number of seconds since the epoch, and matches what
|
||||
// comes from our Now function.
|
||||
func Weekday(input []types.Value) (types.Value, error) {
|
||||
epochDelta := input[0].Int()
|
||||
if epochDelta < 0 {
|
||||
return nil, fmt.Errorf("epoch delta must be positive")
|
||||
}
|
||||
|
||||
weekday := time.Unix(epochDelta, 0).Weekday()
|
||||
return &types.StrValue{
|
||||
V: strings.ToLower(weekday.String()),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coredeploy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// AbsPathFuncName is the name this function is registered as.
|
||||
AbsPathFuncName = "abspath"
|
||||
|
||||
// arg names...
|
||||
absPathArgNamePath = "path"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, AbsPathFuncName, func() interfaces.Func { return &AbsPathFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
// AbsPathFunc is a function that returns the absolute, full path in the deploy
|
||||
// from an input path that is relative to the calling file. If you pass it an
|
||||
// empty string, you'll just get the absolute deploy directory path that you're
|
||||
// in.
|
||||
type AbsPathFunc struct {
|
||||
init *interfaces.Init
|
||||
data *interfaces.FuncData
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
path *string // the active path
|
||||
result *string // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *AbsPathFunc) String() string {
|
||||
return AbsPathFuncName
|
||||
}
|
||||
|
||||
// SetData is used by the language to pass our function some code-level context.
|
||||
func (obj *AbsPathFunc) SetData(data *interfaces.FuncData) {
|
||||
obj.data = data
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *AbsPathFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{absPathArgNamePath}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *AbsPathFunc) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *AbsPathFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // maybe false because the file contents can change
|
||||
Memo: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", absPathArgNamePath)),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *AbsPathFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
if obj.data == nil {
|
||||
// programming error
|
||||
return fmt.Errorf("missing function data")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *AbsPathFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
for {
|
||||
select {
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // don't infinite loop back
|
||||
continue // no more inputs, but don't return!
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
path := input.Struct()[absPathArgNamePath].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 := 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 {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coredeploy
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "deploy"
|
||||
)
|
||||
@@ -1,172 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coredeploy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// ReadFileFuncName is the name this function is registered as.
|
||||
ReadFileFuncName = "readfile"
|
||||
|
||||
// arg names...
|
||||
readFileArgNameFilename = "filename"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, ReadFileFuncName, func() interfaces.Func { return &ReadFileFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
// ReadFileFunc is a function that reads the full contents from a file in our
|
||||
// deploy. The file contents can only change with a new deploy, so this is
|
||||
// static. Please note that this is different from the readfile function in the
|
||||
// os package.
|
||||
type ReadFileFunc struct {
|
||||
init *interfaces.Init
|
||||
data *interfaces.FuncData
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
filename *string // the active filename
|
||||
result *string // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *ReadFileFunc) String() string {
|
||||
return ReadFileFuncName
|
||||
}
|
||||
|
||||
// SetData is used by the language to pass our function some code-level context.
|
||||
func (obj *ReadFileFunc) SetData(data *interfaces.FuncData) {
|
||||
obj.data = data
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *ReadFileFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{readFileArgNameFilename}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *ReadFileFunc) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *ReadFileFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // maybe false because the file contents can change
|
||||
Memo: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", readFileArgNameFilename)),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *ReadFileFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
if obj.data == nil {
|
||||
// programming error
|
||||
return fmt.Errorf("missing function data")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *ReadFileFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
for {
|
||||
select {
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // don't infinite loop back
|
||||
continue // no more inputs, but don't return!
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
filename := input.Struct()[readFileArgNameFilename].Str()
|
||||
// TODO: add validation for absolute path?
|
||||
// TODO: add check for empty string
|
||||
if obj.filename != nil && *obj.filename == filename {
|
||||
continue // nothing changed
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
||||
|
||||
result := string(content) // convert to string
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coredeploy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// ReadFileAbsFuncName is the name this function is registered as.
|
||||
ReadFileAbsFuncName = "readfileabs"
|
||||
|
||||
// arg names...
|
||||
readfileArgNameFilename = "filename"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, ReadFileAbsFuncName, func() interfaces.Func { return &ReadFileAbsFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
// ReadFileAbsFunc is a function that reads the full contents from a file in our
|
||||
// deploy. The file contents can only change with a new deploy, so this is
|
||||
// static. In particular, this takes an absolute path relative to the root
|
||||
// deploy. In general, you should use `deploy.readfile` instead. Please note
|
||||
// that this is different from the readfile function in the os package.
|
||||
type ReadFileAbsFunc struct {
|
||||
init *interfaces.Init
|
||||
data *interfaces.FuncData
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
filename *string // the active filename
|
||||
result *string // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *ReadFileAbsFunc) String() string {
|
||||
return ReadFileAbsFuncName
|
||||
}
|
||||
|
||||
// SetData is used by the language to pass our function some code-level context.
|
||||
func (obj *ReadFileAbsFunc) SetData(data *interfaces.FuncData) {
|
||||
obj.data = data
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *ReadFileAbsFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{readfileArgNameFilename}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *ReadFileAbsFunc) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *ReadFileAbsFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // maybe false because the file contents can change
|
||||
Memo: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", readfileArgNameFilename)),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *ReadFileAbsFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
if obj.data == nil {
|
||||
// programming error
|
||||
return fmt.Errorf("missing function data")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *ReadFileAbsFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
for {
|
||||
select {
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // don't infinite loop back
|
||||
continue // no more inputs, but don't return!
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
filename := input.Struct()[readfileArgNameFilename].Str()
|
||||
// TODO: add validation for absolute path?
|
||||
// TODO: add check for empty string
|
||||
if obj.filename != nil && *obj.filename == filename {
|
||||
continue // nothing changed
|
||||
}
|
||||
obj.filename = &filename
|
||||
|
||||
fs, err := obj.init.World.Fs(obj.data.FsURI) // open the remote file system
|
||||
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)
|
||||
}
|
||||
|
||||
result := string(content) // convert to string
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreexample
|
||||
|
||||
import (
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
// Answer is the Answer to Life, the Universe and Everything.
|
||||
const Answer = 42
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "answer", &types.FuncValue{
|
||||
T: types.NewType("func() int"),
|
||||
V: func([]types.Value) (types.Value, error) {
|
||||
return &types.IntValue{V: Answer}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreexample
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "errorbool", &types.FuncValue{
|
||||
T: types.NewType("func(a bool) str"),
|
||||
V: func(input []types.Value) (types.Value, error) {
|
||||
if input[0].Bool() {
|
||||
return nil, fmt.Errorf("we errored on request")
|
||||
}
|
||||
return &types.StrValue{
|
||||
V: "set input to true to generate an error",
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreexample
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "example"
|
||||
)
|
||||
@@ -1,101 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreexample
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// FlipFlopFuncName is the name this fact is registered as. It's still a
|
||||
// Func Name because this is the name space the fact is actually using.
|
||||
FlipFlopFuncName = "flipflop"
|
||||
)
|
||||
|
||||
func init() {
|
||||
facts.ModuleRegister(ModuleName, FlipFlopFuncName, func() facts.Fact { return &FlipFlopFact{} }) // must register the fact and name
|
||||
}
|
||||
|
||||
// FlipFlopFact is a fact which flips a bool repeatedly. This is an example fact
|
||||
// and is not meant for serious computing. This would be better served by a flip
|
||||
// function which you could specify an interval for.
|
||||
type FlipFlopFact struct {
|
||||
init *facts.Init
|
||||
value bool
|
||||
}
|
||||
|
||||
// String returns a simple name for this fact. This is needed so this struct can
|
||||
// satisfy the pgraph.Vertex interface.
|
||||
func (obj *FlipFlopFact) String() string {
|
||||
return FlipFlopFuncName
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal facts that users can use directly.
|
||||
//func (obj *FlipFlopFact) Validate() error {
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *FlipFlopFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Output: types.NewType("bool"),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this fact.
|
||||
func (obj *FlipFlopFact) Init(init *facts.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this fact has over time.
|
||||
func (obj *FlipFlopFact) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // always signal when we're done
|
||||
// TODO: don't hard code 5 sec interval
|
||||
ticker := time.NewTicker(time.Duration(5) * time.Second)
|
||||
|
||||
// streams must generate an initial event on startup
|
||||
startChan := make(chan struct{}) // start signal
|
||||
close(startChan) // kick it off!
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-startChan: // kick the loop once at start
|
||||
startChan = nil // disable
|
||||
case <-ticker.C: // received the timer event
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.BoolValue{ // flip
|
||||
V: obj.value,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
obj.value = !obj.value // flip it
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreexample
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "int2str", &types.FuncValue{
|
||||
T: types.NewType("func(a int) str"),
|
||||
V: func(input []types.Value) (types.Value, error) {
|
||||
return &types.StrValue{
|
||||
V: fmt.Sprintf("%d", input[0].Int()),
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
# Mgmt
|
||||
# Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
# Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
||||
|
||||
# This is a native (mcl) function.
|
||||
func nativeanswer() {
|
||||
42
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corenested
|
||||
|
||||
import (
|
||||
coreexample "github.com/purpleidea/mgmt/lang/funcs/core/example"
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(coreexample.ModuleName+"/"+ModuleName, "hello", &types.FuncValue{
|
||||
T: types.NewType("func() str"),
|
||||
V: Hello,
|
||||
})
|
||||
}
|
||||
|
||||
// Hello returns some string. This is just to test nesting.
|
||||
func Hello(input []types.Value) (types.Value, error) {
|
||||
return &types.StrValue{
|
||||
V: "Hello!",
|
||||
}, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corenested
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "nested"
|
||||
)
|
||||
@@ -1,38 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreexample
|
||||
|
||||
import (
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "plus", &types.FuncValue{
|
||||
T: types.NewType("func(y str, z str) str"),
|
||||
V: Plus,
|
||||
})
|
||||
}
|
||||
|
||||
// Plus returns y + z.
|
||||
func Plus(input []types.Value) (types.Value, error) {
|
||||
y, z := input[0].Str(), input[1].Str()
|
||||
return &types.StrValue{
|
||||
V: y + z,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreexample
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "str2int", &types.FuncValue{
|
||||
T: types.NewType("func(a str) int"),
|
||||
V: func(input []types.Value) (types.Value, error) {
|
||||
var i int64
|
||||
if val, err := strconv.ParseInt(input[0].Str(), 10, 64); err == nil {
|
||||
i = val
|
||||
}
|
||||
return &types.IntValue{
|
||||
V: i,
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
# Mgmt
|
||||
# Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
# Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http:#www.gnu.org/licenses/>.
|
||||
|
||||
# This is a native (mcl) function.
|
||||
func test1() {
|
||||
42
|
||||
}
|
||||
|
||||
# This is a native (mcl) global variable.
|
||||
$test1 = 13
|
||||
|
||||
# This is a native (mcl) class.
|
||||
class test1 {
|
||||
print "inside" {
|
||||
msg => "wow, cool",
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreexample
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// VUMeterFuncName is the name this function is registered as.
|
||||
VUMeterFuncName = "vumeter"
|
||||
|
||||
// arg names...
|
||||
vuMeterArgNameSymbol = "symbol"
|
||||
vuMeterArgNameMultiplier = "multiplier"
|
||||
vuMeterArgNamePeak = "peak"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, VUMeterFuncName, func() interfaces.Func { return &VUMeterFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
// VUMeterFunc is a gimmic function to display a vu meter from the microphone.
|
||||
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
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *VUMeterFunc) String() string {
|
||||
return VUMeterFuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *VUMeterFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{vuMeterArgNameSymbol, vuMeterArgNameMultiplier, vuMeterArgNamePeak}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *VUMeterFunc) Validate() error {
|
||||
check := func(binary string) error {
|
||||
args := []string{"--help"}
|
||||
|
||||
prog := fmt.Sprintf("%s %s", binary, strings.Join(args, " "))
|
||||
|
||||
//obj.init.Logf("running: %s", prog)
|
||||
|
||||
p, err := filepath.EvalSymlinks(binary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: do we need to do the ^C handling?
|
||||
// XXX: is the ^C context cancellation propagating into this correctly?
|
||||
cmd := exec.CommandContext(context.TODO(), p, args...)
|
||||
cmd.Dir = ""
|
||||
cmd.Env = []string{}
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
Pgid: 0,
|
||||
}
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
|
||||
return fmt.Errorf("is %s in your $PATH ?", binary)
|
||||
}
|
||||
|
||||
return errwrap.Wrapf(err, "error running: %s", prog)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// if rec is a symlink, this will error without the above EvalSymlinks!
|
||||
for _, x := range []string{"/usr/bin/rec", "/usr/bin/sox"} {
|
||||
if err := check(x); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *VUMeterFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str, %s int, %s float) str", vuMeterArgNameSymbol, vuMeterArgNameMultiplier, vuMeterArgNamePeak)),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *VUMeterFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *VUMeterFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
ticker := newTicker()
|
||||
defer ticker.Stop()
|
||||
// FIXME: this goChan seems to work better than the ticker :)
|
||||
// this is because we have a ~1sec delay in capturing the value in exec
|
||||
goChan := make(chan struct{})
|
||||
once := &sync.Once{}
|
||||
onceFunc := func() { close(goChan) } // only run once!
|
||||
for {
|
||||
select {
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // don't infinite loop back
|
||||
continue // no more inputs, but don't return!
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
obj.symbol = input.Struct()[vuMeterArgNameSymbol].Str()
|
||||
obj.multiplier = input.Struct()[vuMeterArgNameMultiplier].Int()
|
||||
obj.peak = input.Struct()[vuMeterArgNamePeak].Float()
|
||||
once.Do(onceFunc)
|
||||
continue // we must wrap around and go in through goChan
|
||||
|
||||
//case <-ticker.C: // received the timer event
|
||||
case <-goChan: // triggers constantly
|
||||
|
||||
if obj.last == nil {
|
||||
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
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "cmd failed to run")
|
||||
}
|
||||
|
||||
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 {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTicker() *time.Ticker {
|
||||
return time.NewTicker(time.Duration(1) * time.Second)
|
||||
}
|
||||
|
||||
func extract(data []byte) (float64, error) {
|
||||
const prefix = "Maximum amplitude:"
|
||||
str := string(data)
|
||||
lines := strings.Split(str, "\n")
|
||||
for _, line := range lines {
|
||||
if !strings.HasPrefix(line, prefix) {
|
||||
continue
|
||||
}
|
||||
s := strings.TrimSpace(line[len(prefix):])
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
return 0, fmt.Errorf("could not extract any data")
|
||||
}
|
||||
|
||||
func round(f float64) int {
|
||||
return int(f + math.Copysign(0.5, f))
|
||||
}
|
||||
|
||||
// TODO: make this fancier
|
||||
func visual(symbol string, multiplier int, peak, ratio float64) (string, error) {
|
||||
if ratio > 1 || ratio < 0 {
|
||||
return "", fmt.Errorf("invalid ratio of %f", ratio)
|
||||
}
|
||||
|
||||
x := strings.Repeat(symbol, round(ratio*float64(multiplier)))
|
||||
if x == "" {
|
||||
x += symbol // add a minimum
|
||||
}
|
||||
if ratio > peak {
|
||||
x += " PEAK!!!"
|
||||
}
|
||||
return fmt.Sprintf("(%f):\n%s\n%s", ratio, x, x), nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corefmt
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "fmt"
|
||||
)
|
||||
@@ -1,722 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corefmt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// PrintfFuncName is the name this function is registered as.
|
||||
// FIXME: should this be named sprintf instead?
|
||||
PrintfFuncName = "printf"
|
||||
|
||||
// PrintfAllowNonStaticFormat allows us to use printf when the zeroth
|
||||
// argument (the format string) is not known statically at compile time.
|
||||
// The downside of this is that if it changes while we are running, it
|
||||
// could change from "hello %s" to "hello %d" or "%s %d...". If this
|
||||
// happens we just generate ugly format strings, instead of preventing
|
||||
// it all from even running at all. It's useful to allow dynamic strings
|
||||
// if we were generating custom log messages (for example) where the
|
||||
// format comes from a database lookup or similar. Of course if we knew
|
||||
// that such a lookup could be done quickly and statically (maybe it's a
|
||||
// read from a local key-value config file that's part of our deploy)
|
||||
// then maybe we can do it before unification speculatively.
|
||||
PrintfAllowNonStaticFormat = true
|
||||
|
||||
printfArgNameFormat = "format" // name of the first arg
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, PrintfFuncName, func() interfaces.Func { return &PrintfFunc{} })
|
||||
}
|
||||
|
||||
var _ interfaces.PolyFunc = &PrintfFunc{} // ensure it meets this expectation
|
||||
|
||||
// PrintfFunc is a static polymorphic function that compiles a format string and
|
||||
// returns the output as a string. It bases its output on the values passed in
|
||||
// to it. It examines the type of the arguments at compile time and then
|
||||
// determines the static function signature by parsing the format string and
|
||||
// using that to determine the final function signature. One consequence of this
|
||||
// is that the format string must be a static string which is known at compile
|
||||
// time. This is reasonable, because if it was a reactive, changing string, then
|
||||
// we could expect the type signature to change, which is not allowed in our
|
||||
// statically typed language.
|
||||
type PrintfFunc struct {
|
||||
Type *types.Type // final full type of our function
|
||||
|
||||
init *interfaces.Init
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
result *string // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *PrintfFunc) String() string {
|
||||
return fmt.Sprintf("%s@%p", PrintfFuncName, obj) // be more unique!
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *PrintfFunc) ArgGen(index int) (string, error) {
|
||||
if index == 0 {
|
||||
return printfArgNameFormat, nil
|
||||
}
|
||||
// TODO: if index is big enough that it would return the string in
|
||||
// `printfArgNameFormat` then we should return an error! (Nearly impossible.)
|
||||
return util.NumToAlpha(index - 1), nil
|
||||
}
|
||||
|
||||
// Unify returns the list of invariants that this func produces.
|
||||
func (obj *PrintfFunc) Unify(expr interfaces.Expr) ([]interfaces.Invariant, error) {
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
// func(format string, args... variant) string
|
||||
|
||||
formatName, err := obj.ArgGen(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dummyFormat := &interfaces.ExprAny{} // corresponds to the format type
|
||||
dummyOut := &interfaces.ExprAny{} // corresponds to the out string
|
||||
|
||||
// format arg type of string
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: dummyFormat,
|
||||
Type: types.TypeStr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// return type of string
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: dummyOut,
|
||||
Type: types.TypeStr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: dummyFormat,
|
||||
Expr2: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// dynamic generator function for when the format string is dynamic
|
||||
dynamicFn := func(fnInvariants []interfaces.Invariant, solved map[interfaces.Expr]*types.Type) ([]interfaces.Invariant, error) {
|
||||
for _, invariant := range fnInvariants {
|
||||
// search for this special type of invariant
|
||||
cfavInvar, ok := invariant.(*interfaces.CallFuncArgsValueInvariant)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// did we find the mapping from us to ExprCall ?
|
||||
if cfavInvar.Func != expr {
|
||||
continue
|
||||
}
|
||||
// cfavInvar.Expr is the ExprCall! (the return pointer)
|
||||
// cfavInvar.Args are the args that ExprCall uses!
|
||||
if len(cfavInvar.Args) == 0 {
|
||||
return nil, fmt.Errorf("unable to build function with no args")
|
||||
}
|
||||
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
// add the relationship to the format string arg
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Expr,
|
||||
Expr2: dummyFormat,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// add the relationship to the returned value
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Expr,
|
||||
Expr2: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// add the relationships to the called args
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Args[0],
|
||||
Expr2: dummyFormat,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// first arg must be a string
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: cfavInvar.Args[0],
|
||||
Type: types.TypeStr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// full function
|
||||
mapped := make(map[string]interfaces.Expr)
|
||||
ordered := []string{}
|
||||
for i, x := range cfavInvar.Args {
|
||||
argName, err := obj.ArgGen(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dummyArg := &interfaces.ExprAny{}
|
||||
if i == 0 {
|
||||
dummyArg = dummyFormat // use parent one
|
||||
}
|
||||
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: x, // cfavInvar.Args[i]
|
||||
Expr2: dummyArg,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
mapped[argName] = dummyArg
|
||||
ordered = append(ordered, argName)
|
||||
}
|
||||
|
||||
invar = &interfaces.EqualityWrapFuncInvariant{
|
||||
Expr1: expr, // maps directly to us!
|
||||
Expr2Map: mapped,
|
||||
Expr2Ord: ordered,
|
||||
Expr2Out: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: do we return this relationship with ExprCall?
|
||||
invar = &interfaces.EqualityWrapCallInvariant{
|
||||
// TODO: should Expr1 and Expr2 be reversed???
|
||||
Expr1: cfavInvar.Expr,
|
||||
//Expr2Func: cfavInvar.Func, // same as below
|
||||
Expr2Func: expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: are there any other invariants we should build?
|
||||
return invariants, nil // generator return
|
||||
}
|
||||
// We couldn't tell the solver anything it didn't already know!
|
||||
return nil, fmt.Errorf("couldn't generate new invariants")
|
||||
}
|
||||
|
||||
// generator function
|
||||
fn := func(fnInvariants []interfaces.Invariant, solved map[interfaces.Expr]*types.Type) ([]interfaces.Invariant, error) {
|
||||
for _, invariant := range fnInvariants {
|
||||
// search for this special type of invariant
|
||||
cfavInvar, ok := invariant.(*interfaces.CallFuncArgsValueInvariant)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// did we find the mapping from us to ExprCall ?
|
||||
if cfavInvar.Func != expr {
|
||||
continue
|
||||
}
|
||||
// cfavInvar.Expr is the ExprCall! (the return pointer)
|
||||
// cfavInvar.Args are the args that ExprCall uses!
|
||||
if len(cfavInvar.Args) == 0 {
|
||||
return nil, fmt.Errorf("unable to build function with no args")
|
||||
}
|
||||
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
// add the relationship to the format string arg
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Expr,
|
||||
Expr2: dummyFormat,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// add the relationship to the returned value
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Expr,
|
||||
Expr2: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// add the relationships to the called args
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Args[0],
|
||||
Expr2: dummyFormat,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// first arg must be a string
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: cfavInvar.Args[0],
|
||||
Type: types.TypeStr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// Here we try to see if we know the format string
|
||||
// statically. Perhaps more future Value() invocations
|
||||
// on simple functions will also return before
|
||||
// unification and before the function engine runs.
|
||||
// If we happen to know the value, that's great and we
|
||||
// can unify very easily. If we don't, then we can
|
||||
// decide if we want to allow dynamic format strings.
|
||||
value, err := cfavInvar.Args[0].Value() // is it known?
|
||||
if err != nil {
|
||||
if PrintfAllowNonStaticFormat {
|
||||
return dynamicFn(fnInvariants, solved)
|
||||
}
|
||||
return nil, fmt.Errorf("format string is not known statically")
|
||||
}
|
||||
|
||||
if k := value.Type().Kind; k != types.KindStr {
|
||||
return nil, fmt.Errorf("unable to build function with 0th arg of kind: %s", k)
|
||||
}
|
||||
format := value.Str() // must not panic
|
||||
typList, err := parseFormatToTypeList(format)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not parse format string")
|
||||
}
|
||||
|
||||
// full function
|
||||
mapped := make(map[string]interfaces.Expr)
|
||||
ordered := []string{formatName}
|
||||
mapped[formatName] = dummyFormat
|
||||
|
||||
for i, x := range typList {
|
||||
argName, err := obj.ArgGen(i + 1) // skip 0th
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if argName == printfArgNameFormat {
|
||||
return nil, fmt.Errorf("could not build function with %d args", i+1) // +1 for format arg
|
||||
}
|
||||
|
||||
dummyArg := &interfaces.ExprAny{}
|
||||
// if it's a variant, we can't add the invariant
|
||||
if x != types.TypeVariant {
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: dummyArg,
|
||||
Type: x,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
}
|
||||
|
||||
// catch situations like `printf("%d%d", 42)`
|
||||
if len(cfavInvar.Args) <= i+1 {
|
||||
return nil, fmt.Errorf("more specifiers (%d) than values (%d)", len(typList), len(cfavInvar.Args)-1)
|
||||
}
|
||||
|
||||
// add the relationships to the called args
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Args[i+1],
|
||||
Expr2: dummyArg,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
mapped[argName] = dummyArg
|
||||
ordered = append(ordered, argName)
|
||||
}
|
||||
|
||||
invar = &interfaces.EqualityWrapFuncInvariant{
|
||||
Expr1: expr, // maps directly to us!
|
||||
Expr2Map: mapped,
|
||||
Expr2Ord: ordered,
|
||||
Expr2Out: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: do we return this relationship with ExprCall?
|
||||
invar = &interfaces.EqualityWrapCallInvariant{
|
||||
// TODO: should Expr1 and Expr2 be reversed???
|
||||
Expr1: cfavInvar.Expr,
|
||||
//Expr2Func: cfavInvar.Func, // same as below
|
||||
Expr2Func: expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: are there any other invariants we should build?
|
||||
return invariants, nil // generator return
|
||||
}
|
||||
// We couldn't tell the solver anything it didn't already know!
|
||||
return nil, fmt.Errorf("couldn't generate new invariants")
|
||||
}
|
||||
invar = &interfaces.GeneratorInvariant{
|
||||
Func: fn,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
return invariants, nil
|
||||
}
|
||||
|
||||
// Polymorphisms returns the possible type signature for this function. In this
|
||||
// case, since the number of arguments can be infinite, it returns the final
|
||||
// precise type if it can be gleamed from the format argument. If it cannot, it
|
||||
// is because either the format argument was not known statically, or because it
|
||||
// had an invalid format string.
|
||||
// XXX: This version of the function does not handle any variants returned from
|
||||
// the parseFormatToTypeList helper function.
|
||||
func (obj *PrintfFunc) Polymorphisms(partialType *types.Type, partialValues []types.Value) ([]*types.Type, error) {
|
||||
if partialType == nil || len(partialValues) < 1 {
|
||||
return nil, fmt.Errorf("first argument must be a static format string")
|
||||
}
|
||||
|
||||
if partialType.Out != nil && partialType.Out.Cmp(types.TypeStr) != nil {
|
||||
return nil, fmt.Errorf("return value of printf must be str")
|
||||
}
|
||||
|
||||
ord := partialType.Ord
|
||||
if partialType.Map != nil {
|
||||
if len(ord) < 1 {
|
||||
return nil, fmt.Errorf("must have at least one arg in printf func")
|
||||
}
|
||||
if t, exists := partialType.Map[ord[0]]; exists && t != nil {
|
||||
if t.Cmp(types.TypeStr) != nil {
|
||||
return nil, fmt.Errorf("first arg for printf must be an str")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: we'd like to pre-compute the interpolation if we can, so that
|
||||
// we can run this code properly... for now, we can't, so it's a compile
|
||||
// time error...
|
||||
if partialValues[0] == nil {
|
||||
return nil, fmt.Errorf("could not determine type from format string")
|
||||
}
|
||||
|
||||
format := partialValues[0].Str() // must not panic
|
||||
typList, err := parseFormatToTypeList(format)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not parse format string")
|
||||
}
|
||||
|
||||
typ := &types.Type{
|
||||
Kind: types.KindFunc, // function type
|
||||
Map: make(map[string]*types.Type),
|
||||
Ord: []string{},
|
||||
Out: types.TypeStr,
|
||||
}
|
||||
// add first arg
|
||||
typ.Map[printfArgNameFormat] = types.TypeStr
|
||||
typ.Ord = append(typ.Ord, printfArgNameFormat)
|
||||
|
||||
for i, x := range typList {
|
||||
name := util.NumToAlpha(i) // start with a...
|
||||
if name == printfArgNameFormat {
|
||||
return nil, fmt.Errorf("could not build function with %d args", i+1) // +1 for format arg
|
||||
}
|
||||
|
||||
// if we also had even more partial type information, check it!
|
||||
if t, exists := partialType.Map[ord[i+1]]; exists && t != nil {
|
||||
if err := t.Cmp(x); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "arg %d does not match expected type", i+1)
|
||||
}
|
||||
}
|
||||
|
||||
typ.Map[name] = x
|
||||
typ.Ord = append(typ.Ord, name)
|
||||
}
|
||||
|
||||
return []*types.Type{typ}, nil // return a list with a single possibility
|
||||
}
|
||||
|
||||
// Build takes the now known function signature and stores it so that this
|
||||
// function can appear to be static. That type is used to build our function
|
||||
// statically.
|
||||
func (obj *PrintfFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
if typ.Kind != types.KindFunc {
|
||||
return nil, fmt.Errorf("input type must be of kind func")
|
||||
}
|
||||
if len(typ.Ord) < 1 {
|
||||
return nil, fmt.Errorf("the printf function needs at least one arg")
|
||||
}
|
||||
if typ.Out == nil {
|
||||
return nil, fmt.Errorf("return type of function must be specified")
|
||||
}
|
||||
if typ.Out.Cmp(types.TypeStr) != nil {
|
||||
return nil, fmt.Errorf("return type of function must be an str")
|
||||
}
|
||||
if typ.Map == nil {
|
||||
return nil, fmt.Errorf("invalid input type")
|
||||
}
|
||||
|
||||
t0, exists := typ.Map[typ.Ord[0]]
|
||||
if !exists || t0 == nil {
|
||||
return nil, fmt.Errorf("first arg must be specified")
|
||||
}
|
||||
if t0.Cmp(types.TypeStr) != nil {
|
||||
return nil, fmt.Errorf("first arg for printf must be an str")
|
||||
}
|
||||
|
||||
//newTyp := typ.Copy()
|
||||
newTyp := &types.Type{
|
||||
Kind: typ.Kind, // copy
|
||||
Map: make(map[string]*types.Type), // new
|
||||
Ord: []string{}, // new
|
||||
Out: typ.Out, // copy
|
||||
}
|
||||
for i, x := range typ.Ord { // remap arg names
|
||||
argName, err := obj.ArgGen(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTyp.Map[argName] = typ.Map[x]
|
||||
newTyp.Ord = append(newTyp.Ord, argName)
|
||||
}
|
||||
|
||||
obj.Type = newTyp // function type is now known!
|
||||
return obj.Type, nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *PrintfFunc) Validate() error {
|
||||
if obj.Type == nil { // build must be run first
|
||||
return fmt.Errorf("type is still unspecified")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *PrintfFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Sig: obj.Type,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *PrintfFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *PrintfFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
for {
|
||||
select {
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
return nil // can't output any more
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return err // no errwrap needed b/c helper func
|
||||
}
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return 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 {
|
||||
switch x := value.Type().Kind; x {
|
||||
// FIXME: floats don't print nicely: https://github.com/golang/go/issues/46118
|
||||
case types.KindFloat:
|
||||
// TODO: use formatting flags ?
|
||||
// FIXME: Our String() method in FloatValue doesn't print nicely
|
||||
return value.String()
|
||||
}
|
||||
|
||||
// FIXME: this is just an "easy-out" implementation for now...
|
||||
return fmt.Sprintf("%v", value.Value())
|
||||
|
||||
//switch x := value.Type().Kind; x {
|
||||
//case types.KindBool:
|
||||
// return value.String()
|
||||
//case types.KindStr:
|
||||
// return value.Str() // use this since otherwise it adds " & "
|
||||
//case types.KindInt:
|
||||
// return value.String()
|
||||
//case types.KindFloat:
|
||||
// // TODO: use formatting flags ?
|
||||
// return value.String()
|
||||
//}
|
||||
//panic("unhandled type") // TODO: not fully implemented yet
|
||||
}
|
||||
|
||||
// parseFormatToTypeList takes a format string and returns a list of types that
|
||||
// it expects to use in the order found in the format string. This can also
|
||||
// handle the %v special variant type in the format string.
|
||||
// FIXME: add support for more types, and add tests!
|
||||
func parseFormatToTypeList(format string) ([]*types.Type, error) {
|
||||
typList := []*types.Type{}
|
||||
inType := false
|
||||
for i := 0; i < len(format); i++ {
|
||||
|
||||
// some normal char...
|
||||
if !inType && format[i] != '%' {
|
||||
continue
|
||||
}
|
||||
|
||||
// in a type or we're a %
|
||||
if format[i] == '%' {
|
||||
if inType {
|
||||
// it's a %%
|
||||
inType = false
|
||||
} else {
|
||||
// start looking for type specification!
|
||||
inType = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// we must be in a type
|
||||
switch format[i] {
|
||||
case 't':
|
||||
typList = append(typList, types.TypeBool)
|
||||
case 's':
|
||||
typList = append(typList, types.TypeStr)
|
||||
case 'd':
|
||||
typList = append(typList, types.TypeInt)
|
||||
|
||||
// TODO: parse fancy formats like %0.2f and stuff
|
||||
case 'f':
|
||||
typList = append(typList, types.TypeFloat)
|
||||
|
||||
// FIXME: add fancy types like: %[]s, %[]f, %{s:f}, etc...
|
||||
|
||||
// special!
|
||||
case 'v':
|
||||
typList = append(typList, types.TypeVariant)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid format string at %d", i)
|
||||
}
|
||||
inType = false // done
|
||||
}
|
||||
|
||||
return typList, nil
|
||||
}
|
||||
|
||||
// compileFormatToString takes a format string and a list of values and returns
|
||||
// the compiled/templated output. This can also handle the %v special variant
|
||||
// type in the format string. Of course the corresponding value to those %v
|
||||
// entries must have a static, fixed, precise type.
|
||||
// FIXME: add support for more types, and add tests!
|
||||
// XXX: depending on PrintfAllowNonStaticFormat, we should NOT error if we have
|
||||
// a mismatch between the format string and the available args. Return similar
|
||||
// to golang's EXTRA/MISSING, eg: https://pkg.go.dev/fmt#hdr-Format_errors
|
||||
func compileFormatToString(format string, values []types.Value) (string, error) {
|
||||
output := ""
|
||||
ix := 0
|
||||
inType := false
|
||||
for i := 0; i < len(format); i++ {
|
||||
|
||||
// some normal char...
|
||||
if !inType && format[i] != '%' {
|
||||
output += string(format[i])
|
||||
continue
|
||||
}
|
||||
|
||||
// in a type or we're a %
|
||||
if format[i] == '%' {
|
||||
if inType {
|
||||
// it's a %%
|
||||
output += string(format[i])
|
||||
inType = false
|
||||
} else {
|
||||
// start looking for type specification!
|
||||
inType = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// we must be in a type
|
||||
var typ *types.Type
|
||||
switch format[i] {
|
||||
case 't':
|
||||
typ = types.TypeBool
|
||||
case 's':
|
||||
typ = types.TypeStr
|
||||
case 'd':
|
||||
typ = types.TypeInt
|
||||
|
||||
// TODO: parse fancy formats like %0.2f and stuff
|
||||
case 'f':
|
||||
typ = types.TypeFloat
|
||||
|
||||
// FIXME: add fancy types like: %[]s, %[]f, %{s:f}, etc...
|
||||
|
||||
case 'v':
|
||||
typ = types.TypeVariant
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("invalid format string at %d", i)
|
||||
}
|
||||
inType = false // done
|
||||
|
||||
if ix >= len(values) {
|
||||
// programming error, probably in type unification
|
||||
return "", fmt.Errorf("more specifiers (%d) than values (%d)", ix+1, len(values))
|
||||
}
|
||||
|
||||
// check the type (if not a variant) matches what we have...
|
||||
if typ == types.TypeVariant {
|
||||
if values[ix].Type() == nil {
|
||||
return "", fmt.Errorf("unexpected nil type")
|
||||
}
|
||||
} else if err := typ.Cmp(values[ix].Type()); err != nil {
|
||||
return "", errwrap.Wrapf(err, "unexpected type")
|
||||
}
|
||||
|
||||
output += valueToString(values[ix])
|
||||
ix++ // consume one value
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
# This file is used by github.com/purpleidea/mgmt/lang/funcs/funcgen/ to
|
||||
# generate mcl functions.
|
||||
packages:
|
||||
- name: html
|
||||
- name: math
|
||||
- name: math/rand
|
||||
alias: rand
|
||||
- name: os
|
||||
- name: os/exec
|
||||
alias: exec
|
||||
- name: path
|
||||
- name: path/filepath
|
||||
alias: filepath
|
||||
- name: runtime
|
||||
- name: strconv
|
||||
- name: strings
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreiter
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "iter"
|
||||
)
|
||||
@@ -1,811 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreiter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/funcs/structs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/lang/types/full"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// MapFuncName is the name this function is registered as.
|
||||
MapFuncName = "map"
|
||||
|
||||
// arg names...
|
||||
mapArgNameInputs = "inputs"
|
||||
mapArgNameFunction = "function"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, MapFuncName, func() interfaces.Func { return &MapFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
var _ interfaces.PolyFunc = &MapFunc{} // ensure it meets this expectation
|
||||
|
||||
// MapFunc is the standard map iterator function that applies a function to each
|
||||
// element in a list. It returns a list with the same number of elements as the
|
||||
// input list. There is no requirement that the element output type be the same
|
||||
// as the input element type. This implements the signature: `func(inputs []T1,
|
||||
// function func(T1) T2) []T2` instead of the alternate with the two input args
|
||||
// swapped, because while the latter is more common with languages that support
|
||||
// partial function application, the former variant that we implemented is much
|
||||
// more readable when using an inline lambda.
|
||||
// TODO: should we extend this to support iterating over map's and structs, or
|
||||
// should that be a different function? I think a different function is best.
|
||||
type MapFunc struct {
|
||||
Type *types.Type // this is the type of the elements in our input list
|
||||
RType *types.Type // this is the type of the elements in our output list
|
||||
|
||||
init *interfaces.Init
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
lastFuncValue *full.FuncValue // remember the last function value
|
||||
lastInputListLength int // remember the last input list length
|
||||
|
||||
inputListType *types.Type
|
||||
outputListType *types.Type
|
||||
|
||||
// outputChan is an initially-nil channel from which we receive output
|
||||
// lists from the subgraph. This channel is reset when the subgraph is
|
||||
// recreated.
|
||||
outputChan chan types.Value
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *MapFunc) String() string {
|
||||
return MapFuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *MapFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{mapArgNameInputs, mapArgNameFunction} // inverted for pretty!
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Unify returns the list of invariants that this func produces.
|
||||
func (obj *MapFunc) Unify(expr interfaces.Expr) ([]interfaces.Invariant, error) {
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
// func(inputs []T1, function func(T1) T2) []T2
|
||||
|
||||
inputsName, err := obj.ArgGen(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
functionName, err := obj.ArgGen(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dummyArgList := &interfaces.ExprAny{} // corresponds to the input list
|
||||
dummyArgFunc := &interfaces.ExprAny{} // corresponds to the input func
|
||||
dummyOutList := &interfaces.ExprAny{} // corresponds to the output list
|
||||
|
||||
t1Expr := &interfaces.ExprAny{} // corresponds to the t1 type
|
||||
t2Expr := &interfaces.ExprAny{} // corresponds to the t2 type
|
||||
|
||||
invar = &interfaces.EqualityWrapListInvariant{
|
||||
Expr1: dummyArgList,
|
||||
Expr2Val: t1Expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
invar = &interfaces.EqualityWrapListInvariant{
|
||||
Expr1: dummyOutList,
|
||||
Expr2Val: t2Expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// full function
|
||||
mapped := make(map[string]interfaces.Expr)
|
||||
ordered := []string{inputsName, functionName}
|
||||
mapped[inputsName] = dummyArgList
|
||||
mapped[functionName] = dummyArgFunc
|
||||
|
||||
invar = &interfaces.EqualityWrapFuncInvariant{
|
||||
Expr1: expr, // maps directly to us!
|
||||
Expr2Map: mapped,
|
||||
Expr2Ord: ordered,
|
||||
Expr2Out: dummyOutList,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// relationship between t1 and t2
|
||||
argName := util.NumToAlpha(0) // XXX: does the arg name matter?
|
||||
invar = &interfaces.EqualityWrapFuncInvariant{
|
||||
Expr1: dummyArgFunc,
|
||||
Expr2Map: map[string]interfaces.Expr{
|
||||
argName: t1Expr,
|
||||
},
|
||||
Expr2Ord: []string{argName},
|
||||
Expr2Out: t2Expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// generator function
|
||||
fn := func(fnInvariants []interfaces.Invariant, solved map[interfaces.Expr]*types.Type) ([]interfaces.Invariant, error) {
|
||||
for _, invariant := range fnInvariants {
|
||||
// search for this special type of invariant
|
||||
cfavInvar, ok := invariant.(*interfaces.CallFuncArgsValueInvariant)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// did we find the mapping from us to ExprCall ?
|
||||
if cfavInvar.Func != expr {
|
||||
continue
|
||||
}
|
||||
// cfavInvar.Expr is the ExprCall! (the return pointer)
|
||||
// cfavInvar.Args are the args that ExprCall uses!
|
||||
if l := len(cfavInvar.Args); l != 2 {
|
||||
return nil, fmt.Errorf("unable to build function with %d args", l)
|
||||
}
|
||||
// we must have exactly two args
|
||||
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
// add the relationship to the returned value
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Expr,
|
||||
Expr2: dummyOutList,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// add the relationships to the called args
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Args[0],
|
||||
Expr2: dummyArgList,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Args[1],
|
||||
Expr2: dummyArgFunc,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
invar = &interfaces.EqualityWrapListInvariant{
|
||||
Expr1: cfavInvar.Args[0],
|
||||
Expr2Val: t1Expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
invar = &interfaces.EqualityWrapListInvariant{
|
||||
Expr1: cfavInvar.Expr,
|
||||
Expr2Val: t2Expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
var t1, t2 *types.Type // as seen in our sig's
|
||||
var foundArgName string = util.NumToAlpha(0) // XXX: is this a hack?
|
||||
|
||||
// validateArg0 checks: inputs []T1
|
||||
validateArg0 := func(typ *types.Type) error {
|
||||
if typ == nil { // unknown so far
|
||||
return nil
|
||||
}
|
||||
if typ.Kind != types.KindList {
|
||||
return fmt.Errorf("input type must be of kind list")
|
||||
}
|
||||
if typ.Val == nil { // TODO: is this okay to add?
|
||||
return nil // unknown so far
|
||||
}
|
||||
if t1 == nil { // t1 is not yet known, so done!
|
||||
t1 = typ.Val // learn!
|
||||
return nil
|
||||
}
|
||||
//if err := typ.Val.Cmp(t1); err != nil {
|
||||
// return errwrap.Wrapf(err, "input type was inconsistent")
|
||||
//}
|
||||
//return nil
|
||||
return errwrap.Wrapf(typ.Val.Cmp(t1), "input type was inconsistent")
|
||||
}
|
||||
|
||||
// validateArg1 checks: func(T1) T2
|
||||
validateArg1 := func(typ *types.Type) error {
|
||||
if typ == nil { // unknown so far
|
||||
return nil
|
||||
}
|
||||
if typ.Kind != types.KindFunc {
|
||||
return fmt.Errorf("input type must be of kind func")
|
||||
}
|
||||
if len(typ.Map) != 1 || len(typ.Ord) != 1 {
|
||||
return fmt.Errorf("input type func must have only one input arg")
|
||||
}
|
||||
arg, exists := typ.Map[typ.Ord[0]]
|
||||
if !exists {
|
||||
// programming error
|
||||
return fmt.Errorf("input type func first arg is missing")
|
||||
}
|
||||
|
||||
if t1 != nil {
|
||||
if err := arg.Cmp(t1); err != nil {
|
||||
return errwrap.Wrapf(err, "input type func arg was inconsistent")
|
||||
}
|
||||
}
|
||||
if t2 != nil {
|
||||
if err := typ.Out.Cmp(t2); err != nil {
|
||||
return errwrap.Wrapf(err, "input type func output was inconsistent")
|
||||
}
|
||||
}
|
||||
|
||||
// in case they weren't set already
|
||||
t1 = arg
|
||||
t2 = typ.Out
|
||||
foundArgName = typ.Ord[0] // we found a name!
|
||||
return nil
|
||||
}
|
||||
|
||||
if typ, err := cfavInvar.Args[0].Type(); err == nil { // is it known?
|
||||
// this sets t1 and t2 on success if it learned
|
||||
if err := validateArg0(typ); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "first input arg type is inconsistent")
|
||||
}
|
||||
}
|
||||
if typ, exists := solved[cfavInvar.Args[0]]; exists { // alternate way to lookup type
|
||||
// this sets t1 and t2 on success if it learned
|
||||
if err := validateArg0(typ); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "first input arg type is inconsistent")
|
||||
}
|
||||
}
|
||||
// XXX: since we might not yet have association to this
|
||||
// expression (dummyArgList) yet, we could consider
|
||||
// returning some of the invariants and a new generator
|
||||
// and hoping we get a hit on this one the next time.
|
||||
if typ, exists := solved[dummyArgList]; exists { // alternate way to lookup type
|
||||
// this sets t1 and t2 on success if it learned
|
||||
if err := validateArg0(typ); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "first input arg type is inconsistent")
|
||||
}
|
||||
}
|
||||
|
||||
if typ, err := cfavInvar.Args[1].Type(); err == nil { // is it known?
|
||||
// this sets t1 and t2 on success if it learned
|
||||
if err := validateArg1(typ); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "second input arg type is inconsistent")
|
||||
}
|
||||
}
|
||||
if typ, exists := solved[cfavInvar.Args[1]]; exists { // alternate way to lookup type
|
||||
// this sets t1 and t2 on success if it learned
|
||||
if err := validateArg1(typ); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "second input arg type is inconsistent")
|
||||
}
|
||||
}
|
||||
// XXX: since we might not yet have association to this
|
||||
// expression (dummyArgFunc) yet, we could consider
|
||||
// returning some of the invariants and a new generator
|
||||
// and hoping we get a hit on this one the next time.
|
||||
if typ, exists := solved[dummyArgFunc]; exists { // alternate way to lookup type
|
||||
// this sets t1 and t2 on success if it learned
|
||||
if err := validateArg1(typ); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "second input arg type is inconsistent")
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: look for t1 and t2 in other places?
|
||||
|
||||
if t1 != nil {
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: t1Expr,
|
||||
Type: t1,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
}
|
||||
|
||||
if t1 != nil && t2 != nil {
|
||||
// TODO: if the argName matters, do it here...
|
||||
_ = foundArgName
|
||||
//argName := foundArgName // XXX: is this a hack?
|
||||
//mapped := make(map[string]interfaces.Expr)
|
||||
//ordered := []string{argName}
|
||||
//mapped[argName] = t1Expr
|
||||
//invar = &interfaces.EqualityWrapFuncInvariant{
|
||||
// Expr1: dummyArgFunc,
|
||||
// Expr2Map: mapped,
|
||||
// Expr2Ord: ordered,
|
||||
// Expr2Out: t2Expr,
|
||||
//}
|
||||
//invariants = append(invariants, invar)
|
||||
}
|
||||
|
||||
// note, currently, we can't learn t2 without t1
|
||||
if t2 != nil {
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: t2Expr,
|
||||
Type: t2,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
}
|
||||
|
||||
// We need to require this knowledge to continue!
|
||||
if t1 == nil || t2 == nil {
|
||||
return nil, fmt.Errorf("not enough known about function signature")
|
||||
}
|
||||
|
||||
// TODO: do we return this relationship with ExprCall?
|
||||
invar = &interfaces.EqualityWrapCallInvariant{
|
||||
// TODO: should Expr1 and Expr2 be reversed???
|
||||
Expr1: cfavInvar.Expr,
|
||||
//Expr2Func: cfavInvar.Func, // same as below
|
||||
Expr2Func: expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: are there any other invariants we should build?
|
||||
return invariants, nil // generator return
|
||||
}
|
||||
// We couldn't tell the solver anything it didn't already know!
|
||||
return nil, fmt.Errorf("couldn't generate new invariants")
|
||||
}
|
||||
invar = &interfaces.GeneratorInvariant{
|
||||
Func: fn,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
return invariants, nil
|
||||
}
|
||||
|
||||
// Polymorphisms returns the list of possible function signatures available for
|
||||
// this static polymorphic function. It relies on type and value hints to limit
|
||||
// the number of returned possibilities.
|
||||
func (obj *MapFunc) Polymorphisms(partialType *types.Type, partialValues []types.Value) ([]*types.Type, error) {
|
||||
// XXX: double check that this works with `func([]int, func(int) str) []str` (when types change!)
|
||||
// TODO: look at partialValues to gleam type information?
|
||||
if partialType == nil {
|
||||
return nil, fmt.Errorf("zero type information given")
|
||||
}
|
||||
if partialType.Kind != types.KindFunc {
|
||||
return nil, fmt.Errorf("partial type must be of kind func")
|
||||
}
|
||||
|
||||
// If we figure out both of these two types, we'll know the full type...
|
||||
var t1 *types.Type // type
|
||||
var t2 *types.Type // rtype
|
||||
|
||||
// Look at the returned "out" type if it's known.
|
||||
if tOut := partialType.Out; tOut != nil {
|
||||
if tOut.Kind != types.KindList {
|
||||
return nil, fmt.Errorf("partial out type must be of kind list")
|
||||
}
|
||||
t2 = tOut.Val // found (if not nil)
|
||||
}
|
||||
|
||||
ord := partialType.Ord
|
||||
if partialType.Map != nil {
|
||||
// TODO: is it okay to assume this?
|
||||
//if len(ord) == 0 {
|
||||
// return nil, fmt.Errorf("must have two args in func")
|
||||
//}
|
||||
if len(ord) != 2 {
|
||||
return nil, fmt.Errorf("must have two args in func")
|
||||
}
|
||||
|
||||
if tInputs, exists := partialType.Map[ord[0]]; exists && tInputs != nil {
|
||||
if tInputs.Kind != types.KindList {
|
||||
return nil, fmt.Errorf("first input arg must be of kind list")
|
||||
}
|
||||
t1 = tInputs.Val // found (if not nil)
|
||||
}
|
||||
|
||||
if tFunction, exists := partialType.Map[ord[1]]; exists && tFunction != nil {
|
||||
if tFunction.Kind != types.KindFunc {
|
||||
return nil, fmt.Errorf("second input arg must be a func")
|
||||
}
|
||||
|
||||
fOrd := tFunction.Ord
|
||||
if fMap := tFunction.Map; fMap != nil {
|
||||
if len(fOrd) != 1 {
|
||||
return nil, fmt.Errorf("second input arg func, must have only one arg")
|
||||
}
|
||||
if fIn, exists := fMap[fOrd[0]]; exists && fIn != nil {
|
||||
if err := fIn.Cmp(t1); t1 != nil && err != nil {
|
||||
return nil, errwrap.Wrapf(err, "first arg function in type is inconsistent")
|
||||
}
|
||||
t1 = fIn // found
|
||||
}
|
||||
}
|
||||
|
||||
if fOut := tFunction.Out; fOut != nil {
|
||||
if err := fOut.Cmp(t2); t2 != nil && err != nil {
|
||||
return nil, errwrap.Wrapf(err, "second arg function out type is inconsistent")
|
||||
}
|
||||
t2 = fOut // found
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if t1 == nil || t2 == nil {
|
||||
return nil, fmt.Errorf("not enough type information given")
|
||||
}
|
||||
tI := types.NewType(fmt.Sprintf("[]%s", t1.String())) // in
|
||||
tO := types.NewType(fmt.Sprintf("[]%s", t2.String())) // out
|
||||
tF := types.NewType(fmt.Sprintf("func(%s) %s", t1.String(), t2.String()))
|
||||
s := fmt.Sprintf("func(%s %s, %s %s) %s", mapArgNameInputs, tI, mapArgNameFunction, tF, tO)
|
||||
typ := types.NewType(s) // yay!
|
||||
|
||||
// TODO: type check that the partialValues are compatible
|
||||
|
||||
return []*types.Type{typ}, nil // solved!
|
||||
}
|
||||
|
||||
// Build is run to turn the polymorphic, undetermined function, into the
|
||||
// specific statically typed version. It is usually run after Unify completes,
|
||||
// and must be run before Info() and any of the other Func interface methods are
|
||||
// used. This function is idempotent, as long as the arg isn't changed between
|
||||
// runs.
|
||||
func (obj *MapFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
// typ is the KindFunc signature we're trying to build...
|
||||
if typ.Kind != types.KindFunc {
|
||||
return nil, fmt.Errorf("input type must be of kind func")
|
||||
}
|
||||
|
||||
if len(typ.Ord) != 2 {
|
||||
return nil, fmt.Errorf("the map needs exactly two args")
|
||||
}
|
||||
if typ.Map == nil {
|
||||
return nil, fmt.Errorf("the map is nil")
|
||||
}
|
||||
|
||||
tInputs, exists := typ.Map[typ.Ord[0]]
|
||||
if !exists || tInputs == nil {
|
||||
return nil, fmt.Errorf("first argument was missing")
|
||||
}
|
||||
tFunction, exists := typ.Map[typ.Ord[1]]
|
||||
if !exists || tFunction == nil {
|
||||
return nil, fmt.Errorf("second argument was missing")
|
||||
}
|
||||
|
||||
if tInputs.Kind != types.KindList {
|
||||
return nil, fmt.Errorf("first argument must be of kind list")
|
||||
}
|
||||
if tFunction.Kind != types.KindFunc {
|
||||
return nil, fmt.Errorf("second argument must be of kind func")
|
||||
}
|
||||
|
||||
if typ.Out == nil {
|
||||
return nil, fmt.Errorf("return type must be specified")
|
||||
}
|
||||
if typ.Out.Kind != types.KindList {
|
||||
return nil, fmt.Errorf("return argument must be a list")
|
||||
}
|
||||
|
||||
if len(tFunction.Ord) != 1 {
|
||||
return nil, fmt.Errorf("the functions map needs exactly one arg")
|
||||
}
|
||||
if tFunction.Map == nil {
|
||||
return nil, fmt.Errorf("the functions map is nil")
|
||||
}
|
||||
tArg, exists := tFunction.Map[tFunction.Ord[0]]
|
||||
if !exists || tArg == nil {
|
||||
return nil, fmt.Errorf("the functions first argument was missing")
|
||||
}
|
||||
if err := tArg.Cmp(tInputs.Val); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "the functions arg type must match the input list contents type")
|
||||
}
|
||||
|
||||
if tFunction.Out == nil {
|
||||
return nil, fmt.Errorf("return type of function must be specified")
|
||||
}
|
||||
if err := tFunction.Out.Cmp(typ.Out.Val); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "return type of function must match returned list contents type")
|
||||
}
|
||||
|
||||
obj.Type = tInputs.Val // or tArg
|
||||
obj.RType = tFunction.Out // or typ.Out.Val
|
||||
|
||||
return obj.sig(), nil
|
||||
}
|
||||
|
||||
// Validate tells us if the input struct takes a valid form.
|
||||
func (obj *MapFunc) Validate() error {
|
||||
if obj.Type == nil || obj.RType == nil {
|
||||
return fmt.Errorf("type is not yet known")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself. Build must be called before this
|
||||
// will return correct data.
|
||||
func (obj *MapFunc) Info() *interfaces.Info {
|
||||
sig := obj.sig() // helper
|
||||
|
||||
return &interfaces.Info{
|
||||
Pure: false, // TODO: what if the input function isn't pure?
|
||||
Memo: false,
|
||||
Sig: sig,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// helper
|
||||
func (obj *MapFunc) sig() *types.Type {
|
||||
// TODO: what do we put if this is unknown?
|
||||
tIi := types.TypeVariant
|
||||
if obj.Type != nil {
|
||||
tIi = obj.Type
|
||||
}
|
||||
tI := types.NewType(fmt.Sprintf("[]%s", tIi.String())) // type of 2nd arg
|
||||
|
||||
tOi := types.TypeVariant
|
||||
if obj.RType != nil {
|
||||
tOi = obj.RType
|
||||
}
|
||||
tO := types.NewType(fmt.Sprintf("[]%s", tOi.String())) // return type
|
||||
|
||||
// type of 1st arg (the function)
|
||||
tF := types.NewType(fmt.Sprintf("func(%s %s) %s", "name-which-can-vary-over-time", tIi.String(), tOi.String()))
|
||||
|
||||
s := fmt.Sprintf("func(%s %s, %s %s) %s", mapArgNameInputs, tI, mapArgNameFunction, tF, tO)
|
||||
return types.NewType(s) // yay!
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *MapFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
obj.lastFuncValue = nil
|
||||
obj.lastInputListLength = -1
|
||||
|
||||
obj.inputListType = types.NewType(fmt.Sprintf("[]%s", obj.Type))
|
||||
obj.outputListType = types.NewType(fmt.Sprintf("[]%s", obj.RType))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *MapFunc) Stream(ctx context.Context) error {
|
||||
// Every time the FuncValue or the length of the list changes, recreate the
|
||||
// subgraph, by calling the FuncValue N times on N nodes, each of which
|
||||
// extracts one of the N values in the list.
|
||||
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
|
||||
// A Func to send input lists to the subgraph. The Txn.Erase() call ensures
|
||||
// that this Func is not removed when the subgraph is recreated, so that the
|
||||
// function graph can propagate the last list we received to the subgraph.
|
||||
inputChan := make(chan types.Value)
|
||||
subgraphInput := &structs.ChannelBasedSourceFunc{
|
||||
Name: "subgraphInput",
|
||||
Source: obj,
|
||||
Chan: inputChan,
|
||||
Type: obj.inputListType,
|
||||
}
|
||||
obj.init.Txn.AddVertex(subgraphInput)
|
||||
if err := obj.init.Txn.Commit(); err != nil {
|
||||
return errwrap.Wrapf(err, "commit error in Stream")
|
||||
}
|
||||
obj.init.Txn.Erase() // prevent the next Reverse() from removing subgraphInput
|
||||
defer func() {
|
||||
close(inputChan)
|
||||
obj.init.Txn.Reverse()
|
||||
obj.init.Txn.DeleteVertex(subgraphInput)
|
||||
obj.init.Txn.Commit()
|
||||
}()
|
||||
|
||||
obj.outputChan = nil
|
||||
|
||||
canReceiveMoreFuncValuesOrInputLists := true
|
||||
canReceiveMoreOutputLists := true
|
||||
for {
|
||||
|
||||
if !canReceiveMoreFuncValuesOrInputLists && !canReceiveMoreOutputLists {
|
||||
//break
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // block looping back here
|
||||
canReceiveMoreFuncValuesOrInputLists = false
|
||||
continue
|
||||
}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
value, exists := input.Struct()[mapArgNameFunction]
|
||||
if !exists {
|
||||
return fmt.Errorf("programming error, can't find edge")
|
||||
}
|
||||
|
||||
newFuncValue, ok := value.(*full.FuncValue)
|
||||
if !ok {
|
||||
return fmt.Errorf("programming error, can't convert to *FuncValue")
|
||||
}
|
||||
|
||||
newInputList, exists := input.Struct()[mapArgNameInputs]
|
||||
if !exists {
|
||||
return fmt.Errorf("programming error, can't find edge")
|
||||
}
|
||||
|
||||
// If we have a new function or the length of the input
|
||||
// list has changed, then we need to replace the
|
||||
// subgraph with a new one that uses the new function
|
||||
// the correct number of times.
|
||||
|
||||
// It's important to have this compare step to avoid
|
||||
// redundant graph replacements which slow things down,
|
||||
// but also cause the engine to lock, which can preempt
|
||||
// the process scheduler, which can cause duplicate or
|
||||
// unnecessary re-sending of values here, which causes
|
||||
// the whole process to repeat ad-nauseum.
|
||||
n := len(newInputList.List())
|
||||
if newFuncValue != obj.lastFuncValue || n != obj.lastInputListLength {
|
||||
obj.lastFuncValue = newFuncValue
|
||||
obj.lastInputListLength = n
|
||||
// replaceSubGraph uses the above two values
|
||||
if err := obj.replaceSubGraph(subgraphInput); err != nil {
|
||||
return errwrap.Wrapf(err, "could not replace subgraph")
|
||||
}
|
||||
canReceiveMoreOutputLists = true
|
||||
}
|
||||
|
||||
// send the new input list to the subgraph
|
||||
select {
|
||||
case inputChan <- newInputList:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
case outputList, ok := <-obj.outputChan:
|
||||
// send the new output list downstream
|
||||
if !ok {
|
||||
obj.outputChan = nil
|
||||
canReceiveMoreOutputLists = false
|
||||
continue
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- outputList:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (obj *MapFunc) replaceSubGraph(subgraphInput interfaces.Func) error {
|
||||
// Create a subgraph which splits the input list into 'n' nodes, applies
|
||||
// 'newFuncValue' to each, then combines the 'n' outputs back into a list.
|
||||
//
|
||||
// Here is what the subgraph looks like:
|
||||
//
|
||||
// digraph {
|
||||
// "subgraphInput" -> "inputElemFunc0"
|
||||
// "subgraphInput" -> "inputElemFunc1"
|
||||
// "subgraphInput" -> "inputElemFunc2"
|
||||
//
|
||||
// "inputElemFunc0" -> "outputElemFunc0"
|
||||
// "inputElemFunc1" -> "outputElemFunc1"
|
||||
// "inputElemFunc2" -> "outputElemFunc2"
|
||||
//
|
||||
// "outputElemFunc0" -> "outputListFunc"
|
||||
// "outputElemFunc1" -> "outputListFunc"
|
||||
// "outputElemFunc1" -> "outputListFunc"
|
||||
//
|
||||
// "outputListFunc" -> "subgraphOutput"
|
||||
// }
|
||||
|
||||
const channelBasedSinkFuncArgNameEdgeName = structs.ChannelBasedSinkFuncArgName // XXX: not sure if the specific name matters.
|
||||
|
||||
// delete the old subgraph
|
||||
if err := obj.init.Txn.Reverse(); err != nil {
|
||||
return errwrap.Wrapf(err, "could not Reverse")
|
||||
}
|
||||
|
||||
// create the new subgraph
|
||||
|
||||
obj.outputChan = make(chan types.Value)
|
||||
subgraphOutput := &structs.ChannelBasedSinkFunc{
|
||||
Name: "subgraphOutput",
|
||||
Target: obj,
|
||||
EdgeName: channelBasedSinkFuncArgNameEdgeName,
|
||||
Chan: obj.outputChan,
|
||||
Type: obj.outputListType,
|
||||
}
|
||||
obj.init.Txn.AddVertex(subgraphOutput)
|
||||
|
||||
m := make(map[string]*types.Type)
|
||||
ord := []string{}
|
||||
for i := 0; i < obj.lastInputListLength; i++ {
|
||||
argName := fmt.Sprintf("outputElem%d", i)
|
||||
m[argName] = obj.RType
|
||||
ord = append(ord, argName)
|
||||
}
|
||||
typ := &types.Type{
|
||||
Kind: types.KindFunc,
|
||||
Map: m,
|
||||
Ord: ord,
|
||||
Out: obj.outputListType,
|
||||
}
|
||||
outputListFunc := structs.SimpleFnToDirectFunc(
|
||||
"mapOutputList",
|
||||
&types.FuncValue{
|
||||
V: func(args []types.Value) (types.Value, error) {
|
||||
listValue := &types.ListValue{
|
||||
V: args,
|
||||
T: obj.outputListType,
|
||||
}
|
||||
|
||||
return listValue, nil
|
||||
},
|
||||
T: typ,
|
||||
},
|
||||
)
|
||||
|
||||
obj.init.Txn.AddVertex(outputListFunc)
|
||||
obj.init.Txn.AddEdge(outputListFunc, subgraphOutput, &interfaces.FuncEdge{
|
||||
Args: []string{channelBasedSinkFuncArgNameEdgeName},
|
||||
})
|
||||
|
||||
for i := 0; i < obj.lastInputListLength; i++ {
|
||||
i := i
|
||||
inputElemFunc := structs.SimpleFnToDirectFunc(
|
||||
fmt.Sprintf("mapInputElem[%d]", i),
|
||||
&types.FuncValue{
|
||||
V: func(args []types.Value) (types.Value, error) {
|
||||
if len(args) != 1 {
|
||||
return nil, fmt.Errorf("inputElemFunc: expected a single argument")
|
||||
}
|
||||
arg := args[0]
|
||||
|
||||
list, ok := arg.(*types.ListValue)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("inputElemFunc: expected a ListValue argument")
|
||||
}
|
||||
|
||||
return list.List()[i], nil
|
||||
},
|
||||
T: types.NewType(fmt.Sprintf("func(inputList %s) %s", obj.inputListType, obj.Type)),
|
||||
},
|
||||
)
|
||||
obj.init.Txn.AddVertex(inputElemFunc)
|
||||
|
||||
outputElemFunc, err := obj.lastFuncValue.Call(obj.init.Txn, []interfaces.Func{inputElemFunc})
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not call obj.lastFuncValue.Call()")
|
||||
}
|
||||
|
||||
obj.init.Txn.AddEdge(subgraphInput, inputElemFunc, &interfaces.FuncEdge{
|
||||
Args: []string{"inputList"},
|
||||
})
|
||||
obj.init.Txn.AddEdge(outputElemFunc, outputListFunc, &interfaces.FuncEdge{
|
||||
Args: []string{fmt.Sprintf("outputElem%d", i)},
|
||||
})
|
||||
}
|
||||
|
||||
return obj.init.Txn.Commit()
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simplepoly"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simplepoly.Register("len", []*types.FuncValue{
|
||||
{
|
||||
T: types.NewType("func(str) int"),
|
||||
V: Len,
|
||||
},
|
||||
{
|
||||
T: types.NewType("func([]variant) int"),
|
||||
V: Len,
|
||||
},
|
||||
{
|
||||
T: types.NewType("func(map{variant: variant}) int"),
|
||||
V: Len,
|
||||
},
|
||||
// TODO: should we add support for struct or func lengths?
|
||||
})
|
||||
}
|
||||
|
||||
// Len returns the number of elements in a list or the number of key pairs in a
|
||||
// map. It can operate on either of these types.
|
||||
func Len(input []types.Value) (types.Value, error) {
|
||||
var length int
|
||||
switch k := input[0].Type().Kind; k {
|
||||
case types.KindStr:
|
||||
length = len(input[0].Str())
|
||||
case types.KindList:
|
||||
length = len(input[0].List())
|
||||
case types.KindMap:
|
||||
length = len(input[0].Map())
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported kind: %+v", k)
|
||||
}
|
||||
|
||||
return &types.IntValue{
|
||||
V: int64(length),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coremath
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simplepoly"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
typInt := types.NewType("func() int")
|
||||
typFloat := types.NewType("func() float")
|
||||
simplepoly.ModuleRegister(ModuleName, "fortytwo", []*types.FuncValue{
|
||||
{
|
||||
T: typInt,
|
||||
V: fortyTwo(typInt), // generate the correct function here
|
||||
},
|
||||
{
|
||||
T: typFloat,
|
||||
V: fortyTwo(typFloat),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// fortyTwo is a helper function to build the correct function for the desired
|
||||
// signature, because the simplepoly API doesn't tell the implementing function
|
||||
// what its signature should be! In the next version of this API, we could pass
|
||||
// in a sig field, like how we demonstrate in the implementation of FortyTwo. If
|
||||
// the API doesn't change, then this is an example of how to build this as a
|
||||
// wrapper.
|
||||
func fortyTwo(sig *types.Type) func([]types.Value) (types.Value, error) {
|
||||
return func(input []types.Value) (types.Value, error) {
|
||||
return FortyTwo(sig, input)
|
||||
}
|
||||
}
|
||||
|
||||
// FortyTwo returns 42 as either an int or a float. This is not especially
|
||||
// useful, but was built for a fun test case of a simple poly function with two
|
||||
// different return types, but no input args.
|
||||
func FortyTwo(sig *types.Type, input []types.Value) (types.Value, error) {
|
||||
if sig.Out.Kind == types.KindInt {
|
||||
return &types.IntValue{
|
||||
V: 42,
|
||||
}, nil
|
||||
}
|
||||
if sig.Out.Kind == types.KindFloat {
|
||||
return &types.FloatValue{
|
||||
V: 42.0,
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown output type: %+v", sig.Out)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coremath
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "math"
|
||||
)
|
||||
@@ -1,38 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coremath
|
||||
|
||||
import (
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "minus1", &types.FuncValue{
|
||||
T: types.NewType("func(x int) int"),
|
||||
V: Minus1,
|
||||
})
|
||||
}
|
||||
|
||||
// Minus1 takes an int and subtracts one from it.
|
||||
func Minus1(input []types.Value) (types.Value, error) {
|
||||
// TODO: check for overflow
|
||||
return &types.IntValue{
|
||||
V: input[0].Int() - 1,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coremath
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simplepoly"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simplepoly.ModuleRegister(ModuleName, "mod", []*types.FuncValue{
|
||||
{
|
||||
T: types.NewType("func(int, int) int"),
|
||||
V: Mod,
|
||||
},
|
||||
{
|
||||
T: types.NewType("func(float, float) float"),
|
||||
V: Mod,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Mod returns mod(x, y), the remainder of x/y. The two values must be either
|
||||
// both of KindInt or both of KindFloat, and it will return the same kind. If
|
||||
// you pass in a divisor of zero, this will error, eg: mod(x, 0) = NaN.
|
||||
// TODO: consider returning zero instead of erroring?
|
||||
func Mod(input []types.Value) (types.Value, error) {
|
||||
var x, y float64
|
||||
var float bool
|
||||
k := input[0].Type().Kind
|
||||
switch k {
|
||||
case types.KindFloat:
|
||||
float = true
|
||||
x = input[0].Float()
|
||||
y = input[1].Float()
|
||||
case types.KindInt:
|
||||
x = float64(input[0].Int())
|
||||
y = float64(input[1].Int())
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected kind: %s", k)
|
||||
}
|
||||
z := math.Mod(x, y)
|
||||
if math.IsNaN(z) {
|
||||
return nil, fmt.Errorf("result is not a number")
|
||||
}
|
||||
if math.IsInf(z, 1) {
|
||||
return nil, fmt.Errorf("unexpected positive infinity")
|
||||
}
|
||||
if math.IsInf(z, -1) {
|
||||
return nil, fmt.Errorf("unexpected negative infinity")
|
||||
}
|
||||
if float {
|
||||
return &types.FloatValue{
|
||||
V: z,
|
||||
}, nil
|
||||
}
|
||||
return &types.IntValue{
|
||||
V: int64(z), // XXX: does this truncate?
|
||||
}, nil
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coremath
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "pow", &types.FuncValue{
|
||||
T: types.NewType("func(x float, y float) float"),
|
||||
V: Pow,
|
||||
})
|
||||
}
|
||||
|
||||
// Pow returns x ^ y, the base-x exponential of y.
|
||||
func Pow(input []types.Value) (types.Value, error) {
|
||||
x, y := input[0].Float(), input[1].Float()
|
||||
// FIXME: check for overflow
|
||||
z := math.Pow(x, y)
|
||||
if math.IsNaN(z) {
|
||||
return nil, fmt.Errorf("result is not a number")
|
||||
}
|
||||
if math.IsInf(z, 1) {
|
||||
return nil, fmt.Errorf("result is positive infinity")
|
||||
}
|
||||
if math.IsInf(z, -1) {
|
||||
return nil, fmt.Errorf("result is negative infinity")
|
||||
}
|
||||
// TODO: consider only returning floats, and adding isinf and
|
||||
// isnan functions so that users can decide for themselves...
|
||||
return &types.FloatValue{
|
||||
V: z,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coremath
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "sqrt", &types.FuncValue{
|
||||
T: types.NewType("func(x float) float"),
|
||||
V: Sqrt,
|
||||
})
|
||||
}
|
||||
|
||||
// Sqrt returns sqrt(x), the square root of x.
|
||||
func Sqrt(input []types.Value) (types.Value, error) {
|
||||
x := input[0].Float()
|
||||
y := math.Sqrt(x)
|
||||
if math.IsNaN(y) {
|
||||
return nil, fmt.Errorf("result is not a number")
|
||||
}
|
||||
if math.IsInf(y, 1) {
|
||||
return nil, fmt.Errorf("result is positive infinity")
|
||||
}
|
||||
if math.IsInf(y, -1) {
|
||||
return nil, fmt.Errorf("result is negative infinity")
|
||||
}
|
||||
return &types.FloatValue{
|
||||
V: y,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coremath
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func testSqrtSuccess(input, sqrt float64) error {
|
||||
inputVal := &types.FloatValue{V: input}
|
||||
|
||||
val, err := Sqrt([]types.Value{inputVal})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val.Float() != sqrt {
|
||||
return fmt.Errorf("invalid output, expected %f, got %f", sqrt, val.Float())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func testSqrtError(input float64) error {
|
||||
inputVal := &types.FloatValue{V: input}
|
||||
_, err := Sqrt([]types.Value{inputVal})
|
||||
if err == nil {
|
||||
return fmt.Errorf("expected error for input %f, got nil", input)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSqrtValidInput(t *testing.T) {
|
||||
values := map[float64]float64{
|
||||
4.0: 2.0,
|
||||
16.0: 4.0,
|
||||
2.0: math.Sqrt(2.0),
|
||||
}
|
||||
|
||||
for input, sqrt := range values {
|
||||
if err := testSqrtSuccess(input, sqrt); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSqrtInvalidInput(t *testing.T) {
|
||||
values := []float64{-1.0}
|
||||
|
||||
for _, input := range values {
|
||||
if err := testSqrtError(input); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corenet
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "cidr_to_ip", &types.FuncValue{
|
||||
T: types.NewType("func(a str) str"),
|
||||
V: CidrToIP,
|
||||
})
|
||||
}
|
||||
|
||||
// CidrToIP returns the IP from a CIDR address
|
||||
func CidrToIP(input []types.Value) (types.Value, error) {
|
||||
cidr := input[0].Str()
|
||||
ip, _, err := net.ParseCIDR(strings.TrimSpace(cidr))
|
||||
if err != nil {
|
||||
return &types.StrValue{}, err
|
||||
}
|
||||
return &types.StrValue{
|
||||
V: ip.String(),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corenet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func TestCidrToIP(t *testing.T) {
|
||||
cidrtests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
err error
|
||||
}{
|
||||
// IPv4 success.
|
||||
{"IPv4 cidr", "192.0.2.12/24", "192.0.2.12", nil},
|
||||
{"spaced IPv4 cidr ", " 192.168.42.13/24 ", "192.168.42.13", nil},
|
||||
|
||||
// IPv4 failure - tests error.
|
||||
{"invalid IPv4 cidr", "192.168.42.13/33", "", fmt.Errorf("invalid CIDR address: 192.168.42.13/33")},
|
||||
|
||||
// IPV6 success.
|
||||
{"IPv6 cidr", "2001:db8::/32", "2001:db8::", nil},
|
||||
{"spaced IPv6 cidr ", " 2001:db8::/32 ", "2001:db8::", nil},
|
||||
|
||||
// IPv6 failure - tests error.
|
||||
{"invalid IPv6 cidr", "2001:db8::/333", "", fmt.Errorf("invalid CIDR address: 2001:db8::/333")},
|
||||
}
|
||||
|
||||
for _, ts := range cidrtests {
|
||||
test := ts
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
output, err := CidrToIP([]types.Value{&types.StrValue{V: test.input}})
|
||||
expectedStr := &types.StrValue{V: test.expected}
|
||||
|
||||
if test.err != nil && err.Error() != test.err.Error() {
|
||||
t.Errorf("input: %s, expected error: %q, got: %q", test.input, test.err, err)
|
||||
return
|
||||
} else if test.err != nil && err == nil {
|
||||
t.Errorf("input: %s, expected error: %v, but got nil", test.input, test.err)
|
||||
return
|
||||
} else if test.err == nil && err != nil {
|
||||
t.Errorf("input: %s, did not expect error but got: %#v", test.input, err)
|
||||
return
|
||||
}
|
||||
if err := output.Cmp(expectedStr); err != nil {
|
||||
t.Errorf("input: %s, expected: %s, got: %s", test.input, expectedStr, output)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corenet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "macfmt", &types.FuncValue{
|
||||
T: types.NewType("func(a str) str"),
|
||||
V: MacFmt,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "oldmacfmt", &types.FuncValue{
|
||||
T: types.NewType("func(a str) str"),
|
||||
V: OldMacFmt,
|
||||
})
|
||||
}
|
||||
|
||||
// MacFmt takes a MAC address with hyphens and converts it to a format with
|
||||
// colons.
|
||||
func MacFmt(input []types.Value) (types.Value, error) {
|
||||
mac := input[0].Str()
|
||||
|
||||
// Check if the MAC address is valid.
|
||||
if len(mac) != len("00:00:00:00:00:00") {
|
||||
return nil, fmt.Errorf("invalid MAC address length: %s", mac)
|
||||
}
|
||||
_, err := net.ParseMAC(mac)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.StrValue{
|
||||
V: strings.Replace(mac, "-", ":", -1),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// OldMacFmt takes a MAC address with colons and converts it to a format with
|
||||
// hyphens. This is the old deprecated style that nobody likes.
|
||||
func OldMacFmt(input []types.Value) (types.Value, error) {
|
||||
mac := input[0].Str()
|
||||
|
||||
// Check if the MAC address is valid.
|
||||
if len(mac) != len("00:00:00:00:00:00") {
|
||||
return nil, fmt.Errorf("invalid MAC address length: %s", mac)
|
||||
}
|
||||
_, err := net.ParseMAC(mac)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.StrValue{
|
||||
V: strings.Replace(mac, ":", "-", -1),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corenet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func TestMacFmt(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
in string
|
||||
out string
|
||||
wantErr bool
|
||||
}{
|
||||
{"Valid mac with hyphens", "01-23-45-67-89-AB", "01:23:45:67:89:AB", false},
|
||||
{"Valid mac with colons", "01:23:45:67:89:AB", "01:23:45:67:89:AB", false},
|
||||
{"Incorrect mac length with colons", "01:23:45:67:89:AB:01:23:45:67:89:AB", "01:23:45:67:89:AB:01:23:45:67:89:AB", true},
|
||||
{"Invalid mac", "", "", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m, err := MacFmt([]types.Value{&types.StrValue{V: tt.in}})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("func MacFmt() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if m != nil {
|
||||
if err := m.Cmp(&types.StrValue{V: tt.out}); err != nil {
|
||||
t.Errorf("got %q, want %q", m.Value(), tt.out)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOldMacFmt(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
in string
|
||||
out string
|
||||
wantErr bool
|
||||
}{
|
||||
{"Valid mac with hyphens", "01:23:45:67:89:AB", "01-23-45-67-89-AB", false},
|
||||
{"Valid mac with colons", "01-23-45-67-89-AB", "01-23-45-67-89-AB", false},
|
||||
{"Incorrect mac length with hyphens", "01-23-45-67-89-AB-01-23-45-67-89-AB", "01-23-45-67-89-AB-01-23-45-67-89-AB", true},
|
||||
{"Invalid mac", "", "", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m, err := OldMacFmt([]types.Value{&types.StrValue{V: tt.in}})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("func MacFmt() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if m != nil {
|
||||
if err := m.Cmp(&types.StrValue{V: tt.out}); err != nil {
|
||||
t.Errorf("got %q, want %q", m.Value(), tt.out)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corenet
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "net"
|
||||
)
|
||||
@@ -1,47 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreos
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "args", &types.FuncValue{
|
||||
T: types.NewType("func() []str"),
|
||||
V: Args,
|
||||
})
|
||||
}
|
||||
|
||||
// Args returns the `argv` of the current process. Keep in mind that this will
|
||||
// return different values depending on how this is deployed, so don't expect a
|
||||
// result on your deploy client to behave the same as a server receiving code.
|
||||
// FIXME: Sanitize any command-line secrets we might pass in by cli.
|
||||
func Args([]types.Value) (types.Value, error) {
|
||||
values := []types.Value{}
|
||||
for _, s := range os.Args {
|
||||
values = append(values, &types.StrValue{V: s})
|
||||
}
|
||||
return &types.ListValue{
|
||||
V: values,
|
||||
T: types.NewType("[]str"),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreos
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// TODO: Create a family method that will return a giant struct.
|
||||
simple.ModuleRegister(ModuleName, "is_debian", &types.FuncValue{
|
||||
T: types.NewType("func() bool"),
|
||||
V: IsDebian,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "is_redhat", &types.FuncValue{
|
||||
T: types.NewType("func() bool"),
|
||||
V: IsRedHat,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "is_archlinux", &types.FuncValue{
|
||||
T: types.NewType("func() bool"),
|
||||
V: IsArchLinux,
|
||||
})
|
||||
}
|
||||
|
||||
// IsDebian detects if the os family is debian.
|
||||
// TODO: Detect OS changes.
|
||||
func IsDebian(input []types.Value) (types.Value, error) {
|
||||
exists := true
|
||||
_, err := os.Stat("/etc/debian_version")
|
||||
if os.IsNotExist(err) {
|
||||
exists = false
|
||||
}
|
||||
return &types.BoolValue{
|
||||
V: exists,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsRedHat detects if the os family is redhat.
|
||||
// TODO: Detect OS changes.
|
||||
func IsRedHat(input []types.Value) (types.Value, error) {
|
||||
exists := true
|
||||
_, err := os.Stat("/etc/redhat-release")
|
||||
if os.IsNotExist(err) {
|
||||
exists = false
|
||||
}
|
||||
return &types.BoolValue{
|
||||
V: exists,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsArchLinux detects if the os family is archlinux.
|
||||
// TODO: Detect OS changes.
|
||||
func IsArchLinux(input []types.Value) (types.Value, error) {
|
||||
exists := true
|
||||
_, err := os.Stat("/etc/arch-release")
|
||||
if os.IsNotExist(err) {
|
||||
exists = false
|
||||
}
|
||||
return &types.BoolValue{
|
||||
V: exists,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreos
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "os"
|
||||
)
|
||||
@@ -1,229 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/recwatch"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// ReadFileFuncName is the name this function is registered as.
|
||||
ReadFileFuncName = "readfile"
|
||||
|
||||
// arg names...
|
||||
readFileArgNameFilename = "filename"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, ReadFileFuncName, func() interfaces.Func { return &ReadFileFunc{} }) // must register the func and name
|
||||
}
|
||||
|
||||
// ReadFileFunc is a function that reads the full contents from a local file. If
|
||||
// the file contents change or the file path changes, a new string will be sent.
|
||||
// Please note that this is different from the readfile function in the deploy
|
||||
// package.
|
||||
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
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *ReadFileFunc) String() string {
|
||||
return ReadFileFuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *ReadFileFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{readFileArgNameFilename}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *ReadFileFunc) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *ReadFileFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // maybe false because the file contents can change
|
||||
Memo: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", readFileArgNameFilename)),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *ReadFileFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
obj.events = make(chan error)
|
||||
obj.wg = &sync.WaitGroup{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *ReadFileFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
defer close(obj.events) // clean up for fun
|
||||
defer obj.wg.Wait()
|
||||
defer func() {
|
||||
if obj.recWatcher != nil {
|
||||
obj.recWatcher.Close() // close previous watcher
|
||||
obj.wg.Wait()
|
||||
}
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // don't infinite loop back
|
||||
continue // no more inputs, but don't return!
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
filename := input.Struct()[readFileArgNameFilename].Str()
|
||||
// TODO: add validation for absolute path?
|
||||
// TODO: add check for empty string
|
||||
if obj.filename != nil && *obj.filename == filename {
|
||||
continue // nothing changed
|
||||
}
|
||||
obj.filename = &filename
|
||||
|
||||
if obj.recWatcher != nil {
|
||||
obj.recWatcher.Close() // close previous watcher
|
||||
obj.wg.Wait()
|
||||
}
|
||||
// create new watcher
|
||||
obj.recWatcher = &recwatch.RecWatcher{
|
||||
Path: *obj.filename,
|
||||
Recurse: false,
|
||||
Flags: recwatch.Flags{
|
||||
// TODO: add Logf
|
||||
Debug: obj.init.Debug,
|
||||
},
|
||||
}
|
||||
if err := obj.recWatcher.Init(); err != nil {
|
||||
obj.recWatcher = nil
|
||||
// TODO: should we ignore the error and send ""?
|
||||
return errwrap.Wrapf(err, "could not watch file")
|
||||
}
|
||||
|
||||
// FIXME: instead of sending one event here, the recwatch
|
||||
// library should send one initial event at startup...
|
||||
startup := make(chan struct{})
|
||||
close(startup)
|
||||
|
||||
// watch recwatch events in a proxy goroutine, since
|
||||
// changing the recwatch object would panic the main
|
||||
// select when it's nil...
|
||||
obj.wg.Add(1)
|
||||
go func() {
|
||||
defer obj.wg.Done()
|
||||
for {
|
||||
var err error
|
||||
select {
|
||||
case <-startup:
|
||||
startup = nil
|
||||
// send an initial event
|
||||
|
||||
case event, ok := <-obj.recWatcher.Events():
|
||||
if !ok {
|
||||
return // file watcher shut down
|
||||
}
|
||||
if err = event.Error; err != nil {
|
||||
err = errwrap.Wrapf(err, "error event received")
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.events <- err:
|
||||
// send event...
|
||||
|
||||
case <-ctx.Done():
|
||||
// don't block here on shutdown
|
||||
return
|
||||
}
|
||||
//err = nil // reset
|
||||
}
|
||||
}()
|
||||
continue // wait for an actual event or we'd send empty!
|
||||
|
||||
case err, ok := <-obj.events:
|
||||
if !ok {
|
||||
return fmt.Errorf("no more events")
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "error event received")
|
||||
}
|
||||
|
||||
if obj.last == nil {
|
||||
continue // still waiting for input values
|
||||
}
|
||||
|
||||
// read file...
|
||||
content, err := ioutil.ReadFile(*obj.filename)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "error reading file")
|
||||
}
|
||||
result := string(content) // convert to string
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreos
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// SystemFuncName is the name this function is registered as.
|
||||
SystemFuncName = "system"
|
||||
|
||||
// arg names...
|
||||
systemArgNameCmd = "cmd"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, SystemFuncName, func() interfaces.Func { return &SystemFunc{} })
|
||||
}
|
||||
|
||||
// SystemFunc runs a string as a shell command, then produces each line from
|
||||
// stdout. If the input string changes, then the commands are executed one after
|
||||
// the other and the concatenation of their outputs is produced line by line.
|
||||
//
|
||||
// Note that in the likely case in which the process emits several lines one
|
||||
// after the other, the downstream resources might not run for every line unless
|
||||
// the "Meta:realize" metaparam is set to true.
|
||||
type SystemFunc struct {
|
||||
init *interfaces.Init
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *SystemFunc) String() string {
|
||||
return SystemFuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *SystemFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{systemArgNameCmd}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *SystemFunc) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *SystemFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) str", systemArgNameCmd)),
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *SystemFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *SystemFunc) Stream(ctx context.Context) error {
|
||||
// XXX: this implementation is a bit awkward especially with the port to
|
||||
// the Stream(context.Context) signature change. This is a straight port
|
||||
// but we could refactor this eventually.
|
||||
|
||||
// Close the output chan to signal that no more values are coming.
|
||||
defer close(obj.init.Output)
|
||||
|
||||
// A channel which closes when the current process exits, on its own
|
||||
// or due to cancel(). The channel is only closed once all the pending
|
||||
// stdout and stderr lines have been processed.
|
||||
//
|
||||
// The channel starts closed because no process is running yet. A new
|
||||
// channel is created each time a new process is started. We never run
|
||||
// more than one process at a time.
|
||||
processedChan := make(chan struct{})
|
||||
close(processedChan)
|
||||
|
||||
// Wait for the current process to exit, if any.
|
||||
defer func() {
|
||||
<-processedChan
|
||||
}()
|
||||
|
||||
// Kill the current process, if any. A new cancel function is created
|
||||
// each time a new process is started.
|
||||
var innerCtx context.Context
|
||||
defer func() {
|
||||
if obj.cancel == nil {
|
||||
return
|
||||
}
|
||||
obj.cancel()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case input, more := <-obj.init.Input:
|
||||
if !more {
|
||||
// Wait until the current process exits and all of its
|
||||
// stdout is sent downstream.
|
||||
select {
|
||||
case <-processedChan:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
shellCommand := input.Struct()[systemArgNameCmd].Str()
|
||||
|
||||
// Kill the previous command, if any.
|
||||
if obj.cancel != nil {
|
||||
obj.cancel()
|
||||
}
|
||||
<-processedChan
|
||||
|
||||
// Run the command, connecting it to ctx so we can kill
|
||||
// it if needed, and to two Readers so we can read its
|
||||
// stdout and stderr.
|
||||
innerCtx, obj.cancel = context.WithCancel(context.Background())
|
||||
cmd := exec.CommandContext(innerCtx, "sh", "-c", shellCommand)
|
||||
stdoutReader, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stderrReader, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We will now start several goroutines:
|
||||
// 1. To process stdout
|
||||
// 2. To process stderr
|
||||
// 3. To wait for (1) and (2) to terminate and close processedChan
|
||||
//
|
||||
// This WaitGroup is used by (3) to wait for (1) and (2).
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
// Emit one value downstream for each line from stdout.
|
||||
// Terminates when the process exits, on its own or due
|
||||
// to cancel().
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
stdoutScanner := bufio.NewScanner(stdoutReader)
|
||||
for stdoutScanner.Scan() {
|
||||
outputValue := &types.StrValue{V: stdoutScanner.Text()}
|
||||
obj.init.Output <- outputValue
|
||||
}
|
||||
}()
|
||||
|
||||
// Log the lines from stderr, to help the user debug.
|
||||
// Terminates when the process exits, on its own or
|
||||
// due to cancel().
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
stderrScanner := bufio.NewScanner(stderrReader)
|
||||
for stderrScanner.Scan() {
|
||||
obj.init.Logf("system: \"%v\": stderr: %v\n", shellCommand, stderrScanner.Text())
|
||||
}
|
||||
}()
|
||||
|
||||
// Closes processedChan after the previous two
|
||||
// goroutines terminate. Thus, this goroutine also
|
||||
// terminates when the process exits, on its own or due
|
||||
// to cancel().
|
||||
processedChan = make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(processedChan)
|
||||
}()
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simplepoly"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simplepoly.Register("panic", []*types.FuncValue{
|
||||
{
|
||||
T: types.NewType("func(x bool) bool"),
|
||||
V: Panic,
|
||||
},
|
||||
{
|
||||
T: types.NewType("func(x str) bool"),
|
||||
V: Panic,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Panic returns an error when it receives a non-empty string or a true boolean.
|
||||
// The error should cause the function engine to shutdown. If there's no error,
|
||||
// it returns false.
|
||||
func Panic(input []types.Value) (types.Value, error) {
|
||||
switch k := input[0].Type().Kind; k {
|
||||
case types.KindBool:
|
||||
if input[0].Bool() {
|
||||
return nil, fmt.Errorf("bool panic occurred")
|
||||
}
|
||||
case types.KindStr:
|
||||
if s := input[0].Str(); s != "" {
|
||||
return nil, fmt.Errorf("str panic occurred: %s", s)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported kind: %+v", k)
|
||||
}
|
||||
|
||||
return &types.BoolValue{
|
||||
V: false,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core // TODO: should this be in its own individual package?
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// Random1FuncName is the name this function is registered as.
|
||||
Random1FuncName = "random1"
|
||||
|
||||
// arg names...
|
||||
random1ArgNameLength = "length"
|
||||
|
||||
alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.Register(Random1FuncName, func() interfaces.Func { return &Random1Func{} })
|
||||
}
|
||||
|
||||
// Random1Func returns one random string of a certain length.
|
||||
// XXX: return a stream instead, and combine this with a first(?) function which
|
||||
// takes the first value and then puts backpressure on the stream. This should
|
||||
// notify parent functions somehow that their values are no longer required so
|
||||
// that they can shutdown if possible. Maybe it should be returning a stream of
|
||||
// floats [0,1] as well, which someone can later map to the alphabet that they
|
||||
// want. Should random() take an interval to know how often to spit out values?
|
||||
// It could also just do it once per second, and we could filter for less. If we
|
||||
// want something high precision, we could add that in the future... We could
|
||||
// name that "random" and this one can be "random1" until we deprecate it.
|
||||
type Random1Func struct {
|
||||
init *interfaces.Init
|
||||
|
||||
finished bool // did we send the random string?
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *Random1Func) String() string {
|
||||
return Random1FuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *Random1Func) ArgGen(index int) (string, error) {
|
||||
seq := []string{random1ArgNameLength}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *Random1Func) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *Random1Func) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false,
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s int) str", random1ArgNameLength)),
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// generate generates a random string.
|
||||
func generate(length uint16) (string, error) {
|
||||
max := len(alphabet) - 1 // last index
|
||||
output := ""
|
||||
|
||||
// FIXME: have someone verify this is cryptographically secure & correct
|
||||
for i := uint16(0); i < length; i++ {
|
||||
big, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
|
||||
if err != nil {
|
||||
return "", errwrap.Wrapf(err, "could not generate random string")
|
||||
}
|
||||
ix := big.Int64()
|
||||
output += string(alphabet[ix])
|
||||
}
|
||||
|
||||
if length != 0 && output == "" { // safety against empty strings
|
||||
return "", fmt.Errorf("string is empty")
|
||||
}
|
||||
|
||||
if uint16(len(output)) != length { // safety against weird bugs
|
||||
return "", fmt.Errorf("random string is too short") // bug!
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *Random1Func) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the single value that was generated and then closes.
|
||||
func (obj *Random1Func) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
var result string
|
||||
for {
|
||||
select {
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
return nil // can't output any more
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.finished {
|
||||
// TODO: continue instead?
|
||||
return fmt.Errorf("you can only pass a single input to random")
|
||||
}
|
||||
|
||||
length := input.Struct()[random1ArgNameLength].Int()
|
||||
// TODO: if negative, randomly pick a length ?
|
||||
if length < 0 {
|
||||
return fmt.Errorf("can't generate a negative length")
|
||||
}
|
||||
|
||||
var err error
|
||||
if result, err = generate(uint16(length)); err != nil {
|
||||
return err // no errwrap needed b/c helper func
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: result,
|
||||
}:
|
||||
// we only send one value, then wait for input to close
|
||||
obj.finished = true
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreregexp
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "match", &types.FuncValue{
|
||||
T: types.NewType("func(pattern str, s str) bool"),
|
||||
V: Match,
|
||||
})
|
||||
}
|
||||
|
||||
// Match matches whether a string matches the regexp pattern.
|
||||
func Match(input []types.Value) (types.Value, error) {
|
||||
pattern := input[0].Str()
|
||||
s := input[1].Str()
|
||||
|
||||
// TODO: We could make this more efficient with the regular function API
|
||||
// by only compiling the pattern when it changes.
|
||||
re, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "pattern did not compile")
|
||||
}
|
||||
|
||||
result := re.MatchString(s)
|
||||
return &types.BoolValue{
|
||||
V: result,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreregexp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func TestMatch0(t *testing.T) {
|
||||
values := []struct {
|
||||
pattern string
|
||||
s string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
"(mgmt){2}",
|
||||
"mgmt",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"(mgmt){2}",
|
||||
"mgmtmgmt",
|
||||
true,
|
||||
},
|
||||
{
|
||||
"(mgmt){2}",
|
||||
"mgmtmgmtmgmt",
|
||||
true,
|
||||
},
|
||||
{
|
||||
`^db\d+\.example\.com$`,
|
||||
"db1.example.com",
|
||||
true,
|
||||
},
|
||||
{
|
||||
`^db\d+\.example\.com$`,
|
||||
"dbX.example.com",
|
||||
false,
|
||||
},
|
||||
{
|
||||
`^db\d+\.example\.com$`,
|
||||
"db1.exampleXcom",
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, x := range values {
|
||||
pattern := &types.StrValue{V: x.pattern}
|
||||
s := &types.StrValue{V: x.s}
|
||||
val, err := Match([]types.Value{pattern, s})
|
||||
if err != nil {
|
||||
t.Errorf("test index %d failed with: %+v", i, err)
|
||||
}
|
||||
if a, b := x.expected, val.Bool(); a != b {
|
||||
t.Errorf("test index %d expected %t, got %t", i, a, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreregexp
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "regexp"
|
||||
)
|
||||
@@ -1,50 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corestrings
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "split", &types.FuncValue{
|
||||
T: types.NewType("func(a str, b str) []str"),
|
||||
V: Split,
|
||||
})
|
||||
}
|
||||
|
||||
// Split splits the input string using the separator and returns the segments as
|
||||
// a list.
|
||||
func Split(input []types.Value) (types.Value, error) {
|
||||
str, sep := input[0].Str(), input[1].Str()
|
||||
|
||||
segments := strings.Split(str, sep)
|
||||
|
||||
listVal := types.NewList(types.NewType("[]str"))
|
||||
|
||||
for _, segment := range segments {
|
||||
listVal.Add(&types.StrValue{
|
||||
V: segment,
|
||||
})
|
||||
}
|
||||
|
||||
return listVal, nil
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corestrings
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
)
|
||||
|
||||
func testSplit(input, sep string, output []string) error {
|
||||
inputVal, sepVal := &types.StrValue{V: input}, &types.StrValue{V: sep}
|
||||
|
||||
val, err := Split([]types.Value{inputVal, sepVal})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listVal, ok := val.(*types.ListValue)
|
||||
if !ok {
|
||||
return fmt.Errorf("split did not return a list")
|
||||
}
|
||||
for _, segment := range output {
|
||||
if _, ok := listVal.Contains(&types.StrValue{V: segment}); !ok {
|
||||
return fmt.Errorf("output does not contained expected segment %s", segment)
|
||||
}
|
||||
}
|
||||
for _, segment := range listVal.V {
|
||||
if !util.StrInList(segment.Str(), output) {
|
||||
return fmt.Errorf("output contains unexpected segment %s", segment.Str())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
values := map[string][]string{
|
||||
"hello,world": {"hello", "world"},
|
||||
"hello world": {"hello world"},
|
||||
"hello;world": {"hello;world"},
|
||||
}
|
||||
|
||||
for input, output := range values {
|
||||
if err := testSplit(input, ",", output); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corestrings
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "strings"
|
||||
)
|
||||
@@ -1,39 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corestrings
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "to_lower", &types.FuncValue{
|
||||
T: types.NewType("func(a str) str"),
|
||||
V: ToLower,
|
||||
})
|
||||
}
|
||||
|
||||
// ToLower turns a string to lowercase.
|
||||
func ToLower(input []types.Value) (types.Value, error) {
|
||||
return &types.StrValue{
|
||||
V: strings.ToLower(input[0].Str()),
|
||||
}, nil
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corestrings
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func testToLower(t *testing.T, input, expected string) {
|
||||
inputStr := &types.StrValue{V: input}
|
||||
value, err := ToLower([]types.Value{inputStr})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if value.Str() != expected {
|
||||
t.Errorf("invalid output, expected %s, got %s", expected, value.Str())
|
||||
}
|
||||
}
|
||||
|
||||
func TestToLowerSimple(t *testing.T) {
|
||||
testToLower(t, "Hello", "hello")
|
||||
}
|
||||
|
||||
func TestToLowerSameString(t *testing.T) {
|
||||
testToLower(t, "HELLO 22", "hello 22")
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !darwin
|
||||
|
||||
package coresys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
"github.com/purpleidea/mgmt/util/socketset"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// CPUCountFuncName is the name this fact is registered as. It's still a
|
||||
// Func Name because this is the name space the fact is actually using.
|
||||
CPUCountFuncName = "cpu_count"
|
||||
|
||||
rtmGrps = 0x1 // make me a multicast receiver
|
||||
socketFile = "pipe.sock"
|
||||
cpuDevpathRegex = "/devices/system/cpu/cpu[0-9]"
|
||||
)
|
||||
|
||||
func init() {
|
||||
facts.ModuleRegister(ModuleName, CPUCountFuncName, func() facts.Fact { return &CPUCountFact{} }) // must register the fact and name
|
||||
}
|
||||
|
||||
// CPUCountFact is a fact that returns the current CPU count.
|
||||
type CPUCountFact struct {
|
||||
init *facts.Init
|
||||
}
|
||||
|
||||
// String returns a simple name for this fact. This is needed so this struct can
|
||||
// satisfy the pgraph.Vertex interface.
|
||||
func (obj *CPUCountFact) String() string {
|
||||
return CPUCountFuncName
|
||||
}
|
||||
|
||||
// Info returns static typing info about what the fact returns.
|
||||
func (obj *CPUCountFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Output: types.NewType("int"),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs startup code for this fact and sets the facts.Init variable.
|
||||
func (obj *CPUCountFact) Init(init *facts.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this fact has over time. It will
|
||||
// first poll sysfs to get the initial cpu count, and then receives UEvents from
|
||||
// the kernel as CPUs are added/removed.
|
||||
func (obj CPUCountFact) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // signal when we're done
|
||||
|
||||
ss, err := socketset.NewSocketSet(rtmGrps, socketFile, unix.NETLINK_KOBJECT_UEVENT)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "error creating socket set")
|
||||
}
|
||||
|
||||
// waitgroup for netlink receive goroutine
|
||||
wg := &sync.WaitGroup{}
|
||||
defer ss.Close()
|
||||
// We must wait for the Shutdown() AND the select inside of SocketSet to
|
||||
// complete before we Close, since the unblocking in SocketSet is not a
|
||||
// synchronous operation.
|
||||
defer wg.Wait()
|
||||
defer ss.Shutdown() // close the netlink socket and unblock conn.receive()
|
||||
|
||||
eventChan := make(chan *nlChanEvent) // updated in goroutine when we receive uevent
|
||||
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() {
|
||||
defer wg.Done()
|
||||
defer close(eventChan)
|
||||
for {
|
||||
uevent, err := ss.ReceiveUEvent() // calling Shutdown will stop this from blocking
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("sending uevent SEQNUM: %s", uevent.Data["SEQNUM"])
|
||||
}
|
||||
select {
|
||||
case eventChan <- &nlChanEvent{
|
||||
uevent: uevent,
|
||||
err: err,
|
||||
}:
|
||||
case <-closeChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
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 {
|
||||
continue
|
||||
}
|
||||
if event.err != nil {
|
||||
return errwrap.Wrapf(event.err, "error receiving uevent")
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
if once && newCount == cpuCount {
|
||||
continue
|
||||
}
|
||||
cpuCount = newCount
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.IntValue{
|
||||
V: cpuCount,
|
||||
}:
|
||||
once = true
|
||||
// send
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getCPUCount looks in sysfs to get the number of CPUs that are online.
|
||||
func getCPUCount() (int64, error) {
|
||||
dat, err := ioutil.ReadFile("/sys/devices/system/cpu/online")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parseCPUList(string(dat))
|
||||
}
|
||||
|
||||
// Parses a line of the form X,Y,Z,... where X,Y,Z can be either a single CPU or
|
||||
// a contiguous range of CPUs. e.g. "2,4-31,32-63". If there is an error parsing
|
||||
// the line the function will return 0.
|
||||
func parseCPUList(list string) (int64, error) {
|
||||
var count int64
|
||||
for _, rg := range strings.Split(list, ",") {
|
||||
cpuRange := strings.SplitN(rg, "-", 2)
|
||||
if len(cpuRange) == 1 {
|
||||
count++
|
||||
} else if len(cpuRange) == 2 {
|
||||
lo, err := strconv.ParseInt(cpuRange[0], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
hi, err := strconv.ParseInt(strings.TrimRight(cpuRange[1], "\n"), 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
count += hi - lo + 1
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// When we receive a udev event, we filter only those that indicate a CPU is
|
||||
// being added or removed, or being taken online or offline.
|
||||
func isCPUEvent(event *socketset.UEvent) bool {
|
||||
if event.Subsystem != "cpu" {
|
||||
return false
|
||||
}
|
||||
// is this a valid cpu path in sysfs?
|
||||
m, err := regexp.MatchString(cpuDevpathRegex, event.Devpath)
|
||||
if !m || err != nil {
|
||||
return false
|
||||
}
|
||||
if event.Action == "add" || event.Action == "remove" || event.Action == "online" || event.Action == "offline" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// nlChanEvent defines the channel used to send netlink messages and errors to
|
||||
// the event processing loop in Stream.
|
||||
type nlChanEvent struct {
|
||||
uevent *socketset.UEvent
|
||||
err error
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !darwin
|
||||
|
||||
package coresys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func TestSimple(t *testing.T) {
|
||||
fact := &CPUCountFact{}
|
||||
|
||||
output := make(chan types.Value)
|
||||
err := fact.Init(&facts.Init{
|
||||
Output: output,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
t.Logf("cpucount_fact_test: "+format, v...)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("could not init CPUCountFact")
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
defer cancel()
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case cpus := <-output:
|
||||
t.Logf("CPUS: %d\n", cpus.Int())
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// now start the stream
|
||||
if err := fact.Stream(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCPUList(t *testing.T) {
|
||||
var cpulistTests = []struct {
|
||||
desc string
|
||||
list string
|
||||
result int64
|
||||
}{
|
||||
{
|
||||
desc: "single CPU",
|
||||
list: "1",
|
||||
result: 1,
|
||||
},
|
||||
{
|
||||
desc: "cpu range",
|
||||
list: "0-31",
|
||||
result: 32,
|
||||
},
|
||||
{
|
||||
desc: "range and single",
|
||||
list: "0-1,3",
|
||||
result: 3,
|
||||
},
|
||||
{
|
||||
desc: "single, two ranges",
|
||||
list: "2,4-8,10-16",
|
||||
result: 13,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range cpulistTests {
|
||||
t.Run(tt.list, func(t *testing.T) {
|
||||
cpuCount, err := parseCPUList(tt.list)
|
||||
if err != nil {
|
||||
t.Errorf("could not parseCPUList: %+v", err)
|
||||
return
|
||||
}
|
||||
if cpuCount != tt.result {
|
||||
t.Errorf("expected %d, got %d", tt.result, cpuCount)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coresys
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
simple.ModuleRegister(ModuleName, "getenv", &types.FuncValue{
|
||||
T: types.NewType("func(str) str"),
|
||||
V: GetEnv,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "defaultenv", &types.FuncValue{
|
||||
T: types.NewType("func(str, str) str"),
|
||||
V: DefaultEnv,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "hasenv", &types.FuncValue{
|
||||
T: types.NewType("func(str) bool"),
|
||||
V: HasEnv,
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, "env", &types.FuncValue{
|
||||
T: types.NewType("func() map{str: str}"),
|
||||
V: Env,
|
||||
})
|
||||
}
|
||||
|
||||
// GetEnv gets environment variable by name or returns empty string if non
|
||||
// existing.
|
||||
func GetEnv(input []types.Value) (types.Value, error) {
|
||||
return &types.StrValue{
|
||||
V: os.Getenv(input[0].Str()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DefaultEnv gets environment variable by name or returns default if non
|
||||
// existing.
|
||||
func DefaultEnv(input []types.Value) (types.Value, error) {
|
||||
value, exists := os.LookupEnv(input[0].Str())
|
||||
if !exists {
|
||||
value = input[1].Str()
|
||||
}
|
||||
return &types.StrValue{
|
||||
V: value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HasEnv returns true if environment variable exists.
|
||||
func HasEnv(input []types.Value) (types.Value, error) {
|
||||
_, exists := os.LookupEnv(input[0].Str())
|
||||
return &types.BoolValue{
|
||||
V: exists,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Env returns a map of all keys and their values.
|
||||
func Env(input []types.Value) (types.Value, error) {
|
||||
environ := make(map[types.Value]types.Value)
|
||||
for _, keyval := range os.Environ() {
|
||||
if i := strings.IndexRune(keyval, '='); i != -1 {
|
||||
environ[&types.StrValue{V: keyval[:i]}] = &types.StrValue{V: keyval[i+1:]}
|
||||
}
|
||||
}
|
||||
return &types.MapValue{
|
||||
T: types.NewType("map{str: str}"),
|
||||
V: environ,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coresys
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// HostnameFuncName is the name this fact is registered as. It's still a
|
||||
// Func Name because this is the name space the fact is actually using.
|
||||
HostnameFuncName = "hostname"
|
||||
)
|
||||
|
||||
func init() {
|
||||
facts.ModuleRegister(ModuleName, HostnameFuncName, func() facts.Fact { return &HostnameFact{} }) // must register the fact and name
|
||||
}
|
||||
|
||||
// HostnameFact is a function that returns the hostname.
|
||||
// TODO: support hostnames that change in the future.
|
||||
type HostnameFact struct {
|
||||
init *facts.Init
|
||||
}
|
||||
|
||||
// String returns a simple name for this fact. This is needed so this struct can
|
||||
// satisfy the pgraph.Vertex interface.
|
||||
func (obj *HostnameFact) String() string {
|
||||
return HostnameFuncName
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal facts that users can use directly.
|
||||
//func (obj *HostnameFact) Validate() error {
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *HostnameFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Output: types.NewType("str"),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this fact.
|
||||
func (obj *HostnameFact) Init(init *facts.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the single value that this fact has, and then closes.
|
||||
func (obj *HostnameFact) Stream(ctx context.Context) error {
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: obj.init.Hostname,
|
||||
}:
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
close(obj.init.Output) // signal that we're done sending
|
||||
return nil
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coresys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// LoadFuncName is the name this fact is registered as. It's still a
|
||||
// Func Name because this is the name space the fact is actually using.
|
||||
LoadFuncName = "load"
|
||||
|
||||
loadSignature = "struct{x1 float; x5 float; x15 float}"
|
||||
)
|
||||
|
||||
func init() {
|
||||
facts.ModuleRegister(ModuleName, LoadFuncName, func() facts.Fact { return &LoadFact{} }) // must register the fact and name
|
||||
}
|
||||
|
||||
// LoadFact is a fact which returns the current system load.
|
||||
type LoadFact struct {
|
||||
init *facts.Init
|
||||
}
|
||||
|
||||
// String returns a simple name for this fact. This is needed so this struct can
|
||||
// satisfy the pgraph.Vertex interface.
|
||||
func (obj *LoadFact) String() string {
|
||||
return LoadFuncName
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal facts that users can use directly.
|
||||
//func (obj *LoadFact) Validate() error {
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *LoadFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Output: types.NewType(loadSignature),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this fact.
|
||||
func (obj *LoadFact) Init(init *facts.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this fact has over time.
|
||||
func (obj *LoadFact) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // always signal when we're done
|
||||
|
||||
// it seems the different values only update once every 5
|
||||
// seconds, so that's as often as we need to refresh this!
|
||||
// TODO: lookup this value if it's something configurable
|
||||
ticker := time.NewTicker(time.Duration(5) * time.Second)
|
||||
|
||||
// streams must generate an initial event on startup
|
||||
startChan := make(chan struct{}) // start signal
|
||||
close(startChan) // kick it off!
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-startChan: // kick the loop once at start
|
||||
startChan = nil // disable
|
||||
case <-ticker.C: // received the timer event
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
x1, x5, x15, err := load()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- st:
|
||||
// send
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build darwin
|
||||
|
||||
package coresys
|
||||
|
||||
/*
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// macOS/Darwin specific implementation to get load.
|
||||
func load() (one, five, fifteen float64, err error) {
|
||||
avg := []C.double{0, 0, 0}
|
||||
|
||||
C.getloadavg(&avg[0], C.int(len(avg)))
|
||||
|
||||
one = float64(avg[0])
|
||||
five = float64(avg[1])
|
||||
fifteen = float64(avg[2])
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !darwin
|
||||
|
||||
package coresys
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
// LoadScale factor scales the output from sysinfo to the correct float
|
||||
// value.
|
||||
LoadScale = 65536 // XXX: is this correct or should it be 65535?
|
||||
)
|
||||
|
||||
// load returns the system load averages for the last minute, five minutes and
|
||||
// fifteen minutes. Calling this more often than once every five seconds seems
|
||||
// to be unnecessary, since the kernel only updates these values that often.
|
||||
// TODO: is the kernel update interval configurable?
|
||||
func load() (one, five, fifteen float64, err error) {
|
||||
var sysinfo syscall.Sysinfo_t
|
||||
if err = syscall.Sysinfo(&sysinfo); err != nil {
|
||||
return
|
||||
}
|
||||
one = float64(sysinfo.Loads[0]) / LoadScale
|
||||
five = float64(sysinfo.Loads[1]) / LoadScale
|
||||
fifteen = float64(sysinfo.Loads[2]) / LoadScale
|
||||
return
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coresys
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "sys"
|
||||
)
|
||||
@@ -1,93 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coresys
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// UptimeFuncName is the name this fact is registered as. It's still a
|
||||
// Func Name because this is the name space the fact is actually using.
|
||||
UptimeFuncName = "uptime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
facts.ModuleRegister(ModuleName, UptimeFuncName, func() facts.Fact { return &UptimeFact{} })
|
||||
}
|
||||
|
||||
// UptimeFact is a fact which returns the current uptime of your system.
|
||||
type UptimeFact struct {
|
||||
init *facts.Init
|
||||
}
|
||||
|
||||
// String returns a simple name for this fact. This is needed so this struct can
|
||||
// satisfy the pgraph.Vertex interface.
|
||||
func (obj *UptimeFact) String() string {
|
||||
return UptimeFuncName
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *UptimeFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Output: types.TypeInt,
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this fact.
|
||||
func (obj *UptimeFact) Init(init *facts.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this fact has over time.
|
||||
func (obj *UptimeFact) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output)
|
||||
ticker := time.NewTicker(time.Duration(1) * time.Second)
|
||||
|
||||
startChan := make(chan struct{})
|
||||
close(startChan)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-startChan:
|
||||
startChan = nil
|
||||
case <-ticker.C:
|
||||
// send
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
uptime, err := uptime()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not read uptime value")
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.IntValue{V: uptime}:
|
||||
// send
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build darwin
|
||||
|
||||
package coresys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func uptime() (int64, error) {
|
||||
// get time of boot struct
|
||||
rawBoottime, err := unix.SysctlRaw("kern.boottime")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// make sure size is correct
|
||||
var boottime syscall.Timeval
|
||||
if len(rawBoottime) != int(unsafe.Sizeof(boottime)) {
|
||||
return 0, fmt.Errorf("invalid boottime encountered while calculating uptime")
|
||||
}
|
||||
|
||||
// uptime is difference between current time and boot time
|
||||
boottime = *(*syscall.Timeval)(unsafe.Pointer(&rawBoottime[0]))
|
||||
return time.Now().Unix() - boottime.Sec, nil
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !darwin
|
||||
|
||||
package coresys
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func uptime() (int64, error) {
|
||||
var sysinfo unix.Sysinfo_t
|
||||
if err := unix.Sysinfo(&sysinfo); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return sysinfo.Uptime, nil
|
||||
}
|
||||
@@ -1,632 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core // TODO: should this be in its own individual package?
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// TemplateFuncName is the name this function is registered as.
|
||||
TemplateFuncName = "template"
|
||||
|
||||
// TemplateName is the name of our template as required by the template
|
||||
// library.
|
||||
TemplateName = "template"
|
||||
|
||||
// arg names...
|
||||
templateArgNameTemplate = "template"
|
||||
templateArgNameVars = "vars"
|
||||
)
|
||||
|
||||
var (
|
||||
// errorType represents a reflection type of error as seen in:
|
||||
// https://github.com/golang/go/blob/ec62ee7f6d3839fe69aeae538dadc1c9dc3bf020/src/text/template/exec.go#L612
|
||||
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.Register(TemplateFuncName, func() interfaces.Func { return &TemplateFunc{} })
|
||||
}
|
||||
|
||||
var _ interfaces.PolyFunc = &TemplateFunc{} // ensure it meets this expectation
|
||||
|
||||
// TemplateFunc is a static polymorphic function that compiles a template and
|
||||
// returns the output as a string. It bases its output on the values passed in
|
||||
// to it. It examines the type of the second argument (the input data vars) at
|
||||
// compile time and then determines the static functions signature by including
|
||||
// that in the overall signature.
|
||||
// TODO: We *might* need to add events for internal function changes over time,
|
||||
// but only if they are not pure. We currently only use simple, pure functions.
|
||||
type TemplateFunc struct {
|
||||
// Type is the type of the input vars (2nd) arg if one is specified. Nil
|
||||
// is the special undetermined value that is used before type is known.
|
||||
Type *types.Type // type of vars
|
||||
|
||||
built bool // was this function built yet?
|
||||
|
||||
init *interfaces.Init
|
||||
last types.Value // last value received to use for diff
|
||||
|
||||
result *string // last calculated output
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *TemplateFunc) String() string {
|
||||
return TemplateFuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *TemplateFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{templateArgNameTemplate, templateArgNameVars}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Unify returns the list of invariants that this func produces.
|
||||
func (obj *TemplateFunc) Unify(expr interfaces.Expr) ([]interfaces.Invariant, error) {
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
// func(format string) string
|
||||
// OR
|
||||
// func(format string, arg variant) string
|
||||
|
||||
formatName, err := obj.ArgGen(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dummyFormat := &interfaces.ExprAny{} // corresponds to the format type
|
||||
dummyOut := &interfaces.ExprAny{} // corresponds to the out string
|
||||
|
||||
// format arg type of string
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: dummyFormat,
|
||||
Type: types.TypeStr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// return type of string
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: dummyOut,
|
||||
Type: types.TypeStr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// generator function
|
||||
fn := func(fnInvariants []interfaces.Invariant, solved map[interfaces.Expr]*types.Type) ([]interfaces.Invariant, error) {
|
||||
for _, invariant := range fnInvariants {
|
||||
// search for this special type of invariant
|
||||
cfavInvar, ok := invariant.(*interfaces.CallFuncArgsValueInvariant)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// did we find the mapping from us to ExprCall ?
|
||||
if cfavInvar.Func != expr {
|
||||
continue
|
||||
}
|
||||
// cfavInvar.Expr is the ExprCall! (the return pointer)
|
||||
// cfavInvar.Args are the args that ExprCall uses!
|
||||
if len(cfavInvar.Args) == 0 {
|
||||
return nil, fmt.Errorf("unable to build function with no args")
|
||||
}
|
||||
if l := len(cfavInvar.Args); l > 2 {
|
||||
return nil, fmt.Errorf("unable to build function with %d args", l)
|
||||
}
|
||||
// we can either have one arg or two
|
||||
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
// add the relationship to the returned value
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Expr,
|
||||
Expr2: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// add the relationships to the called args
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Args[0],
|
||||
Expr2: dummyFormat,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// first arg must be a string
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: cfavInvar.Args[0],
|
||||
Type: types.TypeStr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: if the template is known statically, we could
|
||||
// parse it to check for variable safety if we wanted!
|
||||
//value, err := cfavInvar.Args[0].Value() // is it known?
|
||||
//if err != nil {
|
||||
//}
|
||||
|
||||
// full function
|
||||
mapped := make(map[string]interfaces.Expr)
|
||||
ordered := []string{formatName}
|
||||
mapped[formatName] = dummyFormat
|
||||
|
||||
if len(cfavInvar.Args) == 2 { // two args is more complex
|
||||
argName, err := obj.ArgGen(1) // 1st arg after 0
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if argName == templateArgNameTemplate {
|
||||
return nil, fmt.Errorf("could not build function with %d args", 1)
|
||||
}
|
||||
|
||||
dummyArg := &interfaces.ExprAny{}
|
||||
|
||||
// speculate about the type? (maybe redundant)
|
||||
if typ, err := cfavInvar.Args[1].Type(); err == nil {
|
||||
invar := &interfaces.EqualsInvariant{
|
||||
Expr: dummyArg,
|
||||
Type: typ,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
}
|
||||
|
||||
if typ, exists := solved[cfavInvar.Args[1]]; exists { // alternate way to lookup type
|
||||
invar := &interfaces.EqualsInvariant{
|
||||
Expr: dummyArg,
|
||||
Type: typ,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
}
|
||||
|
||||
// expression must match type of the input arg
|
||||
invar := &interfaces.EqualityInvariant{
|
||||
Expr1: dummyArg,
|
||||
Expr2: cfavInvar.Args[1],
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
mapped[argName] = dummyArg
|
||||
ordered = append(ordered, argName)
|
||||
}
|
||||
|
||||
invar = &interfaces.EqualityWrapFuncInvariant{
|
||||
Expr1: expr, // maps directly to us!
|
||||
Expr2Map: mapped,
|
||||
Expr2Ord: ordered,
|
||||
Expr2Out: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: do we return this relationship with ExprCall?
|
||||
invar = &interfaces.EqualityWrapCallInvariant{
|
||||
// TODO: should Expr1 and Expr2 be reversed???
|
||||
Expr1: cfavInvar.Expr,
|
||||
//Expr2Func: cfavInvar.Func, // same as below
|
||||
Expr2Func: expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: are there any other invariants we should build?
|
||||
return invariants, nil // generator return
|
||||
}
|
||||
// We couldn't tell the solver anything it didn't already know!
|
||||
return nil, fmt.Errorf("couldn't generate new invariants")
|
||||
}
|
||||
invar = &interfaces.GeneratorInvariant{
|
||||
Func: fn,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
return invariants, nil
|
||||
}
|
||||
|
||||
// Polymorphisms returns the possible type signatures for this template. In this
|
||||
// case, since the second argument can be an infinite number of values, it
|
||||
// instead returns either the final precise type (if it can be gleamed from the
|
||||
// input partials) or if it cannot, it returns a single entry with the complete
|
||||
// type but with the variable second argument specified as a `variant` type. If
|
||||
// it encounters any partial type specifications which are not possible, then it
|
||||
// errors out. This could happen if you specified a non string template arg.
|
||||
// XXX: is there a better API than returning a buried `variant` type?
|
||||
func (obj *TemplateFunc) Polymorphisms(partialType *types.Type, partialValues []types.Value) ([]*types.Type, error) {
|
||||
// TODO: return `variant` as second arg for now -- maybe there's a better way?
|
||||
str := fmt.Sprintf("func(%s str, %s variant) str", templateArgNameTemplate, templateArgNameVars)
|
||||
variant := []*types.Type{types.NewType(str)}
|
||||
|
||||
if partialType == nil {
|
||||
return variant, nil
|
||||
}
|
||||
|
||||
if partialType.Out != nil && partialType.Out.Cmp(types.TypeStr) != nil {
|
||||
return nil, fmt.Errorf("return value of template must be str")
|
||||
}
|
||||
|
||||
ord := partialType.Ord
|
||||
if partialType.Map != nil {
|
||||
if len(ord) != 2 && len(ord) != 1 {
|
||||
return nil, fmt.Errorf("must have exactly one or two args in template func")
|
||||
}
|
||||
if t, exists := partialType.Map[ord[0]]; exists && t != nil {
|
||||
if t.Cmp(types.TypeStr) != nil {
|
||||
return nil, fmt.Errorf("first arg for template must be an str")
|
||||
}
|
||||
}
|
||||
if len(ord) == 1 { // no args being passed in (boring template)
|
||||
return []*types.Type{types.NewType(fmt.Sprintf("func(%s str) str", templateArgNameTemplate))}, nil
|
||||
|
||||
} else if t, exists := partialType.Map[ord[1]]; exists && t != nil {
|
||||
// known vars type! w00t!
|
||||
return []*types.Type{types.NewType(fmt.Sprintf("func(%s str, %s %s) str", templateArgNameTemplate, templateArgNameVars, t.String()))}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return variant, nil
|
||||
}
|
||||
|
||||
// Build takes the now known function signature and stores it so that this
|
||||
// function can appear to be static. It extracts the type of the vars argument,
|
||||
// which is the dynamic part which can change. That type is used to build our
|
||||
// function statically.
|
||||
func (obj *TemplateFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
if typ.Kind != types.KindFunc {
|
||||
return nil, fmt.Errorf("input type must be of kind func")
|
||||
}
|
||||
if len(typ.Ord) != 2 && len(typ.Ord) != 1 {
|
||||
return nil, fmt.Errorf("the template function needs exactly one or two args")
|
||||
}
|
||||
if typ.Out == nil {
|
||||
return nil, fmt.Errorf("return type of function must be specified")
|
||||
}
|
||||
if typ.Out.Cmp(types.TypeStr) != nil {
|
||||
return nil, fmt.Errorf("return type of function must be an str")
|
||||
}
|
||||
if typ.Map == nil {
|
||||
return nil, fmt.Errorf("invalid input type")
|
||||
}
|
||||
|
||||
t0, exists := typ.Map[typ.Ord[0]]
|
||||
if !exists || t0 == nil {
|
||||
return nil, fmt.Errorf("first arg must be specified")
|
||||
}
|
||||
if t0.Cmp(types.TypeStr) != nil {
|
||||
return nil, fmt.Errorf("first arg for template must be an str")
|
||||
}
|
||||
|
||||
if len(typ.Ord) == 1 { // no args being passed in (boring template)
|
||||
obj.built = true
|
||||
return obj.sig(), nil
|
||||
}
|
||||
|
||||
t1, exists := typ.Map[typ.Ord[1]]
|
||||
if !exists || t1 == nil {
|
||||
return nil, fmt.Errorf("second arg must be specified")
|
||||
}
|
||||
obj.Type = t1 // extracted vars type is now known!
|
||||
|
||||
obj.built = true
|
||||
return obj.sig(), nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *TemplateFunc) Validate() error {
|
||||
if !obj.built {
|
||||
return fmt.Errorf("function wasn't built yet")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *TemplateFunc) Info() *interfaces.Info {
|
||||
var sig *types.Type
|
||||
if obj.built {
|
||||
sig = obj.sig() // helper
|
||||
}
|
||||
return &interfaces.Info{
|
||||
Pure: true,
|
||||
Memo: false,
|
||||
Sig: sig,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// helper
|
||||
func (obj *TemplateFunc) sig() *types.Type {
|
||||
if obj.Type != nil { // don't panic if called speculatively
|
||||
str := fmt.Sprintf("func(%s str, %s %s) str", templateArgNameTemplate, templateArgNameVars, obj.Type.String())
|
||||
return types.NewType(str)
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("func(%s str) str", templateArgNameTemplate)
|
||||
return types.NewType(str)
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *TemplateFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// run runs a template and returns the result.
|
||||
func (obj *TemplateFunc) run(templateText string, vars types.Value) (string, error) {
|
||||
// see: https://golang.org/pkg/text/template/#FuncMap for more info
|
||||
// note: we can override any other functions by adding them here...
|
||||
funcMap := map[string]interface{}{
|
||||
//"test1": func(in interface{}) (interface{}, error) { // ok
|
||||
// return fmt.Sprintf("got(%T): %+v", in, in), nil
|
||||
//},
|
||||
//"test2": func(in interface{}) interface{} { // NOT ok
|
||||
// panic("panic") // a panic here brings down everything!
|
||||
//},
|
||||
//"test3": func(foo int64) (string, error) { // ok, but errors
|
||||
// return "", fmt.Errorf("i am an error")
|
||||
//},
|
||||
//"test4": func(in1, in2 reflect.Value) (reflect.Value, error) { // ok
|
||||
// s := fmt.Sprintf("got: %+v and: %+v", in1, in2)
|
||||
// return reflect.ValueOf(s), nil
|
||||
//},
|
||||
}
|
||||
|
||||
// FIXME: should we do this once in init() instead, or in the Register
|
||||
// function in the simple package?
|
||||
// TODO: loop through this map in a sorted, deterministic order
|
||||
// XXX: should this use the scope instead (so imports are used properly) ?
|
||||
for name, fn := range simple.RegisteredFuncs {
|
||||
name = safename(name) // TODO: rename since we can't include dot
|
||||
if _, exists := funcMap[name]; exists {
|
||||
obj.init.Logf("warning, existing function named: `%s` exists", name)
|
||||
continue
|
||||
}
|
||||
|
||||
// When template execution invokes a function with an argument
|
||||
// list, that list must be assignable to the function's
|
||||
// parameter types. Functions meant to apply to arguments of
|
||||
// arbitrary type can use parameters of type interface{} or of
|
||||
// type reflect.Value.
|
||||
f := wrap(name, fn) // wrap it so that it meets API expectations
|
||||
funcMap[name] = f // add it
|
||||
}
|
||||
|
||||
var err error
|
||||
tmpl := template.New(TemplateName)
|
||||
tmpl = tmpl.Funcs(funcMap)
|
||||
tmpl, err = tmpl.Parse(templateText)
|
||||
if err != nil {
|
||||
return "", errwrap.Wrapf(err, "template: parse error")
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if vars == nil {
|
||||
// run the template
|
||||
if err := tmpl.Execute(buf, nil); err != nil {
|
||||
return "", errwrap.Wrapf(err, "template: execution error")
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// NOTE: any objects in here can have their methods called by the template!
|
||||
var data interface{} // can be many types, eg a struct!
|
||||
v := vars.Copy() // make a copy since we make modifications to it...
|
||||
Loop:
|
||||
// TODO: simplify with Type.Underlying()
|
||||
for {
|
||||
switch x := v.Type().Kind; x {
|
||||
case types.KindBool:
|
||||
fallthrough
|
||||
case types.KindStr:
|
||||
fallthrough
|
||||
case types.KindInt:
|
||||
fallthrough
|
||||
case types.KindFloat:
|
||||
// standalone values can be used in templates with a dot
|
||||
data = v.Value()
|
||||
break Loop
|
||||
|
||||
case types.KindList:
|
||||
// TODO: can we improve on this to expose indexes?
|
||||
data = v.Value()
|
||||
break Loop
|
||||
|
||||
case types.KindMap:
|
||||
if v.Type().Key.Cmp(types.TypeStr) != nil {
|
||||
return "", errwrap.Wrapf(err, "template: map keys must be str")
|
||||
}
|
||||
m := make(map[string]interface{})
|
||||
for k, v := range v.Map() { // map[Value]Value
|
||||
m[k.Str()] = v.Value()
|
||||
}
|
||||
data = m
|
||||
break Loop
|
||||
|
||||
case types.KindStruct:
|
||||
m := make(map[string]interface{})
|
||||
for k, v := range v.Struct() { // map[string]Value
|
||||
m[k] = v.Value()
|
||||
}
|
||||
data = m
|
||||
break Loop
|
||||
|
||||
// TODO: should we allow functions here?
|
||||
//case types.KindFunc:
|
||||
|
||||
case types.KindVariant:
|
||||
v = v.(*types.VariantValue).V // un-nest and recurse
|
||||
continue Loop
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("can't use `%+v` as vars input", x)
|
||||
}
|
||||
}
|
||||
|
||||
// run the template
|
||||
if err := tmpl.Execute(buf, data); err != nil {
|
||||
return "", errwrap.Wrapf(err, "template: execution error")
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *TemplateFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
for {
|
||||
select {
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
return nil // can't output any more
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
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(tmpl, vars)
|
||||
if err != nil {
|
||||
return err // no errwrap needed b/c helper func
|
||||
}
|
||||
|
||||
if obj.result != nil && *obj.result == result {
|
||||
continue // result didn't change
|
||||
}
|
||||
obj.result = &result // store new result
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: *obj.result,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return 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.
|
||||
func safename(name string) string {
|
||||
// TODO: should we pick a different replacement char?
|
||||
char := funcs.ReplaceChar // can't be any of: .-#
|
||||
result := strings.Replace(name, funcs.ModuleSep, char, -1)
|
||||
result = strings.Replace(result, "/", char, -1) // nested imports
|
||||
if result == name {
|
||||
// No change, so add a prefix for package-less functions... This
|
||||
// prevents conflicts from sys.func1 -> sys_func1 which would be
|
||||
// a conflict with a top-level function named sys_func1 which is
|
||||
// now renamed to _sys_func1.
|
||||
return char + name
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// wrap builds a function in the format expected by the template engine, and
|
||||
// returns it as an interface{}. It does so by wrapping our type system and
|
||||
// function API with what is expected from the reflection API. It returns a
|
||||
// version that includes the optional second error return value so that our
|
||||
// functions can return errors without causing a panic.
|
||||
func wrap(name string, fn *types.FuncValue) interface{} {
|
||||
if fn.T.Map == nil {
|
||||
panic("malformed func type")
|
||||
}
|
||||
if len(fn.T.Map) != len(fn.T.Ord) {
|
||||
panic("malformed func length")
|
||||
}
|
||||
in := []reflect.Type{}
|
||||
for _, k := range fn.T.Ord {
|
||||
t, ok := fn.T.Map[k]
|
||||
if !ok {
|
||||
panic("malformed func order")
|
||||
}
|
||||
if t == nil {
|
||||
panic("malformed func arg")
|
||||
}
|
||||
|
||||
in = append(in, t.Reflect())
|
||||
}
|
||||
out := []reflect.Type{fn.T.Out.Reflect(), errorType}
|
||||
var variadic = false // currently not supported in our function value
|
||||
typ := reflect.FuncOf(in, out, variadic)
|
||||
|
||||
// wrap our function with the translation that is necessary
|
||||
f := func(args []reflect.Value) (results []reflect.Value) { // build
|
||||
innerArgs := []types.Value{}
|
||||
zeroValue := reflect.Zero(fn.T.Out.Reflect()) // zero value of return type
|
||||
for _, x := range args {
|
||||
v, err := types.ValueOf(x) // reflect.Value -> Value
|
||||
if err != nil {
|
||||
r := reflect.ValueOf(errwrap.Wrapf(err, "function `%s` errored", name))
|
||||
if !r.Type().ConvertibleTo(errorType) { // for fun!
|
||||
r = reflect.ValueOf(fmt.Errorf("function `%s` errored: %+v", name, err))
|
||||
}
|
||||
e := r.Convert(errorType) // must be seen as an `error`
|
||||
return []reflect.Value{zeroValue, e}
|
||||
}
|
||||
innerArgs = append(innerArgs, v)
|
||||
}
|
||||
|
||||
result, err := fn.Call(innerArgs) // call it
|
||||
if err != nil { // function errored :(
|
||||
// errwrap is a better way to report errors, if allowed!
|
||||
r := reflect.ValueOf(errwrap.Wrapf(err, "function `%s` errored", name))
|
||||
if !r.Type().ConvertibleTo(errorType) { // for fun!
|
||||
r = reflect.ValueOf(fmt.Errorf("function `%s` errored: %+v", name, err))
|
||||
}
|
||||
e := r.Convert(errorType) // must be seen as an `error`
|
||||
return []reflect.Value{zeroValue, e}
|
||||
} else if result == nil { // someone wrote a bad function
|
||||
r := reflect.ValueOf(fmt.Errorf("function `%s` returned nil", name))
|
||||
e := r.Convert(errorType) // must be seen as an `error`
|
||||
return []reflect.Value{zeroValue, e}
|
||||
}
|
||||
|
||||
nilError := reflect.Zero(errorType)
|
||||
return []reflect.Value{reflect.ValueOf(result.Value()), nilError}
|
||||
}
|
||||
val := reflect.MakeFunc(typ, f)
|
||||
return val.Interface()
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coretest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// FastCountFuncName is the name this fact is registered as. It's still
|
||||
// a Func Name because this is the name space the fact is actually
|
||||
// using.
|
||||
FastCountFuncName = "fastcount"
|
||||
)
|
||||
|
||||
func init() {
|
||||
facts.ModuleRegister(ModuleName, FastCountFuncName, func() facts.Fact { return &FastCountFact{} }) // must register the fact and name
|
||||
}
|
||||
|
||||
// FastCountFact is a fact that counts up as fast as possible from zero forever.
|
||||
type FastCountFact struct {
|
||||
init *facts.Init
|
||||
}
|
||||
|
||||
// String returns a simple name for this fact. This is needed so this struct can
|
||||
// satisfy the pgraph.Vertex interface.
|
||||
func (obj *FastCountFact) String() string {
|
||||
return FastCountFuncName
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal facts that users can use directly.
|
||||
//func (obj *FastCountFact) Validate() error {
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *FastCountFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Output: types.NewType("int"),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this fact.
|
||||
func (obj *FastCountFact) Init(init *facts.Init) error {
|
||||
obj.init = init
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this fact has over time.
|
||||
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 {
|
||||
select {
|
||||
case obj.init.Output <- &types.IntValue{V: count}:
|
||||
count++
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coretest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs/facts"
|
||||
"github.com/purpleidea/mgmt/lang/funcs/simple"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// OneInstanceAFuncName is the name this fact is registered as. It's
|
||||
// still a Func Name because this is the name space the fact is actually
|
||||
// using.
|
||||
OneInstanceAFuncName = "one_instance_a"
|
||||
|
||||
// OneInstanceBFuncName is the name this fact is registered as. It's
|
||||
// still a Func Name because this is the name space the fact is actually
|
||||
// using.
|
||||
OneInstanceBFuncName = "one_instance_b"
|
||||
|
||||
// OneInstanceCFuncName is the name this fact is registered as. It's
|
||||
// still a Func Name because this is the name space the fact is actually
|
||||
// using.
|
||||
OneInstanceCFuncName = "one_instance_c"
|
||||
|
||||
// OneInstanceDFuncName is the name this fact is registered as. It's
|
||||
// still a Func Name because this is the name space the fact is actually
|
||||
// using.
|
||||
OneInstanceDFuncName = "one_instance_d"
|
||||
|
||||
// OneInstanceEFuncName is the name this fact is registered as. It's
|
||||
// still a Func Name because this is the name space the fact is actually
|
||||
// using.
|
||||
OneInstanceEFuncName = "one_instance_e"
|
||||
|
||||
// OneInstanceFFuncName is the name this fact is registered as. It's
|
||||
// still a Func Name because this is the name space the fact is actually
|
||||
// using.
|
||||
OneInstanceFFuncName = "one_instance_f"
|
||||
|
||||
// OneInstanceGFuncName is the name this fact is registered as. It's
|
||||
// still a Func Name because this is the name space the fact is actually
|
||||
// using.
|
||||
OneInstanceGFuncName = "one_instance_g"
|
||||
|
||||
// OneInstanceHFuncName is the name this fact is registered as. It's
|
||||
// still a Func Name because this is the name space the fact is actually
|
||||
// using.
|
||||
OneInstanceHFuncName = "one_instance_h"
|
||||
|
||||
msg = "hello"
|
||||
)
|
||||
|
||||
func init() {
|
||||
oneInstanceAMutex = &sync.Mutex{}
|
||||
oneInstanceBMutex = &sync.Mutex{}
|
||||
oneInstanceCMutex = &sync.Mutex{}
|
||||
oneInstanceDMutex = &sync.Mutex{}
|
||||
oneInstanceEMutex = &sync.Mutex{}
|
||||
oneInstanceFMutex = &sync.Mutex{}
|
||||
oneInstanceGMutex = &sync.Mutex{}
|
||||
oneInstanceHMutex = &sync.Mutex{}
|
||||
|
||||
facts.ModuleRegister(ModuleName, OneInstanceAFuncName, func() facts.Fact {
|
||||
return &OneInstanceFact{
|
||||
Name: OneInstanceAFuncName,
|
||||
Mutex: oneInstanceAMutex,
|
||||
Flag: &oneInstanceAFlag,
|
||||
}
|
||||
}) // must register the fact and name
|
||||
facts.ModuleRegister(ModuleName, OneInstanceCFuncName, func() facts.Fact {
|
||||
return &OneInstanceFact{
|
||||
Name: OneInstanceCFuncName,
|
||||
Mutex: oneInstanceCMutex,
|
||||
Flag: &oneInstanceCFlag,
|
||||
}
|
||||
})
|
||||
facts.ModuleRegister(ModuleName, OneInstanceEFuncName, func() facts.Fact {
|
||||
return &OneInstanceFact{
|
||||
Name: OneInstanceEFuncName,
|
||||
Mutex: oneInstanceEMutex,
|
||||
Flag: &oneInstanceEFlag,
|
||||
}
|
||||
})
|
||||
facts.ModuleRegister(ModuleName, OneInstanceGFuncName, func() facts.Fact {
|
||||
return &OneInstanceFact{
|
||||
Name: OneInstanceGFuncName,
|
||||
Mutex: oneInstanceGMutex,
|
||||
Flag: &oneInstanceGFlag,
|
||||
}
|
||||
})
|
||||
|
||||
simple.ModuleRegister(ModuleName, OneInstanceBFuncName, &types.FuncValue{
|
||||
T: types.NewType("func() str"),
|
||||
V: func([]types.Value) (types.Value, error) {
|
||||
oneInstanceBMutex.Lock()
|
||||
if oneInstanceBFlag {
|
||||
panic("should not get called twice")
|
||||
}
|
||||
oneInstanceBFlag = true
|
||||
oneInstanceBMutex.Unlock()
|
||||
return &types.StrValue{V: msg}, nil
|
||||
},
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, OneInstanceDFuncName, &types.FuncValue{
|
||||
T: types.NewType("func() str"),
|
||||
V: func([]types.Value) (types.Value, error) {
|
||||
oneInstanceDMutex.Lock()
|
||||
if oneInstanceDFlag {
|
||||
panic("should not get called twice")
|
||||
}
|
||||
oneInstanceDFlag = true
|
||||
oneInstanceDMutex.Unlock()
|
||||
return &types.StrValue{V: msg}, nil
|
||||
},
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, OneInstanceFFuncName, &types.FuncValue{
|
||||
T: types.NewType("func() str"),
|
||||
V: func([]types.Value) (types.Value, error) {
|
||||
oneInstanceFMutex.Lock()
|
||||
if oneInstanceFFlag {
|
||||
panic("should not get called twice")
|
||||
}
|
||||
oneInstanceFFlag = true
|
||||
oneInstanceFMutex.Unlock()
|
||||
return &types.StrValue{V: msg}, nil
|
||||
},
|
||||
})
|
||||
simple.ModuleRegister(ModuleName, OneInstanceHFuncName, &types.FuncValue{
|
||||
T: types.NewType("func() str"),
|
||||
V: func([]types.Value) (types.Value, error) {
|
||||
oneInstanceHMutex.Lock()
|
||||
if oneInstanceHFlag {
|
||||
panic("should not get called twice")
|
||||
}
|
||||
oneInstanceHFlag = true
|
||||
oneInstanceHMutex.Unlock()
|
||||
return &types.StrValue{V: msg}, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
oneInstanceAFlag bool
|
||||
oneInstanceBFlag bool
|
||||
oneInstanceCFlag bool
|
||||
oneInstanceDFlag bool
|
||||
oneInstanceEFlag bool
|
||||
oneInstanceFFlag bool
|
||||
oneInstanceGFlag bool
|
||||
oneInstanceHFlag bool
|
||||
oneInstanceAMutex *sync.Mutex
|
||||
oneInstanceBMutex *sync.Mutex
|
||||
oneInstanceCMutex *sync.Mutex
|
||||
oneInstanceDMutex *sync.Mutex
|
||||
oneInstanceEMutex *sync.Mutex
|
||||
oneInstanceFMutex *sync.Mutex
|
||||
oneInstanceGMutex *sync.Mutex
|
||||
oneInstanceHMutex *sync.Mutex
|
||||
)
|
||||
|
||||
// OneInstanceFact is a fact which flips a bool repeatedly. This is an example
|
||||
// fact and is not meant for serious computing. This would be better served by a
|
||||
// flip function which you could specify an interval for.
|
||||
type OneInstanceFact struct {
|
||||
init *facts.Init
|
||||
|
||||
Name string
|
||||
Mutex *sync.Mutex
|
||||
Flag *bool
|
||||
}
|
||||
|
||||
// String returns a simple name for this fact. This is needed so this struct can
|
||||
// satisfy the pgraph.Vertex interface.
|
||||
func (obj *OneInstanceFact) String() string {
|
||||
return obj.Name
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal facts that users can use directly.
|
||||
//func (obj *OneInstanceFact) Validate() error {
|
||||
// return nil
|
||||
//}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *OneInstanceFact) Info() *facts.Info {
|
||||
return &facts.Info{
|
||||
Output: types.NewType("str"),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this fact.
|
||||
func (obj *OneInstanceFact) Init(init *facts.Init) error {
|
||||
obj.init = init
|
||||
obj.init.Logf("Init of `%s` @ %p", obj.Name, obj)
|
||||
|
||||
obj.Mutex.Lock()
|
||||
if *obj.Flag {
|
||||
panic("should not get called twice")
|
||||
}
|
||||
b := true
|
||||
obj.Flag = &b
|
||||
obj.Mutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this fact has over time.
|
||||
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
|
||||
select {
|
||||
case obj.init.Output <- &types.StrValue{
|
||||
V: msg,
|
||||
}:
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coretest
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "test"
|
||||
)
|
||||
@@ -1,545 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corevalue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// GetFuncName is the name this function is registered as. This variant
|
||||
// is the fanciest version, although type unification is much more
|
||||
// difficult when using this.
|
||||
// XXX: type unification doesn't work perfectly here yet... maybe a bug with returned structs?
|
||||
GetFuncName = "get"
|
||||
|
||||
// GetBoolFuncName is the name this function is registered as. This
|
||||
// variant can only pull in values of type bool.
|
||||
GetBoolFuncName = "get_bool"
|
||||
|
||||
// GetStrFuncName is the name this function is registered as. This
|
||||
// variant can only pull in values of type str.
|
||||
GetStrFuncName = "get_str"
|
||||
|
||||
// GetIntFuncName is the name this function is registered as. This
|
||||
// variant can only pull in values of type int.
|
||||
GetIntFuncName = "get_int"
|
||||
|
||||
// GetFloatFuncName is the name this function is registered as. This
|
||||
// variant can only pull in values of type float.
|
||||
GetFloatFuncName = "get_float"
|
||||
|
||||
// arg names...
|
||||
getArgNameKey = "key"
|
||||
// struct field names...
|
||||
getFieldNameValue = "value"
|
||||
getFieldNameReady = "ready"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, GetFuncName, func() interfaces.Func { return &GetFunc{} })
|
||||
funcs.ModuleRegister(ModuleName, GetBoolFuncName, func() interfaces.Func { return &GetFunc{Type: types.TypeBool} })
|
||||
funcs.ModuleRegister(ModuleName, GetStrFuncName, func() interfaces.Func { return &GetFunc{Type: types.TypeStr} })
|
||||
funcs.ModuleRegister(ModuleName, GetIntFuncName, func() interfaces.Func { return &GetFunc{Type: types.TypeInt} })
|
||||
funcs.ModuleRegister(ModuleName, GetFloatFuncName, func() interfaces.Func { return &GetFunc{Type: types.TypeFloat} })
|
||||
}
|
||||
|
||||
// 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
|
||||
// of that type. It is instead recommended to use the Get* functions that are
|
||||
// more strictly typed.
|
||||
type GetFunc struct {
|
||||
// Type is the actual type being used for the value we are looking up.
|
||||
Type *types.Type
|
||||
|
||||
init *interfaces.Init
|
||||
|
||||
key string
|
||||
|
||||
last types.Value
|
||||
result types.Value // last calculated output
|
||||
|
||||
watchChan chan struct{}
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *GetFunc) String() string {
|
||||
return GetFuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *GetFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{getArgNameKey}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Unify returns the list of invariants that this func produces.
|
||||
func (obj *GetFunc) Unify(expr interfaces.Expr) ([]interfaces.Invariant, error) {
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
if obj.Type != nil { // if we set the type statically, unify is simple
|
||||
sig := obj.sig() // helper
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: expr,
|
||||
Type: sig,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
return invariants, nil
|
||||
}
|
||||
|
||||
// func(key str) struct{value T1; ready bool}
|
||||
|
||||
keyName, err := obj.ArgGen(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dummyKey := &interfaces.ExprAny{} // corresponds to the key type
|
||||
dummyOut := &interfaces.ExprAny{} // corresponds to the out struct
|
||||
dummyValue := &interfaces.ExprAny{} // corresponds to the value type
|
||||
dummyReady := &interfaces.ExprAny{} // corresponds to the ready type
|
||||
|
||||
// the known types...
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: dummyKey,
|
||||
Type: types.TypeStr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: dummyReady,
|
||||
Type: types.TypeBool,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// relationship between Out and T1
|
||||
// TODO: do the precise field string names matter or can we cmp anyways?
|
||||
mapped := make(map[string]interfaces.Expr)
|
||||
ordered := []string{getFieldNameValue, getFieldNameReady}
|
||||
mapped[getFieldNameValue] = dummyValue
|
||||
mapped[getFieldNameReady] = dummyReady
|
||||
invar = &interfaces.EqualityWrapStructInvariant{
|
||||
Expr1: dummyOut, // unique id for this expression (a pointer)
|
||||
Expr2Map: mapped,
|
||||
Expr2Ord: ordered,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// full function
|
||||
invar = &interfaces.EqualityWrapFuncInvariant{
|
||||
Expr1: expr, // maps directly to us!
|
||||
Expr2Map: map[string]interfaces.Expr{keyName: dummyKey},
|
||||
Expr2Ord: []string{keyName},
|
||||
Expr2Out: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// generator function
|
||||
fn := func(fnInvariants []interfaces.Invariant, solved map[interfaces.Expr]*types.Type) ([]interfaces.Invariant, error) {
|
||||
for _, invariant := range fnInvariants {
|
||||
// search for this special type of invariant
|
||||
cfavInvar, ok := invariant.(*interfaces.CallFuncArgsValueInvariant)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// did we find the mapping from us to ExprCall ?
|
||||
if cfavInvar.Func != expr {
|
||||
continue
|
||||
}
|
||||
// cfavInvar.Expr is the ExprCall! (the return pointer)
|
||||
// cfavInvar.Args are the args that ExprCall uses!
|
||||
if l := len(cfavInvar.Args); l != 1 {
|
||||
return nil, fmt.Errorf("unable to build function with %d args", l)
|
||||
}
|
||||
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
// add the relationship to the returned value
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Expr,
|
||||
Expr2: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// add the relationships to the called args
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Args[0],
|
||||
Expr2: dummyKey,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// If we figure out this type, we'll know the full type!
|
||||
var t1 *types.Type // value type
|
||||
|
||||
// validateArg0 checks: key input
|
||||
validateArg0 := func(typ *types.Type) error {
|
||||
if typ == nil { // unknown so far
|
||||
return nil
|
||||
}
|
||||
if typ.Kind != types.KindStr {
|
||||
return errwrap.Wrapf(err, "input index type was inconsistent")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateOut checks: T1
|
||||
validateOut := func(typ *types.Type) error {
|
||||
if typ == nil { // unknown so far
|
||||
return nil
|
||||
}
|
||||
|
||||
// we happen to have a struct!
|
||||
if k := typ.Kind; k != types.KindStruct {
|
||||
return fmt.Errorf("unable to build function with return type of kind: %s", k)
|
||||
}
|
||||
|
||||
if typ.Map == nil || len(typ.Ord) == 0 {
|
||||
// programming error
|
||||
return fmt.Errorf("return struct is missing type")
|
||||
}
|
||||
|
||||
// TODO: do the precise field string names
|
||||
// matter or can we cmp anyways?
|
||||
tReady, exists := typ.Map[getFieldNameReady]
|
||||
if !exists {
|
||||
return fmt.Errorf("return struct is missing ready field")
|
||||
}
|
||||
if tReady.Kind != types.KindBool {
|
||||
return fmt.Errorf("return struct ready field must be bool kind")
|
||||
}
|
||||
|
||||
tValue, exists := typ.Map[getFieldNameValue]
|
||||
if !exists {
|
||||
return fmt.Errorf("return struct is missing value field")
|
||||
}
|
||||
|
||||
if err := tValue.Cmp(t1); t1 != nil && err != nil {
|
||||
return errwrap.Wrapf(err, "value type was inconsistent")
|
||||
}
|
||||
|
||||
// learn!
|
||||
t1 = tValue
|
||||
return nil
|
||||
}
|
||||
|
||||
if typ, err := cfavInvar.Args[0].Type(); err == nil { // is it known?
|
||||
// this only checks if this is an str
|
||||
if err := validateArg0(typ); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "first list arg type is inconsistent")
|
||||
}
|
||||
}
|
||||
if typ, exists := solved[cfavInvar.Args[0]]; exists { // alternate way to lookup type
|
||||
// this only checks if this is an str
|
||||
if err := validateArg0(typ); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "first list arg type is inconsistent")
|
||||
}
|
||||
}
|
||||
|
||||
// return type...
|
||||
if typ, err := cfavInvar.Expr.Type(); err == nil { // is it known?
|
||||
if err := validateOut(typ); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "return type is inconsistent")
|
||||
}
|
||||
}
|
||||
if typ, exists := solved[cfavInvar.Expr]; exists { // alternate way to lookup type
|
||||
if err := validateOut(typ); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "return type is inconsistent")
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: We need to add a relationship somehow here or
|
||||
// elsewhere between dummyValue and the type we are
|
||||
// expecting.
|
||||
// (1) we shouldn't look on disk in the cached storage.
|
||||
// (2) how can we match on function send/recv values and
|
||||
// resource fields???
|
||||
// (3) worst case scenario we just hope for the best,
|
||||
// and hope we can infer the type some other way...
|
||||
|
||||
// XXX: if the types aren't know statically?
|
||||
|
||||
if t1 != nil {
|
||||
invar := &interfaces.EqualsInvariant{
|
||||
Expr: dummyValue,
|
||||
Type: t1,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
}
|
||||
|
||||
// XXX: if t1 is missing, we could also return a new
|
||||
// generator for later if we learn new information, but
|
||||
// we'd have to be careful to not do it infinitely.
|
||||
|
||||
// TODO: do we return this relationship with ExprCall?
|
||||
invar = &interfaces.EqualityWrapCallInvariant{
|
||||
// TODO: should Expr1 and Expr2 be reversed???
|
||||
Expr1: cfavInvar.Expr,
|
||||
//Expr2Func: cfavInvar.Func, // same as below
|
||||
Expr2Func: expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: are there any other invariants we should build?
|
||||
return invariants, nil // generator return
|
||||
}
|
||||
// We couldn't tell the solver anything it didn't already know!
|
||||
return nil, fmt.Errorf("couldn't generate new invariants")
|
||||
}
|
||||
invar = &interfaces.GeneratorInvariant{
|
||||
Func: fn,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
return invariants, nil
|
||||
}
|
||||
|
||||
// Build is run to turn the polymorphic, undetermined function, into the
|
||||
// specific statically typed version. It is usually run after Unify completes,
|
||||
// and must be run before Info() and any of the other Func interface methods are
|
||||
// used. This function is idempotent, as long as the arg isn't changed between
|
||||
// runs.
|
||||
func (obj *GetFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
// typ is the KindFunc signature we're trying to build...
|
||||
if typ.Kind != types.KindFunc {
|
||||
return nil, fmt.Errorf("input type must be of kind func")
|
||||
}
|
||||
|
||||
if typ.Map == nil {
|
||||
return nil, fmt.Errorf("invalid input type")
|
||||
}
|
||||
if len(typ.Ord) != 1 {
|
||||
return nil, fmt.Errorf("the function needs exactly one arg")
|
||||
}
|
||||
if typ.Out == nil {
|
||||
return nil, fmt.Errorf("return type of function must be specified")
|
||||
}
|
||||
|
||||
tKey, exists := typ.Map[typ.Ord[0]]
|
||||
if !exists || tKey == nil {
|
||||
return nil, fmt.Errorf("first arg must be specified")
|
||||
}
|
||||
if tKey.Kind != types.KindStr {
|
||||
return nil, fmt.Errorf("key must be str kind")
|
||||
}
|
||||
|
||||
if typ.Out.Kind != types.KindStruct {
|
||||
return nil, fmt.Errorf("return must be kind struct")
|
||||
}
|
||||
if typ.Out.Map == nil {
|
||||
return nil, fmt.Errorf("invalid return type")
|
||||
}
|
||||
if len(typ.Out.Ord) != 2 {
|
||||
return nil, fmt.Errorf("invalid return type")
|
||||
}
|
||||
tValue, exists := typ.Out.Map[typ.Out.Ord[0]]
|
||||
if !exists || tValue == nil {
|
||||
return nil, fmt.Errorf("first struct field must be specified")
|
||||
}
|
||||
tReady, exists := typ.Out.Map[typ.Out.Ord[1]]
|
||||
if !exists || tReady == nil {
|
||||
return nil, fmt.Errorf("second struct field must be specified")
|
||||
}
|
||||
if tReady.Kind != types.KindBool {
|
||||
return nil, fmt.Errorf("second struct field must be bool kind")
|
||||
}
|
||||
|
||||
obj.Type = tValue // type of our value
|
||||
return obj.sig(), nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *GetFunc) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *GetFunc) Info() *interfaces.Info {
|
||||
var sig *types.Type
|
||||
if obj.Type != nil { // don't panic if called speculatively
|
||||
sig = obj.sig() // helper
|
||||
}
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
Sig: sig,
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// helper
|
||||
func (obj *GetFunc) sig() *types.Type {
|
||||
// output is a struct with two fields:
|
||||
// value is the zero value if not ready. A bool for that in other field.
|
||||
return types.NewType(fmt.Sprintf("func(%s str) struct{%s %s; %s bool}", getArgNameKey, getFieldNameValue, obj.Type.String(), getFieldNameReady))
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *GetFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
obj.watchChan = make(chan struct{}) // sender closes this when Stream ends
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *GetFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel() // important so that we cleanup the watch when exiting
|
||||
for {
|
||||
select {
|
||||
// TODO: should this first chan be run as a priority channel to
|
||||
// avoid some sort of glitch? is that even possible? can our
|
||||
// hostname check with reality (below) fix that?
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // don't infinite loop back
|
||||
continue // no more inputs, but don't return!
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
key := input.Struct()[getArgNameKey].Str()
|
||||
if key == "" {
|
||||
return fmt.Errorf("can't use an empty key")
|
||||
}
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("key: %s", key)
|
||||
}
|
||||
|
||||
// We don't support changing the key over time, since it
|
||||
// might cause the type to need to be changed.
|
||||
if obj.key == "" {
|
||||
obj.key = key // store it
|
||||
var err error
|
||||
// Don't send a value right away, wait for the
|
||||
// first ValueWatch startup event to get one!
|
||||
obj.watchChan, err = obj.init.Local.ValueWatch(ctx, obj.key) // watch for var changes
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else if obj.key != key {
|
||||
return fmt.Errorf("can't change key, previously: `%s`", obj.key)
|
||||
}
|
||||
|
||||
continue // we get values on the watch chan, not here!
|
||||
|
||||
case _, ok := <-obj.watchChan:
|
||||
if !ok { // closed
|
||||
return nil
|
||||
}
|
||||
//if err != nil {
|
||||
// return errwrap.Wrapf(err, "channel watch failed on `%s`", obj.key)
|
||||
//}
|
||||
|
||||
result, err := obj.getValue(ctx) // get the value...
|
||||
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
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getValue gets the value we're looking for.
|
||||
func (obj *GetFunc) getValue(ctx context.Context) (types.Value, error) {
|
||||
typ, exists := obj.Info().Sig.Out.Map[getFieldNameValue] // type of value field
|
||||
if !exists || typ == nil {
|
||||
// programming error
|
||||
return nil, fmt.Errorf("missing type for %s field", getFieldNameValue)
|
||||
}
|
||||
|
||||
// The API will pull from the on-disk stored cache if present... This
|
||||
// value comes from the field in the Value resource... We only have an
|
||||
// on-disk cache because since functions load before resources do, we'd
|
||||
// like to warm the cache with the right value before the resource can
|
||||
// issue a new one to our in-memory store. This avoids a re-provisioning
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", obj.key)
|
||||
}
|
||||
if val == nil { // val doesn't exist
|
||||
isReady = false
|
||||
}
|
||||
|
||||
ready := &types.BoolValue{V: isReady}
|
||||
value := typ.New() // new zero value of that typ
|
||||
if isReady {
|
||||
value, err = types.ValueOfGolang(val) // interface{} -> types.Value
|
||||
if err != nil {
|
||||
// programming error
|
||||
return nil, errwrap.Wrapf(err, "invalid value")
|
||||
}
|
||||
if err := value.Type().Cmp(typ); err != nil {
|
||||
// XXX: when we run get_int, but the resource value is
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
st := types.NewStruct(obj.Info().Sig.Out)
|
||||
if err := st.Set(getFieldNameValue, value); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "struct could not add field `%s`, val: `%s`", getFieldNameValue, value)
|
||||
}
|
||||
if err := st.Set(getFieldNameReady, ready); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "struct could not add field `%s`, val: `%s`", getFieldNameReady, ready)
|
||||
}
|
||||
|
||||
return st, nil // put struct into interface type
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package corevalue
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "value"
|
||||
)
|
||||
@@ -1,200 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreworld
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// ExchangeFuncName is the name this function is registered as.
|
||||
ExchangeFuncName = "exchange"
|
||||
|
||||
// arg names...
|
||||
exchangeArgNameNamespace = "namespace"
|
||||
exchangeArgNameValue = "value"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, ExchangeFuncName, func() interfaces.Func { return &ExchangeFunc{} })
|
||||
}
|
||||
|
||||
// ExchangeFunc is special function which returns all the values of a given key
|
||||
// in the exposed world, and sets it's own.
|
||||
type ExchangeFunc struct {
|
||||
init *interfaces.Init
|
||||
|
||||
namespace string
|
||||
|
||||
last types.Value
|
||||
result types.Value // last calculated output
|
||||
|
||||
watchChan chan error
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *ExchangeFunc) String() string {
|
||||
return ExchangeFuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *ExchangeFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{exchangeArgNameNamespace, exchangeArgNameValue}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *ExchangeFunc) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *ExchangeFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
// TODO: do we want to allow this to be statically polymorphic,
|
||||
// and have value be any type we might want?
|
||||
// output is map of: hostname => value
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str, %s str) map{str: str}", exchangeArgNameNamespace, exchangeArgNameValue)),
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *ExchangeFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
obj.watchChan = make(chan error) // XXX: sender should close this, but did I implement that part yet???
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *ExchangeFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
for {
|
||||
select {
|
||||
// TODO: should this first chan be run as a priority channel to
|
||||
// avoid some sort of glitch? is that even possible? can our
|
||||
// hostname check with reality (below) fix that?
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // don't infinite loop back
|
||||
continue // no more inputs, but don't return!
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
namespace := input.Struct()[exchangeArgNameNamespace].Str()
|
||||
if namespace == "" {
|
||||
return fmt.Errorf("can't use an empty namespace")
|
||||
}
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("namespace: %s", namespace)
|
||||
}
|
||||
|
||||
// TODO: support changing the namespace over time...
|
||||
// TODO: possibly removing our stored value there first!
|
||||
if obj.namespace == "" {
|
||||
obj.namespace = namespace // store it
|
||||
var err error
|
||||
obj.watchChan, err = obj.init.World.StrMapWatch(ctx, obj.namespace) // watch for var changes
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else if obj.namespace != namespace {
|
||||
return fmt.Errorf("can't change namespace, previously: `%s`", obj.namespace)
|
||||
}
|
||||
|
||||
value := input.Struct()[exchangeArgNameValue].Str()
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("value: %+v", value)
|
||||
}
|
||||
|
||||
if err := obj.init.World.StrMapSet(ctx, obj.namespace, value); err != nil {
|
||||
return errwrap.Wrapf(err, "namespace write error of `%s` to `%s`", value, obj.namespace)
|
||||
}
|
||||
|
||||
continue // we get values on the watch chan, not here!
|
||||
|
||||
case err, ok := <-obj.watchChan:
|
||||
if !ok { // closed
|
||||
// XXX: if we close, perhaps the engine is
|
||||
// switching etcd hosts and we should retry?
|
||||
// maybe instead we should get an "etcd
|
||||
// reconnect" signal, and the lang will restart?
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "channel watch failed on `%s`", obj.namespace)
|
||||
}
|
||||
|
||||
keyMap, err := obj.init.World.StrMapGet(ctx, obj.namespace)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "channel read failed on `%s`", obj.namespace)
|
||||
}
|
||||
|
||||
var result types.Value
|
||||
|
||||
d := types.NewMap(obj.Info().Sig.Out)
|
||||
for k, v := range keyMap {
|
||||
key := &types.StrValue{V: k}
|
||||
val := &types.StrValue{V: v}
|
||||
if err := d.Add(key, val); err != nil {
|
||||
return errwrap.Wrapf(err, "map could not add key `%s`, val: `%s`", k, v)
|
||||
}
|
||||
}
|
||||
result = d // put map into interface type
|
||||
|
||||
// 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
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreworld
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// GetValFuncName is the name this function is registered as.
|
||||
GetValFuncName = "getval"
|
||||
|
||||
// arg names...
|
||||
getValArgNameKey = "key"
|
||||
|
||||
// struct field names...
|
||||
getValFieldNameValue = "value"
|
||||
getValFieldNameExists = "exists"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, GetValFuncName, func() interfaces.Func { return &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
|
||||
|
||||
last types.Value
|
||||
result types.Value // last calculated output
|
||||
|
||||
watchChan chan error
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *GetValFunc) String() string {
|
||||
return GetValFuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *GetValFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{getValArgNameKey}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *GetValFunc) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *GetValFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
// output is a struct with two fields:
|
||||
// value is the zero value if not exists. A bool for that in other field.
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) struct{%s str; %s bool}", getValArgNameKey, getValFieldNameValue, getValFieldNameExists)),
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *GetValFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
obj.watchChan = make(chan error) // XXX: sender should close this, but did I implement that part yet???
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *GetValFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel() // important so that we cleanup the watch when exiting
|
||||
for {
|
||||
select {
|
||||
// TODO: should this first chan be run as a priority channel to
|
||||
// avoid some sort of glitch? is that even possible? can our
|
||||
// hostname check with reality (below) fix that?
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // don't infinite loop back
|
||||
continue // no more inputs, but don't return!
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
key := input.Struct()[getValArgNameKey].Str()
|
||||
if key == "" {
|
||||
return fmt.Errorf("can't use an empty key")
|
||||
}
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("key: %s", key)
|
||||
}
|
||||
|
||||
// TODO: support changing the key over time...
|
||||
if obj.key == "" {
|
||||
obj.key = key // store it
|
||||
var err error
|
||||
// Don't send a value right away, wait for the
|
||||
// first ValueWatch startup event to get one!
|
||||
obj.watchChan, err = obj.init.World.StrWatch(ctx, obj.key) // watch for var changes
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else if obj.key != key {
|
||||
return fmt.Errorf("can't change key, previously: `%s`", obj.key)
|
||||
}
|
||||
|
||||
continue // we get values on the watch chan, not here!
|
||||
|
||||
case err, ok := <-obj.watchChan:
|
||||
if !ok { // closed
|
||||
// XXX: if we close, perhaps the engine is
|
||||
// switching etcd hosts and we should retry?
|
||||
// maybe instead we should get an "etcd
|
||||
// reconnect" signal, and the lang will restart?
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "channel watch failed on `%s`", obj.key)
|
||||
}
|
||||
|
||||
result, err := obj.getValue(ctx) // get the value...
|
||||
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
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getValue gets the value we're looking for.
|
||||
func (obj *GetValFunc) getValue(ctx context.Context) (types.Value, error) {
|
||||
exists := true // assume true
|
||||
val, err := obj.init.World.StrGet(ctx, obj.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)
|
||||
}
|
||||
|
||||
s := &types.StrValue{V: val}
|
||||
b := &types.BoolValue{V: exists}
|
||||
st := types.NewStruct(obj.Info().Sig.Out)
|
||||
if err := st.Set(getValFieldNameValue, s); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "struct could not add field `%s`, val: `%s`", getValFieldNameValue, s)
|
||||
}
|
||||
if err := st.Set(getValFieldNameExists, b); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "struct could not add field `%s`, val: `%s`", getValFieldNameExists, b)
|
||||
}
|
||||
|
||||
return st, nil // put struct into interface type
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreworld
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// KVLookupFuncName is the name this function is registered as.
|
||||
KVLookupFuncName = "kvlookup"
|
||||
|
||||
// arg names...
|
||||
kvLookupArgNameNamespace = "namespace"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, KVLookupFuncName, func() interfaces.Func { return &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
|
||||
|
||||
last types.Value
|
||||
result types.Value // last calculated output
|
||||
|
||||
watchChan chan error
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *KVLookupFunc) String() string {
|
||||
return KVLookupFuncName
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *KVLookupFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{kvLookupArgNameNamespace}
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Validate makes sure we've built our struct properly. It is usually unused for
|
||||
// normal functions that users can use directly.
|
||||
func (obj *KVLookupFunc) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself.
|
||||
func (obj *KVLookupFunc) Info() *interfaces.Info {
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
// output is map of: hostname => value
|
||||
Sig: types.NewType(fmt.Sprintf("func(%s str) map{str: str}", kvLookupArgNameNamespace)),
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *KVLookupFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
obj.watchChan = make(chan error) // XXX: sender should close this, but did I implement that part yet???
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *KVLookupFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
for {
|
||||
select {
|
||||
// TODO: should this first chan be run as a priority channel to
|
||||
// avoid some sort of glitch? is that even possible? can our
|
||||
// hostname check with reality (below) fix that?
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // don't infinite loop back
|
||||
continue // no more inputs, but don't return!
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
namespace := input.Struct()[kvLookupArgNameNamespace].Str()
|
||||
if namespace == "" {
|
||||
return fmt.Errorf("can't use an empty namespace")
|
||||
}
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("namespace: %s", namespace)
|
||||
}
|
||||
|
||||
// TODO: support changing the namespace over time...
|
||||
// TODO: possibly removing our stored value there first!
|
||||
if obj.namespace == "" {
|
||||
obj.namespace = namespace // store it
|
||||
var err error
|
||||
obj.watchChan, err = obj.init.World.StrMapWatch(ctx, obj.namespace) // watch for var changes
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result, err := obj.buildMap(ctx) // build the map...
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case obj.init.Output <- result: // send one!
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
} else if obj.namespace != namespace {
|
||||
return fmt.Errorf("can't change namespace, previously: `%s`", obj.namespace)
|
||||
}
|
||||
|
||||
continue // we get values on the watch chan, not here!
|
||||
|
||||
case err, ok := <-obj.watchChan:
|
||||
if !ok { // closed
|
||||
// XXX: if we close, perhaps the engine is
|
||||
// switching etcd hosts and we should retry?
|
||||
// maybe instead we should get an "etcd
|
||||
// reconnect" signal, and the lang will restart?
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "channel watch failed on `%s`", obj.namespace)
|
||||
}
|
||||
|
||||
result, err := obj.buildMap(ctx) // build the map...
|
||||
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
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "channel read failed on `%s`", obj.namespace)
|
||||
}
|
||||
|
||||
d := types.NewMap(obj.Info().Sig.Out)
|
||||
for k, v := range keyMap {
|
||||
key := &types.StrValue{V: k}
|
||||
val := &types.StrValue{V: v}
|
||||
if err := d.Add(key, val); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "map could not add key `%s`, val: `%s`", k, v)
|
||||
}
|
||||
}
|
||||
return d, nil // put map into interface type
|
||||
}
|
||||
@@ -1,718 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// test with:
|
||||
// time ./mgmt run --hostname h1 --tmp-prefix --no-pgp lang examples/lang/schedule0.mcl
|
||||
// time ./mgmt run --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 --tmp-prefix --no-pgp lang examples/lang/schedule0.mcl
|
||||
// time ./mgmt run --hostname h3 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 --tmp-prefix --no-pgp lang examples/lang/schedule0.mcl
|
||||
// kill h2 (should see h1 and h3 pick [h1, h3] instead)
|
||||
// restart h2 (should see [h1, h3] as before)
|
||||
// kill h3 (should see h1 and h2 pick [h1, h2] instead)
|
||||
// restart h3 (should see [h1, h2] as before)
|
||||
// kill h3
|
||||
// kill h2
|
||||
// kill h1... all done!
|
||||
|
||||
package coreworld
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/purpleidea/mgmt/etcd/scheduler" // TODO: is it okay to import this without abstraction?
|
||||
"github.com/purpleidea/mgmt/lang/funcs"
|
||||
"github.com/purpleidea/mgmt/lang/interfaces"
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
)
|
||||
|
||||
const (
|
||||
// ScheduleFuncName is the name this function is registered as.
|
||||
ScheduleFuncName = "schedule"
|
||||
|
||||
// DefaultStrategy is the strategy to use if none has been specified.
|
||||
DefaultStrategy = "rr"
|
||||
|
||||
// StrictScheduleOpts specifies whether the opts passed into the
|
||||
// scheduler must be strictly what we're expecting, and nothing more.
|
||||
// If this was false, then we'd allow an opts struct that had a field
|
||||
// that wasn't used by the scheduler. This could be useful if we need to
|
||||
// migrate to a newer version of the function. It's probably best to
|
||||
// keep this strict.
|
||||
StrictScheduleOpts = true
|
||||
|
||||
// arg names...
|
||||
scheduleArgNameNamespace = "namespace"
|
||||
scheduleArgNameOpts = "opts"
|
||||
)
|
||||
|
||||
func init() {
|
||||
funcs.ModuleRegister(ModuleName, ScheduleFuncName, func() interfaces.Func { return &ScheduleFunc{} })
|
||||
}
|
||||
|
||||
var _ interfaces.PolyFunc = &ScheduleFunc{} // ensure it meets this expectation
|
||||
|
||||
// ScheduleFunc is special function which determines where code should run in
|
||||
// the cluster.
|
||||
type ScheduleFunc struct {
|
||||
Type *types.Type // this is the type of opts used if specified
|
||||
|
||||
built bool // was this function built yet?
|
||||
|
||||
init *interfaces.Init
|
||||
|
||||
namespace string
|
||||
scheduler *scheduler.Result
|
||||
|
||||
last types.Value
|
||||
result types.Value // last calculated output
|
||||
|
||||
watchChan chan *schedulerResult
|
||||
}
|
||||
|
||||
// String returns a simple name for this function. This is needed so this struct
|
||||
// can satisfy the pgraph.Vertex interface.
|
||||
func (obj *ScheduleFunc) String() string {
|
||||
return ScheduleFuncName
|
||||
}
|
||||
|
||||
// validOpts returns the available mapping of valid opts fields to types.
|
||||
func (obj *ScheduleFunc) validOpts() map[string]*types.Type {
|
||||
return map[string]*types.Type{
|
||||
"strategy": types.TypeStr,
|
||||
"max": types.TypeInt,
|
||||
"reuse": types.TypeBool,
|
||||
"ttl": types.TypeInt,
|
||||
}
|
||||
}
|
||||
|
||||
// ArgGen returns the Nth arg name for this function.
|
||||
func (obj *ScheduleFunc) ArgGen(index int) (string, error) {
|
||||
seq := []string{scheduleArgNameNamespace, scheduleArgNameOpts} // 2nd arg is optional
|
||||
if l := len(seq); index >= l {
|
||||
return "", fmt.Errorf("index %d exceeds arg length of %d", index, l)
|
||||
}
|
||||
return seq[index], nil
|
||||
}
|
||||
|
||||
// Unify returns the list of invariants that this func produces.
|
||||
func (obj *ScheduleFunc) Unify(expr interfaces.Expr) ([]interfaces.Invariant, error) {
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
// func(namespace str) []str
|
||||
// OR
|
||||
// func(namespace str, opts T1) []str
|
||||
|
||||
namespaceName, err := obj.ArgGen(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dummyNamespace := &interfaces.ExprAny{} // corresponds to the namespace type
|
||||
dummyOut := &interfaces.ExprAny{} // corresponds to the out string
|
||||
|
||||
// namespace arg type of string
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: dummyNamespace,
|
||||
Type: types.TypeStr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// return type of []string
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: dummyOut,
|
||||
Type: types.NewType("[]str"),
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// generator function
|
||||
fn := func(fnInvariants []interfaces.Invariant, solved map[interfaces.Expr]*types.Type) ([]interfaces.Invariant, error) {
|
||||
for _, invariant := range fnInvariants {
|
||||
// search for this special type of invariant
|
||||
cfavInvar, ok := invariant.(*interfaces.CallFuncArgsValueInvariant)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// did we find the mapping from us to ExprCall ?
|
||||
if cfavInvar.Func != expr {
|
||||
continue
|
||||
}
|
||||
// cfavInvar.Expr is the ExprCall! (the return pointer)
|
||||
// cfavInvar.Args are the args that ExprCall uses!
|
||||
if len(cfavInvar.Args) == 0 {
|
||||
return nil, fmt.Errorf("unable to build function with no args")
|
||||
}
|
||||
if l := len(cfavInvar.Args); l > 2 {
|
||||
return nil, fmt.Errorf("unable to build function with %d args", l)
|
||||
}
|
||||
// we can either have one arg or two
|
||||
|
||||
var invariants []interfaces.Invariant
|
||||
var invar interfaces.Invariant
|
||||
|
||||
// add the relationship to the returned value
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Expr,
|
||||
Expr2: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// add the relationships to the called args
|
||||
invar = &interfaces.EqualityInvariant{
|
||||
Expr1: cfavInvar.Args[0],
|
||||
Expr2: dummyNamespace,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// first arg must be a string
|
||||
invar = &interfaces.EqualsInvariant{
|
||||
Expr: cfavInvar.Args[0],
|
||||
Type: types.TypeStr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// full function
|
||||
mapped := make(map[string]interfaces.Expr)
|
||||
ordered := []string{namespaceName}
|
||||
mapped[namespaceName] = dummyNamespace
|
||||
|
||||
if len(cfavInvar.Args) == 2 { // two args is more complex
|
||||
dummyOpts := &interfaces.ExprAny{}
|
||||
|
||||
optsTypeKnown := false
|
||||
|
||||
// speculate about the type?
|
||||
if typ, exists := solved[cfavInvar.Args[1]]; exists {
|
||||
optsTypeKnown = true
|
||||
if typ.Kind != types.KindStruct {
|
||||
return nil, fmt.Errorf("second arg must be of kind struct")
|
||||
}
|
||||
|
||||
// XXX: the problem is that I can't
|
||||
// currently express the opts struct as
|
||||
// an invariant, without building a big
|
||||
// giant, unusable exclusive...
|
||||
validOpts := obj.validOpts()
|
||||
|
||||
if StrictScheduleOpts {
|
||||
// strict opts field checking!
|
||||
for _, name := range typ.Ord {
|
||||
t := typ.Map[name]
|
||||
value, exists := validOpts[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("unexpected opts field: `%s`", name)
|
||||
}
|
||||
|
||||
if err := t.Cmp(value); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "expected different type for opts field: `%s`", name)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// permissive field checking...
|
||||
validOptsSorted := []string{}
|
||||
for name := range validOpts {
|
||||
validOptsSorted = append(validOptsSorted, name)
|
||||
}
|
||||
sort.Strings(validOptsSorted)
|
||||
for _, name := range validOptsSorted {
|
||||
value := validOpts[name] // type
|
||||
|
||||
t, exists := typ.Map[name]
|
||||
if !exists {
|
||||
continue // ignore it
|
||||
}
|
||||
|
||||
// if it exists, check the type
|
||||
if err := t.Cmp(value); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "expected different type for opts field: `%s`", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invar := &interfaces.EqualsInvariant{
|
||||
Expr: dummyOpts,
|
||||
Type: typ,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
}
|
||||
// redundant?
|
||||
if typ, err := cfavInvar.Args[1].Type(); err == nil {
|
||||
invar := &interfaces.EqualsInvariant{
|
||||
Expr: cfavInvar.Args[1],
|
||||
Type: typ,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
}
|
||||
|
||||
// If we're strict, require it, otherwise let
|
||||
// in whatever, and let Build() deal with it.
|
||||
if StrictScheduleOpts && !optsTypeKnown {
|
||||
return nil, fmt.Errorf("the type of the opts struct is not known")
|
||||
}
|
||||
|
||||
// expression must match type of the input arg
|
||||
invar := &interfaces.EqualityInvariant{
|
||||
Expr1: dummyOpts,
|
||||
Expr2: cfavInvar.Args[1],
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
mapped[scheduleArgNameOpts] = dummyOpts
|
||||
ordered = append(ordered, scheduleArgNameOpts)
|
||||
}
|
||||
|
||||
invar = &interfaces.EqualityWrapFuncInvariant{
|
||||
Expr1: expr, // maps directly to us!
|
||||
Expr2Map: mapped,
|
||||
Expr2Ord: ordered,
|
||||
Expr2Out: dummyOut,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: do we return this relationship with ExprCall?
|
||||
invar = &interfaces.EqualityWrapCallInvariant{
|
||||
// TODO: should Expr1 and Expr2 be reversed???
|
||||
Expr1: cfavInvar.Expr,
|
||||
//Expr2Func: cfavInvar.Func, // same as below
|
||||
Expr2Func: expr,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
// TODO: are there any other invariants we should build?
|
||||
return invariants, nil // generator return
|
||||
}
|
||||
// We couldn't tell the solver anything it didn't already know!
|
||||
return nil, fmt.Errorf("couldn't generate new invariants")
|
||||
}
|
||||
invar = &interfaces.GeneratorInvariant{
|
||||
Func: fn,
|
||||
}
|
||||
invariants = append(invariants, invar)
|
||||
|
||||
return invariants, nil
|
||||
}
|
||||
|
||||
// Polymorphisms returns the list of possible function signatures available for
|
||||
// this static polymorphic function. It relies on type and value hints to limit
|
||||
// the number of returned possibilities.
|
||||
func (obj *ScheduleFunc) Polymorphisms(partialType *types.Type, partialValues []types.Value) ([]*types.Type, error) {
|
||||
// TODO: technically, we could generate all permutations of the struct!
|
||||
//variant := []*types.Type{}
|
||||
//t0 := types.NewType("func(namespace str) []str")
|
||||
//variant = append(variant, t0)
|
||||
//validOpts := obj.validOpts()
|
||||
//for ? := ? range { // generate all permutations of the struct...
|
||||
// t := types.NewType(fmt.Sprintf("func(namespace str, opts %s) []str", ?))
|
||||
// variant = append(variant, t)
|
||||
//}
|
||||
//if partialType == nil {
|
||||
// return variant, nil
|
||||
//}
|
||||
|
||||
if partialType == nil {
|
||||
return nil, fmt.Errorf("zero type information given")
|
||||
}
|
||||
|
||||
var typ *types.Type
|
||||
|
||||
if tOut := partialType.Out; tOut != nil {
|
||||
if err := tOut.Cmp(types.NewType("[]str")); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "return type must be a list of strings")
|
||||
}
|
||||
}
|
||||
|
||||
ord := partialType.Ord
|
||||
if partialType.Map != nil {
|
||||
if len(ord) == 0 {
|
||||
return nil, fmt.Errorf("must have at least one arg in schedule func")
|
||||
}
|
||||
|
||||
if tNamespace, exists := partialType.Map[ord[0]]; exists && tNamespace != nil {
|
||||
if err := tNamespace.Cmp(types.TypeStr); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "first arg must be an str")
|
||||
}
|
||||
}
|
||||
if len(ord) == 1 {
|
||||
return []*types.Type{types.NewType("func(namespace str) []str")}, nil // done!
|
||||
}
|
||||
|
||||
if len(ord) != 2 {
|
||||
return nil, fmt.Errorf("must have either one or two args in schedule func")
|
||||
}
|
||||
|
||||
if tOpts, exists := partialType.Map[ord[1]]; exists {
|
||||
if tOpts == nil { // usually a `struct{}`
|
||||
typFunc := types.NewType("func(namespace str, opts variant) []str")
|
||||
return []*types.Type{typFunc}, nil // solved!
|
||||
}
|
||||
|
||||
if tOpts.Kind != types.KindStruct {
|
||||
return nil, fmt.Errorf("second arg must be of kind struct")
|
||||
}
|
||||
|
||||
validOpts := obj.validOpts()
|
||||
for _, name := range tOpts.Ord {
|
||||
t := tOpts.Map[name]
|
||||
value, exists := validOpts[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("unexpected opts field: `%s`", name)
|
||||
}
|
||||
|
||||
if err := t.Cmp(value); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "expected different type for opts field: `%s`", name)
|
||||
}
|
||||
}
|
||||
|
||||
typ = tOpts // solved
|
||||
}
|
||||
}
|
||||
|
||||
if typ == nil {
|
||||
return nil, fmt.Errorf("not enough type information")
|
||||
}
|
||||
|
||||
typFunc := types.NewType(fmt.Sprintf("func(namespace str, opts %s) []str", typ.String()))
|
||||
|
||||
// TODO: type check that the partialValues are compatible
|
||||
|
||||
return []*types.Type{typFunc}, nil // solved!
|
||||
}
|
||||
|
||||
// Build is run to turn the polymorphic, undetermined function, into the
|
||||
// specific statically typed version. It is usually run after Unify completes,
|
||||
// and must be run before Info() and any of the other Func interface methods are
|
||||
// used. This function is idempotent, as long as the arg isn't changed between
|
||||
// runs.
|
||||
func (obj *ScheduleFunc) Build(typ *types.Type) (*types.Type, error) {
|
||||
// typ is the KindFunc signature we're trying to build...
|
||||
if typ.Kind != types.KindFunc {
|
||||
return nil, fmt.Errorf("input type must be of kind func")
|
||||
}
|
||||
|
||||
if len(typ.Ord) != 1 && len(typ.Ord) != 2 {
|
||||
return nil, fmt.Errorf("the schedule function needs either one or two args")
|
||||
}
|
||||
if typ.Out == nil {
|
||||
return nil, fmt.Errorf("return type of function must be specified")
|
||||
}
|
||||
if typ.Map == nil {
|
||||
return nil, fmt.Errorf("invalid input type")
|
||||
}
|
||||
|
||||
if err := typ.Out.Cmp(types.NewType("[]str")); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "return type must be a list of strings")
|
||||
}
|
||||
|
||||
tNamespace, exists := typ.Map[typ.Ord[0]]
|
||||
if !exists || tNamespace == nil {
|
||||
return nil, fmt.Errorf("first arg must be specified")
|
||||
}
|
||||
|
||||
if len(typ.Ord) == 1 {
|
||||
obj.Type = nil
|
||||
obj.built = true
|
||||
return obj.sig(), nil // done early, 2nd arg is absent!
|
||||
}
|
||||
tOpts, exists := typ.Map[typ.Ord[1]]
|
||||
if !exists || tOpts == nil {
|
||||
return nil, fmt.Errorf("second argument was missing")
|
||||
}
|
||||
|
||||
if tOpts.Kind != types.KindStruct {
|
||||
return nil, fmt.Errorf("second argument must be of kind struct")
|
||||
}
|
||||
|
||||
validOpts := obj.validOpts()
|
||||
|
||||
if StrictScheduleOpts {
|
||||
// strict opts field checking!
|
||||
for _, name := range tOpts.Ord {
|
||||
t := tOpts.Map[name]
|
||||
value, exists := validOpts[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("unexpected opts field: `%s`", name)
|
||||
}
|
||||
|
||||
if err := t.Cmp(value); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "expected different type for opts field: `%s`", name)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// permissive field checking...
|
||||
validOptsSorted := []string{}
|
||||
for name := range validOpts {
|
||||
validOptsSorted = append(validOptsSorted, name)
|
||||
}
|
||||
sort.Strings(validOptsSorted)
|
||||
for _, name := range validOptsSorted {
|
||||
value := validOpts[name] // type
|
||||
|
||||
t, exists := tOpts.Map[name]
|
||||
if !exists {
|
||||
continue // ignore it
|
||||
}
|
||||
|
||||
// if it exists, check the type
|
||||
if err := t.Cmp(value); err != nil {
|
||||
return nil, errwrap.Wrapf(err, "expected different type for opts field: `%s`", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
obj.Type = tOpts // type of opts struct, even an empty: `struct{}`
|
||||
obj.built = true
|
||||
return obj.sig(), nil
|
||||
}
|
||||
|
||||
// Validate tells us if the input struct takes a valid form.
|
||||
func (obj *ScheduleFunc) Validate() error {
|
||||
if !obj.built {
|
||||
return fmt.Errorf("function wasn't built yet")
|
||||
}
|
||||
// obj.Type can be nil if no 2nd arg is given, or a struct (even empty!)
|
||||
if obj.Type != nil && obj.Type.Kind != types.KindStruct { // build must be run first
|
||||
return fmt.Errorf("type must be nil or a struct")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Info returns some static info about itself. Build must be called before this
|
||||
// will return correct data.
|
||||
func (obj *ScheduleFunc) Info() *interfaces.Info {
|
||||
// It's important that you don't return a non-nil sig if this is called
|
||||
// before you're built. Type unification may call it opportunistically.
|
||||
var sig *types.Type
|
||||
if obj.built {
|
||||
sig = obj.sig() // helper
|
||||
}
|
||||
return &interfaces.Info{
|
||||
Pure: false, // definitely false
|
||||
Memo: false,
|
||||
// output is list of hostnames chosen
|
||||
Sig: sig, // func kind
|
||||
Err: obj.Validate(),
|
||||
}
|
||||
}
|
||||
|
||||
// helper
|
||||
func (obj *ScheduleFunc) sig() *types.Type {
|
||||
sig := types.NewType(fmt.Sprintf("func(%s str) []str", scheduleArgNameNamespace)) // simplest form
|
||||
if obj.Type != nil {
|
||||
sig = types.NewType(fmt.Sprintf("func(%s str, %s %s) []str", scheduleArgNameNamespace, scheduleArgNameOpts, obj.Type.String()))
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
// Init runs some startup code for this function.
|
||||
func (obj *ScheduleFunc) Init(init *interfaces.Init) error {
|
||||
obj.init = init
|
||||
obj.watchChan = make(chan *schedulerResult)
|
||||
//obj.init.Debug = true // use this for local debugging
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream returns the changing values that this func has over time.
|
||||
func (obj *ScheduleFunc) Stream(ctx context.Context) error {
|
||||
defer close(obj.init.Output) // the sender closes
|
||||
for {
|
||||
select {
|
||||
// TODO: should this first chan be run as a priority channel to
|
||||
// avoid some sort of glitch? is that even possible? can our
|
||||
// hostname check with reality (below) fix that?
|
||||
case input, ok := <-obj.init.Input:
|
||||
if !ok {
|
||||
obj.init.Input = nil // don't infinite loop back
|
||||
continue // no more inputs, but don't return!
|
||||
}
|
||||
//if err := input.Type().Cmp(obj.Info().Sig.Input); err != nil {
|
||||
// return errwrap.Wrapf(err, "wrong function input")
|
||||
//}
|
||||
|
||||
if obj.last != nil && input.Cmp(obj.last) == nil {
|
||||
continue // value didn't change, skip it
|
||||
}
|
||||
obj.last = input // store for next
|
||||
|
||||
namespace := input.Struct()[scheduleArgNameNamespace].Str()
|
||||
if namespace == "" {
|
||||
return fmt.Errorf("can't use an empty namespace")
|
||||
}
|
||||
|
||||
opts := make(map[string]types.Value) // empty "struct"
|
||||
if val, exists := input.Struct()[scheduleArgNameOpts]; exists {
|
||||
opts = val.Struct()
|
||||
}
|
||||
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("namespace: %s", namespace)
|
||||
}
|
||||
|
||||
schedulerOpts := []scheduler.Option{}
|
||||
// don't add bad or zero-value options
|
||||
|
||||
defaultStrategy := true
|
||||
if val, exists := opts["strategy"]; exists {
|
||||
if strategy := val.Str(); strategy != "" {
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("opts: strategy: %s", strategy)
|
||||
}
|
||||
defaultStrategy = false
|
||||
schedulerOpts = append(schedulerOpts, scheduler.StrategyKind(strategy))
|
||||
}
|
||||
}
|
||||
if defaultStrategy { // we always need to add one!
|
||||
schedulerOpts = append(schedulerOpts, scheduler.StrategyKind(DefaultStrategy))
|
||||
}
|
||||
if val, exists := opts["max"]; exists {
|
||||
// TODO: check for overflow
|
||||
if max := int(val.Int()); max > 0 {
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("opts: max: %d", max)
|
||||
}
|
||||
schedulerOpts = append(schedulerOpts, scheduler.MaxCount(max))
|
||||
}
|
||||
}
|
||||
if val, exists := opts["reuse"]; exists {
|
||||
reuse := val.Bool()
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("opts: reuse: %t", reuse)
|
||||
}
|
||||
schedulerOpts = append(schedulerOpts, scheduler.ReuseLease(reuse))
|
||||
}
|
||||
if val, exists := opts["ttl"]; exists {
|
||||
// TODO: check for overflow
|
||||
if ttl := int(val.Int()); ttl > 0 {
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("opts: ttl: %d", ttl)
|
||||
}
|
||||
schedulerOpts = append(schedulerOpts, scheduler.SessionTTL(ttl))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: support changing the namespace over time...
|
||||
// TODO: possibly removing our stored value there first!
|
||||
if obj.namespace == "" {
|
||||
obj.namespace = namespace // store it
|
||||
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("starting scheduler...")
|
||||
}
|
||||
var err error
|
||||
obj.scheduler, err = obj.init.World.Scheduler(obj.namespace, schedulerOpts...)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "can't create scheduler")
|
||||
}
|
||||
|
||||
// process the stream of scheduling output...
|
||||
go func() {
|
||||
defer close(obj.watchChan)
|
||||
// XXX: maybe we could share the parent
|
||||
// ctx, but I have to work out the
|
||||
// ordering logic first. For now this is
|
||||
// just a port of what it was before.
|
||||
newCtx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
defer cancel() // unblock Next()
|
||||
defer obj.scheduler.Shutdown()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}()
|
||||
for {
|
||||
hosts, err := obj.scheduler.Next(newCtx)
|
||||
select {
|
||||
case obj.watchChan <- &schedulerResult{
|
||||
hosts: hosts,
|
||||
err: err,
|
||||
}:
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
} else if obj.namespace != namespace {
|
||||
return fmt.Errorf("can't change namespace, previously: `%s`", obj.namespace)
|
||||
}
|
||||
|
||||
continue // we send values on the watch chan, not here!
|
||||
|
||||
case schedulerResult, ok := <-obj.watchChan:
|
||||
if !ok { // closed
|
||||
// XXX: maybe etcd reconnected? (fix etcd implementation)
|
||||
|
||||
// XXX: if we close, perhaps the engine is
|
||||
// switching etcd hosts and we should retry?
|
||||
// maybe instead we should get an "etcd
|
||||
// reconnect" signal, and the lang will restart?
|
||||
return nil
|
||||
}
|
||||
if err := schedulerResult.err; err != nil {
|
||||
if err == scheduler.ErrEndOfResults {
|
||||
//return nil // TODO: we should probably fix the reconnect issue and use this here
|
||||
return fmt.Errorf("scheduler shutdown, reconnect bug?") // XXX: fix etcd reconnects
|
||||
}
|
||||
return errwrap.Wrapf(err, "channel watch failed on `%s`", obj.namespace)
|
||||
}
|
||||
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("got hosts: %+v", schedulerResult.hosts)
|
||||
}
|
||||
|
||||
var result types.Value
|
||||
l := types.NewList(obj.Info().Sig.Out)
|
||||
for _, val := range schedulerResult.hosts {
|
||||
if err := l.Add(&types.StrValue{V: val}); err != nil {
|
||||
return errwrap.Wrapf(err, "list could not add val: `%s`", val)
|
||||
}
|
||||
}
|
||||
result = l // set list as result
|
||||
|
||||
if obj.init.Debug {
|
||||
obj.init.Logf("result: %+v", 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
|
||||
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case obj.init.Output <- obj.result: // send
|
||||
// pass
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// schedulerResult combines our internal events into a single message packet.
|
||||
type schedulerResult struct {
|
||||
hosts []string
|
||||
err error
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ James Shubin and the project contributors
|
||||
// Written by James Shubin <james@shubin.ca> and the project contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package coreworld
|
||||
|
||||
const (
|
||||
// ModuleName is the prefix given to all the functions in this module.
|
||||
ModuleName = "world"
|
||||
)
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
pkg = flag.String("package", "lang/funcs/core", "path to the package")
|
||||
pkg = flag.String("package", "lang/core", "path to the package")
|
||||
filename = flag.String("filename", "funcgen.yaml", "path to the config")
|
||||
templates = flag.String("templates", "lang/funcs/funcgen/templates/*.tpl", "path to the templates")
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user