mirror of
https://github.com/alibaba/higress.git
synced 2026-05-11 22:37:32 +08:00
feat: Add traffic-editor plugin (#2825)
This commit is contained in:
515
plugins/wasm-go/extensions/traffic-editor/pkg/command.go
Normal file
515
plugins/wasm-go/extensions/traffic-editor/pkg/command.go
Normal file
@@ -0,0 +1,515 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/higress-group/wasm-go/pkg/log"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
const (
|
||||
commandTypeSet = "set"
|
||||
commandTypeConcat = "concat"
|
||||
commandTypeCopy = "copy"
|
||||
commandTypeDelete = "delete"
|
||||
commandTypeRename = "rename"
|
||||
)
|
||||
|
||||
var (
|
||||
commandFactories = map[string]func(gjson.Result) (Command, error){
|
||||
"set": newSetCommand,
|
||||
"concat": newConcatCommand,
|
||||
"copy": newCopyCommand,
|
||||
"delete": newDeleteCommand,
|
||||
"rename": newRenameCommand,
|
||||
}
|
||||
)
|
||||
|
||||
type CommandSet struct {
|
||||
DisableReroute bool `json:"disableReroute"`
|
||||
Commands []Command `json:"commands,omitempty"`
|
||||
RelatedStages map[Stage]bool `json:"-"`
|
||||
}
|
||||
|
||||
func (s *CommandSet) FromJson(json gjson.Result) error {
|
||||
relatedStages := map[Stage]bool{}
|
||||
if commandsJson := json.Get("commands"); commandsJson.Exists() && commandsJson.IsArray() {
|
||||
for _, item := range commandsJson.Array() {
|
||||
if command, err := NewCommand(item); err != nil {
|
||||
return fmt.Errorf("failed to create command from json: %v\n %v", err, item)
|
||||
} else {
|
||||
s.Commands = append(s.Commands, command)
|
||||
for _, ref := range command.GetRefs() {
|
||||
relatedStages[ref.GetStage()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s.RelatedStages = relatedStages
|
||||
if disableReroute := json.Get("disableReroute"); disableReroute.Exists() {
|
||||
s.DisableReroute = disableReroute.Bool()
|
||||
} else {
|
||||
s.DisableReroute = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CommandSet) CreatExecutors() []Executor {
|
||||
executors := make([]Executor, 0, len(s.Commands))
|
||||
for _, command := range s.Commands {
|
||||
executor := command.CreateExecutor()
|
||||
executors = append(executors, executor)
|
||||
}
|
||||
return executors
|
||||
}
|
||||
|
||||
type ConditionalCommandSet struct {
|
||||
ConditionSet
|
||||
CommandSet
|
||||
}
|
||||
|
||||
func (s *ConditionalCommandSet) FromJson(json gjson.Result) error {
|
||||
if err := s.ConditionSet.FromJson(json); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.CommandSet.FromJson(json); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Command interface {
|
||||
GetType() string
|
||||
GetRefs() []*Ref
|
||||
CreateExecutor() Executor
|
||||
}
|
||||
|
||||
type Executor interface {
|
||||
GetCommand() Command
|
||||
Run(editorContext EditorContext, stage Stage) error
|
||||
}
|
||||
|
||||
func NewCommand(json gjson.Result) (Command, error) {
|
||||
t := json.Get("type").String()
|
||||
if t == "" {
|
||||
return nil, errors.New("command type is required")
|
||||
}
|
||||
if constructor, ok := commandFactories[t]; ok && constructor != nil {
|
||||
return constructor(json)
|
||||
} else {
|
||||
return nil, errors.New("unknown command type: " + t)
|
||||
}
|
||||
}
|
||||
|
||||
type baseExecutor struct {
|
||||
finished bool
|
||||
}
|
||||
|
||||
// setCommand
|
||||
func newSetCommand(json gjson.Result) (Command, error) {
|
||||
var targetRef *Ref
|
||||
var err error
|
||||
if t := json.Get("target"); !t.Exists() {
|
||||
return nil, errors.New("setCommand: target field is required")
|
||||
} else {
|
||||
targetRef, err = NewRef(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setCommand: failed to create ref from target field: %v\n %v", err, t.Raw)
|
||||
}
|
||||
}
|
||||
var value string
|
||||
if v := json.Get("value"); !v.Exists() {
|
||||
return nil, errors.New("setCommand: value field is required")
|
||||
} else {
|
||||
value = v.String()
|
||||
if value == "" {
|
||||
return nil, errors.New("setCommand: value cannot be empty")
|
||||
}
|
||||
}
|
||||
return &setCommand{
|
||||
targetRef: targetRef,
|
||||
value: value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type setCommand struct {
|
||||
targetRef *Ref
|
||||
value string
|
||||
}
|
||||
|
||||
func (c *setCommand) GetType() string {
|
||||
return commandTypeSet
|
||||
}
|
||||
|
||||
func (c *setCommand) GetRefs() []*Ref {
|
||||
return []*Ref{c.targetRef}
|
||||
}
|
||||
|
||||
func (c *setCommand) CreateExecutor() Executor {
|
||||
return &setExecutor{command: c}
|
||||
}
|
||||
|
||||
type setExecutor struct {
|
||||
baseExecutor
|
||||
command *setCommand
|
||||
}
|
||||
|
||||
func (e *setExecutor) GetCommand() Command {
|
||||
return e.command
|
||||
}
|
||||
|
||||
func (e *setExecutor) Run(editorContext EditorContext, stage Stage) error {
|
||||
if e.finished {
|
||||
return nil
|
||||
}
|
||||
|
||||
command := e.command
|
||||
log.Debugf("setCommand: checking stage %s for target %s", Stage2String[stage], command.targetRef)
|
||||
if command.targetRef.GetStage() == stage {
|
||||
log.Debugf("setCommand: set %s to %s", command.targetRef, command.value)
|
||||
editorContext.SetRefValue(command.targetRef, command.value)
|
||||
e.finished = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// concatCommand
|
||||
func newConcatCommand(json gjson.Result) (Command, error) {
|
||||
var targetRef *Ref
|
||||
var err error
|
||||
if t := json.Get("target"); !t.Exists() {
|
||||
return nil, errors.New("concatCommand: target field is required")
|
||||
} else {
|
||||
targetRef, err = NewRef(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("concatCommand: failed to create ref from target field: %v\n %v", err, t.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
valuesJson := json.Get("values")
|
||||
if !valuesJson.Exists() || !valuesJson.IsArray() {
|
||||
return nil, errors.New("concatCommand: values field is required and must be an array")
|
||||
}
|
||||
|
||||
values := make([]interface{}, 0, len(valuesJson.Array()))
|
||||
for _, item := range valuesJson.Array() {
|
||||
var value interface{}
|
||||
if item.IsObject() {
|
||||
valueRef, err := NewRef(item)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("concatCommand: failed to create ref from values field: %v\n %v", err, item.Raw)
|
||||
}
|
||||
if valueRef.GetStage() > targetRef.GetStage() {
|
||||
return nil, fmt.Errorf("concatCommand: the processing stage of value [%s] cannot be after the stage of target [%s]", Stage2String[valueRef.GetStage()], Stage2String[targetRef.GetStage()])
|
||||
}
|
||||
value = valueRef
|
||||
} else {
|
||||
value = item.String()
|
||||
}
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
return &concatCommand{
|
||||
targetRef: targetRef,
|
||||
values: values,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type concatCommand struct {
|
||||
targetRef *Ref
|
||||
values []interface{}
|
||||
}
|
||||
|
||||
func (c *concatCommand) GetType() string {
|
||||
return commandTypeConcat
|
||||
}
|
||||
|
||||
func (c *concatCommand) GetRefs() []*Ref {
|
||||
refs := []*Ref{c.targetRef}
|
||||
if c.values != nil && len(c.values) != 0 {
|
||||
for _, value := range c.values {
|
||||
if ref, ok := value.(*Ref); ok {
|
||||
refs = append(refs, ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
func (c *concatCommand) CreateExecutor() Executor {
|
||||
return &concatExecutor{command: c}
|
||||
}
|
||||
|
||||
type concatExecutor struct {
|
||||
baseExecutor
|
||||
command *concatCommand
|
||||
values []string
|
||||
}
|
||||
|
||||
func (e *concatExecutor) GetCommand() Command {
|
||||
return e.command
|
||||
}
|
||||
|
||||
func (e *concatExecutor) Run(editorContext EditorContext, stage Stage) error {
|
||||
if e.finished {
|
||||
return nil
|
||||
}
|
||||
|
||||
command := e.command
|
||||
|
||||
if e.values == nil {
|
||||
e.values = make([]string, len(command.values))
|
||||
}
|
||||
|
||||
for i, value := range command.values {
|
||||
if value == nil || e.values[i] != "" {
|
||||
continue
|
||||
}
|
||||
v := ""
|
||||
if s, ok := value.(string); ok {
|
||||
v = s
|
||||
} else if ref, ok := value.(*Ref); ok && ref.GetStage() == stage {
|
||||
v = editorContext.GetRefValue(ref)
|
||||
}
|
||||
e.values[i] = v
|
||||
}
|
||||
|
||||
if command.targetRef.GetStage() == stage {
|
||||
result := ""
|
||||
for _, v := range e.values {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
result += v
|
||||
}
|
||||
log.Debugf("concatCommand: set %s to %s", command.targetRef, result)
|
||||
editorContext.SetRefValue(command.targetRef, result)
|
||||
e.finished = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyCommand
|
||||
func newCopyCommand(json gjson.Result) (Command, error) {
|
||||
var sourceRef *Ref
|
||||
var targetRef *Ref
|
||||
var err error
|
||||
if t := json.Get("source"); !t.Exists() {
|
||||
return nil, errors.New("copyCommand: source field is required")
|
||||
} else {
|
||||
sourceRef, err = NewRef(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("copyCommand: failed to create ref from source field: %v\n %v", err, t.Raw)
|
||||
}
|
||||
}
|
||||
if t := json.Get("target"); !t.Exists() {
|
||||
return nil, errors.New("copyCommand: target field is required")
|
||||
} else {
|
||||
targetRef, err = NewRef(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("copyCommand: failed to create ref from target field: %v\n %v", err, t.Raw)
|
||||
}
|
||||
}
|
||||
if sourceRef.GetStage() > targetRef.GetStage() {
|
||||
return nil, fmt.Errorf("copyCommand: the processing stage of source [%s] cannot be after the stage of target [%s]", Stage2String[sourceRef.GetStage()], Stage2String[targetRef.GetStage()])
|
||||
}
|
||||
return ©Command{
|
||||
sourceRef: sourceRef,
|
||||
targetRef: targetRef,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type copyCommand struct {
|
||||
sourceRef *Ref
|
||||
targetRef *Ref
|
||||
}
|
||||
|
||||
func (c *copyCommand) GetType() string {
|
||||
return commandTypeCopy
|
||||
}
|
||||
|
||||
func (c *copyCommand) GetRefs() []*Ref {
|
||||
return []*Ref{c.sourceRef, c.targetRef}
|
||||
}
|
||||
|
||||
func (c *copyCommand) CreateExecutor() Executor {
|
||||
return ©Executor{command: c}
|
||||
}
|
||||
|
||||
type copyExecutor struct {
|
||||
baseExecutor
|
||||
command *copyCommand
|
||||
valueToCopy string
|
||||
}
|
||||
|
||||
func (e *copyExecutor) GetCommand() Command {
|
||||
return e.command
|
||||
}
|
||||
|
||||
func (e *copyExecutor) Run(editorContext EditorContext, stage Stage) error {
|
||||
if e.finished {
|
||||
return nil
|
||||
}
|
||||
|
||||
command := e.command
|
||||
|
||||
if command.sourceRef.GetStage() == stage {
|
||||
e.valueToCopy = editorContext.GetRefValue(command.sourceRef)
|
||||
log.Debugf("copyCommand: valueToCopy=%s", e.valueToCopy)
|
||||
}
|
||||
|
||||
if e.valueToCopy == "" {
|
||||
log.Debug("copyCommand: valueToCopy is empty. skip.")
|
||||
e.finished = true
|
||||
return nil
|
||||
}
|
||||
|
||||
if command.targetRef.GetStage() == stage {
|
||||
editorContext.SetRefValue(command.targetRef, e.valueToCopy)
|
||||
log.Debugf("copyCommand: set %s to %s", e.valueToCopy, command.targetRef)
|
||||
e.finished = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteCommand
|
||||
func newDeleteCommand(json gjson.Result) (Command, error) {
|
||||
var targetRef *Ref
|
||||
var err error
|
||||
if t := json.Get("target"); !t.Exists() {
|
||||
return nil, errors.New("deleteCommand: target field is required")
|
||||
} else {
|
||||
targetRef, err = NewRef(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("deleteCommand: failed to create ref from target field: %v\n %v", err, t.Raw)
|
||||
}
|
||||
}
|
||||
return &deleteCommand{
|
||||
targetRef: targetRef,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type deleteCommand struct {
|
||||
targetRef *Ref
|
||||
}
|
||||
|
||||
func (c *deleteCommand) GetType() string {
|
||||
return commandTypeDelete
|
||||
}
|
||||
|
||||
func (c *deleteCommand) GetRefs() []*Ref {
|
||||
return []*Ref{c.targetRef}
|
||||
}
|
||||
|
||||
func (c *deleteCommand) CreateExecutor() Executor {
|
||||
return &deleteExecutor{command: c}
|
||||
}
|
||||
|
||||
type deleteExecutor struct {
|
||||
baseExecutor
|
||||
command *deleteCommand
|
||||
}
|
||||
|
||||
func (e *deleteExecutor) GetCommand() Command {
|
||||
return e.command
|
||||
}
|
||||
|
||||
func (e *deleteExecutor) Run(editorContext EditorContext, stage Stage) error {
|
||||
if e.finished {
|
||||
return nil
|
||||
}
|
||||
|
||||
command := e.command
|
||||
log.Debugf("deleteCommand: checking stage %s for target %s", Stage2String[stage], command.targetRef)
|
||||
|
||||
if command.targetRef.GetStage() == stage {
|
||||
log.Debugf("deleteCommand: delete %s", command.targetRef)
|
||||
editorContext.DeleteRefValues(command.targetRef)
|
||||
e.finished = true
|
||||
log.Debugf("deleteCommand: finished deleting %s", command.targetRef)
|
||||
} else {
|
||||
log.Debugf("deleteCommand: stage %s does not match targetRef stage %s, skipping.", Stage2String[stage], Stage2String[command.targetRef.GetStage()])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// renameCommand
|
||||
func newRenameCommand(json gjson.Result) (Command, error) {
|
||||
var targetRef *Ref
|
||||
var err error
|
||||
if t := json.Get("target"); !t.Exists() {
|
||||
return nil, errors.New("renameCommand: target field is required")
|
||||
} else {
|
||||
targetRef, err = NewRef(t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("renameCommand: failed to create ref from target field: %v\n %v", err, t.Raw)
|
||||
}
|
||||
}
|
||||
newName := json.Get("newName").String()
|
||||
if newName == "" {
|
||||
return nil, errors.New("renameCommand: newName field is required")
|
||||
}
|
||||
return &renameCommand{
|
||||
targetRef: targetRef,
|
||||
newName: newName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type renameCommand struct {
|
||||
targetRef *Ref
|
||||
newName string
|
||||
}
|
||||
|
||||
func (c *renameCommand) GetType() string {
|
||||
return commandTypeRename
|
||||
}
|
||||
|
||||
func (c *renameCommand) GetRefs() []*Ref {
|
||||
return []*Ref{c.targetRef}
|
||||
}
|
||||
|
||||
func (c *renameCommand) CreateExecutor() Executor {
|
||||
return &renameExecutor{command: c}
|
||||
}
|
||||
|
||||
type renameExecutor struct {
|
||||
baseExecutor
|
||||
command *renameCommand
|
||||
}
|
||||
|
||||
func (e *renameExecutor) GetCommand() Command {
|
||||
return e.command
|
||||
}
|
||||
|
||||
func (e *renameExecutor) Run(editorContext EditorContext, stage Stage) error {
|
||||
if e.finished {
|
||||
return nil
|
||||
}
|
||||
|
||||
command := e.command
|
||||
log.Debugf("renameCommand: checking stage %s for target %s", Stage2String[stage], command.targetRef)
|
||||
|
||||
if command.targetRef.GetStage() == stage {
|
||||
if command.newName == command.targetRef.Name {
|
||||
log.Debugf("renameCommand: skip renaming %s to itself", command.targetRef)
|
||||
} else {
|
||||
values := editorContext.GetRefValues(command.targetRef)
|
||||
log.Debugf("renameCommand: rename %s to %s value=%v", command.targetRef, command.newName, values)
|
||||
editorContext.SetRefValues(&Ref{
|
||||
Type: command.targetRef.Type,
|
||||
Name: command.newName,
|
||||
}, values)
|
||||
editorContext.DeleteRefValues(command.targetRef)
|
||||
log.Debugf("renameCommand: finished renaming %s to %s", command.targetRef, command.newName)
|
||||
}
|
||||
e.finished = true
|
||||
} else {
|
||||
log.Debugf("renameCommand: stage %s does not match targetRef stage %s, skipping.", Stage2String[stage], Stage2String[command.targetRef.GetStage()])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user