v0.31.0 refinements
This commit is contained in:
1 parent
bfcc15613a
commit
6bd5e12a60
52 files changed
+1948
-683
No files matched your search
@@ -181,13 +181,34 @@ func (c *Composition) pipeline(i int) (*config.PipelineConfig, error) {
|
||||
|
||||
// AddPipeline appends an empty pipeline under a name no other has
|
||||
func (c *Composition) AddPipeline(name string) error {
|
||||
if name == "" || slices.ContainsFunc(c.Pipelines, func(p config.PipelineConfig) bool { return p.Name == name }) {
|
||||
return fmt.Errorf("pipeline %q: a name no other pipeline has", name)
|
||||
if err := c.free(name, -1); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Pipelines = append(c.Pipelines, config.PipelineConfig{Name: name, Flow: &config.FlowConfig{}})
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenamePipeline gives pipeline i a name no other has
|
||||
func (c *Composition) RenamePipeline(i int, name string) error {
|
||||
p, err := c.pipeline(i)
|
||||
if err == nil {
|
||||
err = c.free(name, i)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Name = name
|
||||
return nil
|
||||
}
|
||||
|
||||
// free refuses a name that is empty or held by a pipeline other than i
|
||||
func (c *Composition) free(name string, i int) error {
|
||||
if j := slices.IndexFunc(c.Pipelines, func(p config.PipelineConfig) bool { return p.Name == name }); name == "" || j >= 0 && j != i {
|
||||
return fmt.Errorf("pipeline %q: a name no other pipeline has", name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Composition) RemovePipeline(i int) error {
|
||||
if _, err := c.pipeline(i); err != nil {
|
||||
return err
|
||||
@@ -233,6 +254,52 @@ func (c *Composition) Add(pi int, role, typ string) (Node, error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Retype gives a source or sink another type of its role, in its place: a
|
||||
// chosen id stays and an automatic one follows the type. Options of the same
|
||||
// name and kind carry over to a type on the same network side, where they
|
||||
// mean the same; across sides host, port, tls and auth do not.
|
||||
func (c *Composition) Retype(pi int, n Node, typ string) (Node, error) {
|
||||
p, err := c.pipeline(pi)
|
||||
if err != nil {
|
||||
return Node{}, err
|
||||
}
|
||||
to, ok := config.LookupPlugin(n.Role, typ)
|
||||
if !ok {
|
||||
return Node{}, fmt.Errorf("unknown %s type %q", n.Role, typ)
|
||||
}
|
||||
var id, old *string
|
||||
var opts *map[string]any
|
||||
var ids []string
|
||||
switch i := index(p, n); {
|
||||
case i >= 0 && n.Role == "source":
|
||||
id, old, opts = &p.PluginSources[i].ID, &p.PluginSources[i].Type, &p.PluginSources[i].Config
|
||||
for _, s := range p.PluginSources {
|
||||
ids = append(ids, s.ID)
|
||||
}
|
||||
case i >= 0 && n.Role == "sink":
|
||||
id, old, opts = &p.PluginSinks[i].ID, &p.PluginSinks[i].Type, &p.PluginSinks[i].Config
|
||||
for _, s := range p.PluginSinks {
|
||||
ids = append(ids, s.ID)
|
||||
}
|
||||
default:
|
||||
return Node{}, n.missing()
|
||||
}
|
||||
from, _ := config.LookupPlugin(n.Role, *old)
|
||||
kept := map[string]any{}
|
||||
for _, k := range from.Keys() {
|
||||
same := func(t config.Key) bool { return t.Name == k.Name && t.Kind == k.Kind }
|
||||
if v, set := (*opts)[k.Name]; set && from.Side == to.Side && slices.ContainsFunc(to.Keys(), same) {
|
||||
kept[k.Name] = v
|
||||
}
|
||||
}
|
||||
suffix, auto := strings.CutPrefix(*id, *old)
|
||||
if n, err := strconv.Atoi(strings.TrimPrefix(suffix, "_")); auto && (suffix == "" || suffix[0] == '_' && err == nil && n > 1) {
|
||||
*id = config.FreeID(typ, slices.DeleteFunc(ids, func(s string) bool { return s == *id }))
|
||||
}
|
||||
*old, *opts = typ, kept
|
||||
return Node{Role: n.Role, ID: *id}, nil
|
||||
}
|
||||
|
||||
// Remove deletes a source, sink or filter, or turns a flow stage off
|
||||
func (c *Composition) Remove(pi int, n Node) error {
|
||||
p, err := c.pipeline(pi)
|
||||
@@ -353,22 +420,22 @@ func (c *Composition) edit(pi int, n Node, change func(map[string]any) error) er
|
||||
return plugin(p.PluginSinks[i].Type, &p.PluginSinks[i].Config)
|
||||
case n.Role == "filters":
|
||||
filter := &p.Flow.Filters[i]
|
||||
if err := editStage(reflect.ValueOf(&filter).Elem(), change); err != nil {
|
||||
if err := editStage(reflect.ValueOf(&filter).Elem(), "", change); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Flow.Filters[i] = *filter
|
||||
return nil
|
||||
}
|
||||
if stage, ok := stages(p.Flow)[n.Role]; ok {
|
||||
return editStage(stage, change)
|
||||
return editStage(stage, n.Role, change)
|
||||
}
|
||||
return n.missing()
|
||||
}
|
||||
|
||||
// editStage edits a stage's keys as a spec sets them; the table they scan into
|
||||
// replaces the stage, which turns on
|
||||
func editStage(stage reflect.Value, change func(map[string]any) error) error {
|
||||
m := map[string]any{}
|
||||
// replaces the stage, which turns on as a spec turns it on
|
||||
func editStage(stage reflect.Value, key string, change func(map[string]any) error) error {
|
||||
m := config.StageOn(key, nil)
|
||||
if !stage.IsNil() {
|
||||
m = config.Values(stage.Interface())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
@@ -64,7 +65,7 @@ func TestWrittenFormsStartTheSameComposition(t *testing.T) {
|
||||
_, err = c.Add(0, "filters", "include")
|
||||
must(err)
|
||||
must(c.MoveFilter(0, 1, 0))
|
||||
must(c.Set(0, Node{Role: "rate_limit"}, "rate", "100"))
|
||||
must(c.Set(0, Node{Role: "rate_limit"}, "entries_per_second", "100"))
|
||||
must(c.Set(0, Node{Role: "heartbeat"}, "interval_ms", "5000"))
|
||||
must(c.Set(0, Node{Role: "heartbeat"}, "enabled", "true"))
|
||||
must(c.AddPipeline("-b"))
|
||||
@@ -190,3 +191,71 @@ func TestUnnamedPartsTakeFreeIDs(t *testing.T) {
|
||||
t.Fatalf("%v\n%s", err, line)
|
||||
}
|
||||
}
|
||||
|
||||
// Retype keeps the part's place and a chosen id, renames an automatic one,
|
||||
// and carries the options of the same name and kind only within a side
|
||||
func TestRetypeKeepsThePlace(t *testing.T) {
|
||||
c := &Composition{}
|
||||
if err := c.AddPipeline("p"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, typ := range []string{"tcp", "http", "tcp"} {
|
||||
if _, err := c.Add(0, "sink", typ); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
tcp2, chosen := Node{Role: "sink", ID: "tcp_2"}, Node{Role: "sink", ID: "web"}
|
||||
c.Pipelines[0].PluginSinks[1].ID = chosen.ID
|
||||
for _, err := range []error{c.Set(0, tcp2, "port", "9000"), c.Set(0, tcp2, "keep_alive", "false"), c.Set(0, chosen, "port", "80")} {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
got, err := c.Retype(0, tcp2, "http")
|
||||
if err != nil || got.ID != "http" {
|
||||
t.Fatalf("%+v %v", got, err)
|
||||
}
|
||||
if _, err := c.Retype(0, chosen, "tcp_chain"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var parts []string
|
||||
for _, s := range c.Pipelines[0].PluginSinks {
|
||||
parts = append(parts, fmt.Sprintf("%s=%s %v", s.ID, s.Type, s.Config))
|
||||
}
|
||||
if want := []string{"tcp=tcp map[]", "web=tcp_chain map[]", "http=http map[port:9000]"}; !slices.Equal(parts, want) {
|
||||
t.Fatalf("%q, want %q", parts, want)
|
||||
}
|
||||
}
|
||||
|
||||
// A pipeline takes a name no other has, renamed as when added
|
||||
func TestPipelineNamesAreUnique(t *testing.T) {
|
||||
c := &Composition{}
|
||||
for _, name := range []string{"a", "b"} {
|
||||
if err := c.AddPipeline(name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if c.AddPipeline("a") == nil || c.RenamePipeline(1, "a") == nil || c.RenamePipeline(1, "") == nil {
|
||||
t.Fatal("took a name another pipeline has")
|
||||
}
|
||||
if err := c.RenamePipeline(1, "b"); err != nil || c.RenamePipeline(1, "c") != nil || c.Pipelines[1].Name != "c" {
|
||||
t.Fatalf("%v, %+v", err, c.Pipelines)
|
||||
}
|
||||
}
|
||||
|
||||
// A stage an edit turns on does what its spec would: a rate limit drops what
|
||||
// passes its rate, a heartbeat beats
|
||||
func TestAStageTurnsOnAsItsSpecDoes(t *testing.T) {
|
||||
c := &Composition{}
|
||||
if err := c.AddPipeline("p"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, err := range []error{c.Set(0, Node{Role: "rate_limit"}, "entries_per_second", "5"), c.Set(0, Node{Role: "heartbeat"}, "interval_ms", "500")} {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if f := c.Pipelines[0].Flow; f.RateLimit.Policy != "drop" || !f.Heartbeat.Enabled {
|
||||
t.Fatalf("%+v %+v", f.RateLimit, f.Heartbeat)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user