diff --git a/go.sum b/go.sum index 4620b68ba..543078c13 100644 --- a/go.sum +++ b/go.sum @@ -1181,17 +1181,7 @@ github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86w github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nmstate/nmpolicy v0.0.0-20211104121538-bce7aa71e17c h1:r3b7sxKeW1la+wMCfd4q8Z7eFFf+xMP+ZbW/oMmQTLg= -github.com/nmstate/nmpolicy v0.0.0-20211104121538-bce7aa71e17c/go.mod h1:uItjRVdUUTrtIUmG/772gMujJwMTHgUxPkUPjnpy6Is= -github.com/nmstate/nmpolicy v0.0.0-20211118114510-24193b65a736 h1:m+kXkBA6SZBHgmxf0LnIjtCQu4KEPU59KZcPJXhVY20= -github.com/nmstate/nmpolicy v0.0.0-20211118114510-24193b65a736/go.mod h1:uItjRVdUUTrtIUmG/772gMujJwMTHgUxPkUPjnpy6Is= -github.com/nmstate/nmpolicy v0.1.0-alpha.1 h1:ZZGKbrzxMFoO6kggmUwj/fqEaNYNN5HIUNtaZA9mSd4= -github.com/nmstate/nmpolicy v0.1.0-alpha.1/go.mod h1:uItjRVdUUTrtIUmG/772gMujJwMTHgUxPkUPjnpy6Is= -github.com/nmstate/nmpolicy v0.1.0-alpha.2 h1:U57+tmghM7aF9Z/OTwlSiMbwa+wQ0zqjGzHEUtS4dZQ= -github.com/nmstate/nmpolicy v0.1.0-alpha.2/go.mod h1:uItjRVdUUTrtIUmG/772gMujJwMTHgUxPkUPjnpy6Is= -github.com/nmstate/nmpolicy v0.1.0-alpha.3 h1:jBQX+KBCzQBYTrlYnC0kdNKQEIkV0eiFlWTkaHD6G1k= -github.com/nmstate/nmpolicy v0.1.0-alpha.3/go.mod h1:uItjRVdUUTrtIUmG/772gMujJwMTHgUxPkUPjnpy6Is= -github.com/nmstate/nmpolicy v0.1.0 h1:WTtyDuxVujAPLer7Fh7kaKPmYYoM2xemn+a0fRdScvw= +github.com/nmstate/nmpolicy v0.1.0 h1:A3Zx5X2SJMSmvVAFVksMhz4mLKDX7li45N7eWwMOUOY= github.com/nmstate/nmpolicy v0.1.0/go.mod h1:uItjRVdUUTrtIUmG/772gMujJwMTHgUxPkUPjnpy6Is= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE= github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw= diff --git a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/ast/types.go b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/ast/types.go index b4c82c499..a2c2ce154 100644 --- a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/ast/types.go +++ b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/ast/types.go @@ -31,6 +31,7 @@ type Terminal struct { type Node struct { Meta EqFilter *TernaryOperator `json:"eqfilter,omitempty"` + Replace *TernaryOperator `json:"replace,omitempty"` Path *VariadicOperator `json:"path,omitempty"` Terminal } diff --git a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/lexer/token.go b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/lexer/token.go index 8cd090b93..71addfba5 100644 --- a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/lexer/token.go +++ b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/lexer/token.go @@ -24,12 +24,14 @@ const ( NUMBER STRING - DOT // . - PIPE // | + DOT // . + operatorsBegin + PIPE // | REPLACE // := EQFILTER // == MERGE // + + operatorsEnd TRUE // true FALSE // false @@ -56,6 +58,10 @@ func (t TokenType) String() string { return tokens[t] } +func (t TokenType) IsOperator() bool { + return t > operatorsBegin && t < operatorsEnd +} + type Token struct { Position int Type TokenType diff --git a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/parser/errors.go b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/parser/errors.go index 02f5af635..19585c61b 100644 --- a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/parser/errors.go +++ b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/parser/errors.go @@ -65,9 +65,23 @@ func invalidEqualityFilterError(msg string) *parserError { } } +func invalidReplaceError(msg string) *parserError { + return &parserError{ + prefix: "invalid replace", + msg: msg, + } +} + func invalidExpressionError(msg string) *parserError { return &parserError{ prefix: "invalid expression", msg: msg, } } + +func invalidPipeError(msg string) *parserError { + return &parserError{ + prefix: "invalid pipe", + msg: msg, + } +} diff --git a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/parser/parser.go b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/parser/parser.go index db85f726a..a4e8c73ce 100644 --- a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/parser/parser.go +++ b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/parser/parser.go @@ -31,6 +31,7 @@ type parser struct { tokens []lexer.Token currentTokenIdx int lastNode *ast.Node + pipedInNode *ast.Node } func New() Parser { @@ -71,12 +72,30 @@ func (p *parser) parse() (ast.Node, error) { if err := p.parseEqFilter(); err != nil { return ast.Node{}, err } + } else if p.currentToken().Type == lexer.REPLACE { + if err := p.parseReplace(); err != nil { + return ast.Node{}, err + } + } else if p.currentToken().Type == lexer.PIPE { + if err := p.parsePipe(); err != nil { + return ast.Node{}, err + } } else { return ast.Node{}, invalidExpressionError(fmt.Sprintf("unexpected token `%+v`", p.currentToken().Literal)) } p.nextToken() } - return *p.lastNode, nil + if p.pipedInNode != nil { + return ast.Node{}, invalidPipeError("missing pipe out expression") + } + return p.lastEmitedNode(), nil +} + +func (p *parser) lastEmitedNode() ast.Node { + if p.lastNode == nil { + return ast.Node{} + } + return *p.lastNode } func (p *parser) nextToken() { @@ -103,6 +122,9 @@ func (p *parser) prevToken() { } func (p *parser) currentToken() *lexer.Token { + if len(p.tokens) == 0 || p.currentTokenIdx >= len(p.tokens) { + return nil + } return &p.tokens[p.currentTokenIdx] } @@ -159,7 +181,7 @@ func (p *parser) parsePath() error { } path := append(*operator.Path, *p.lastNode) operator.Path = &path - } else if p.currentToken().Type != lexer.EOF && p.currentToken().Type != lexer.EQFILTER { + } else if p.currentToken().Type != lexer.EOF && !p.currentToken().Type.IsOperator() { return invalidPathError("missing dot") } else { // Token has not being consumed let's go back. @@ -182,7 +204,9 @@ func (p *parser) parseEqFilter() error { if p.lastNode.Path == nil { return invalidEqualityFilterError("left hand argument is not a path") } - operator.EqFilter[0].Terminal = ast.CurrentStateIdentity() + + p.fillInPipedInOrCurrentState(&operator.EqFilter[0]) + operator.EqFilter[1] = *p.lastNode p.nextToken() @@ -198,9 +222,62 @@ func (p *parser) parseEqFilter() error { return err } operator.EqFilter[2] = *p.lastNode - } else if p.currentToken().Type != lexer.EOF { + } else if p.currentToken().Type == lexer.EOF { + return invalidEqualityFilterError("missing right hand argument") + } else { return invalidEqualityFilterError("right hand argument is not a string or identity") } p.lastNode = operator return nil } + +func (p *parser) parseReplace() error { + operator := &ast.Node{ + Meta: ast.Meta{Position: p.currentToken().Position}, + Replace: &ast.TernaryOperator{}, + } + if p.lastNode == nil { + return invalidReplaceError("missing left hand argument") + } + if p.lastNode.Path == nil { + return invalidReplaceError("left hand argument is not a path") + } + + p.fillInPipedInOrCurrentState(&operator.Replace[0]) + + operator.Replace[1] = *p.lastNode + + p.nextToken() + if p.currentToken().Type == lexer.STRING { + if err := p.parseString(); err != nil { + return err + } + operator.Replace[2] = *p.lastNode + } else if p.currentToken().Type == lexer.EOF { + return invalidReplaceError("missing right hand argument") + } else { + return invalidReplaceError("right hand argument is not a string") + } + p.lastNode = operator + return nil +} + +func (p *parser) fillInPipedInOrCurrentState(node *ast.Node) { + if p.pipedInNode != nil { + *node = *p.pipedInNode + p.pipedInNode = nil + } else { + node.Terminal = ast.CurrentStateIdentity() + } +} + +func (p *parser) parsePipe() error { + if p.lastNode == nil { + return invalidPipeError("missing pipe in expression") + } + if p.lastNode.Path == nil { + return invalidPipeError("only paths can be piped in") + } + p.pipedInNode = p.lastNode + return nil +} diff --git a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/errors.go b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/errors.go index 181bd2639..34cf4bba1 100644 --- a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/errors.go +++ b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/errors.go @@ -29,3 +29,11 @@ func wrapWithResolveError(err error) error { func wrapWithEqFilterError(err error) error { return fmt.Errorf("eqfilter error: %v", err) } + +func replaceError(format string, a ...interface{}) error { + return wrapWithReplaceError(fmt.Errorf(format, a...)) +} + +func wrapWithReplaceError(err error) error { + return fmt.Errorf("replace error: %v", err) +} diff --git a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/filter.go b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/filter.go index aabaa1dc3..3289a0ee6 100644 --- a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/filter.go +++ b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/filter.go @@ -23,7 +23,7 @@ import ( ) func filter(inputState map[string]interface{}, path ast.VariadicOperator, expectedNode ast.Node) (map[string]interface{}, error) { - filtered, err := applyFuncOnPath(inputState, path, expectedNode, mapContainsValue, true) + filtered, err := applyFuncOnMap(path, inputState, expectedNode, mapContainsValue, true, true) if err != nil { return nil, fmt.Errorf("failed applying operation on the path: %v", err) diff --git a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/path.go b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/path.go index e7b192908..b3b6bdd2a 100644 --- a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/path.go +++ b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/path.go @@ -32,7 +32,7 @@ func applyFuncOnPath(inputState interface{}, path []ast.Node, expectedNode ast.Node, funcToApply func(map[string]interface{}, string, ast.Node) (interface{}, error), - shouldFilterSlice bool) (interface{}, error) { + shouldFilterSlice bool, shouldFilterMap bool) (interface{}, error) { if len(path) == 0 { return inputState, nil } @@ -41,7 +41,7 @@ func applyFuncOnPath(inputState interface{}, if len(path) == 1 { return applyFuncOnLastMapOnPath(path, originalMap, expectedNode, inputState, funcToApply) } - return applyFuncOnMap(path, originalMap, expectedNode, funcToApply, shouldFilterSlice) + return applyFuncOnMap(path, originalMap, expectedNode, funcToApply, shouldFilterSlice, shouldFilterMap) } originalSlice, isSlice := inputState.([]interface{}) @@ -57,32 +57,33 @@ func applyFuncOnSlice(originalSlice []interface{}, expectedNode ast.Node, funcToApply func(map[string]interface{}, string, ast.Node) (interface{}, error), shouldFilterSlice bool) (interface{}, error) { - filteredSlice := []interface{}{} + adjustedSlice := []interface{}{} + sliceEmptyAfterApply := true for _, valueToCheck := range originalSlice { - value, err := applyFuncOnPath(valueToCheck, path, expectedNode, funcToApply, false) + valueAfterApply, err := applyFuncOnPath(valueToCheck, path, expectedNode, funcToApply, false, false) if err != nil { return nil, err } - if value != nil { - filteredSlice = append(filteredSlice, valueToCheck) + if valueAfterApply != nil { + sliceEmptyAfterApply = false + adjustedSlice = append(adjustedSlice, valueAfterApply) + } else if !shouldFilterSlice { + adjustedSlice = append(adjustedSlice, valueToCheck) } } - if len(filteredSlice) == 0 { + if sliceEmptyAfterApply { return nil, nil } - if shouldFilterSlice { - return filteredSlice, nil - } - return originalSlice, nil + return adjustedSlice, nil } func applyFuncOnMap(path []ast.Node, originalMap map[string]interface{}, expectedNode ast.Node, funcToApply func(map[string]interface{}, string, ast.Node) (interface{}, error), - shouldFilterSlice bool) (interface{}, error) { + shouldFilterSlice bool, shouldFilterMap bool) (interface{}, error) { currentStep := path[0] if currentStep.Identity == nil { return nil, pathError("%v has unsupported fromat", currentStep) @@ -96,14 +97,22 @@ func applyFuncOnMap(path []ast.Node, return nil, pathError("cannot find key %s in %v", key, originalMap) } - adjuctedValue, err := applyFuncOnPath(value, nextPath, expectedNode, funcToApply, shouldFilterSlice) + adjustedValue, err := applyFuncOnPath(value, nextPath, expectedNode, funcToApply, shouldFilterSlice, shouldFilterMap) if err != nil { return nil, err } - if adjuctedValue != nil { - return map[string]interface{}{key: adjuctedValue}, nil + if adjustedValue == nil { + return nil, nil + } + + adjustedMap := map[string]interface{}{} + if !shouldFilterMap { + for k, v := range originalMap { + adjustedMap[k] = v + } } - return nil, nil + adjustedMap[key] = adjustedValue + return adjustedMap, nil } func applyFuncOnLastMapOnPath(path []ast.Node, diff --git a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/replace.go b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/replace.go new file mode 100644 index 000000000..f6e81af7a --- /dev/null +++ b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/replace.go @@ -0,0 +1,48 @@ +/* + * Copyright 2021 NMPolicy Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package resolver + +import ( + "github.com/nmstate/nmpolicy/nmpolicy/internal/ast" +) + +func replace(inputState map[string]interface{}, path ast.VariadicOperator, expectedNode ast.Node) (map[string]interface{}, error) { + replaced, err := applyFuncOnMap(path, inputState, expectedNode, replaceMapFieldValue, false, false) + + if err != nil { + return nil, replaceError("failed applying operation on the path: %v", err) + } + + replacedMap, ok := replaced.(map[string]interface{}) + if !ok { + return nil, replaceError("failed converting result to a map") + } + return replacedMap, nil +} + +func replaceMapFieldValue(inputMap map[string]interface{}, mapEntryKeyToReplace string, expectedNode ast.Node) (interface{}, error) { + if expectedNode.String == nil { + return false, replaceError("the desired value %+v is not supported. Curretly only string values are supported", expectedNode) + } + modifiedMap := map[string]interface{}{} + for k, v := range inputMap { + modifiedMap[k] = v + } + + modifiedMap[mapEntryKeyToReplace] = *expectedNode.String + return modifiedMap, nil +} diff --git a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/resolver.go b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/resolver.go index 92a2a061d..7082a90c3 100644 --- a/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/resolver.go +++ b/vendor/github.com/nmstate/nmpolicy/nmpolicy/internal/resolver/resolver.go @@ -93,41 +93,72 @@ func (r *resolver) resolveCaptureEntryName(captureEntryName string) (types.NMSta func (r resolver) resolveCaptureASTEntry(captureASTEntry ast.Node) (types.NMState, error) { if captureASTEntry.EqFilter != nil { return r.resolveEqFilter(captureASTEntry.EqFilter) + } else if captureASTEntry.Replace != nil { + return r.resolveReplace(captureASTEntry.Replace) } - return nil, fmt.Errorf("root node has unsupported operation : %v", captureASTEntry) + return nil, fmt.Errorf("root node has unsupported operation : %+v", captureASTEntry) } func (r resolver) resolveEqFilter(operator *ast.TernaryOperator) (types.NMState, error) { - inputSource, err := r.resolveInputSource((*operator)[0], r.currentState) + filteredState, err := r.resolveTernaryOperator(operator, filter) if err != nil { return nil, wrapWithEqFilterError(err) } + return filteredState, nil +} + +func (r resolver) resolveReplace(operator *ast.TernaryOperator) (types.NMState, error) { + replacedState, err := r.resolveTernaryOperator(operator, replace) + if err != nil { + return nil, wrapWithResolveError(err) + } + return replacedState, nil +} + +func (r resolver) resolveTernaryOperator(operator *ast.TernaryOperator, + resolverFunc func(map[string]interface{}, ast.VariadicOperator, ast.Node) (map[string]interface{}, error)) (types.NMState, error) { + inputSource, err := r.resolveInputSource((*operator)[0]) + if err != nil { + return nil, err + } path, err := r.resolvePath((*operator)[1]) if err != nil { - return nil, wrapWithEqFilterError(err) + return nil, err } - filteredValue, err := r.resolveFilteredValue((*operator)[2]) + value, err := r.resolveStringOrCaptureEntryPath((*operator)[2]) if err != nil { - return nil, wrapWithEqFilterError(err) + return nil, err } - filteredState, err := filter(inputSource, path.steps, *filteredValue) + resolvedState, err := resolverFunc(inputSource, path.steps, *value) if err != nil { - return nil, wrapWithEqFilterError(err) + return nil, err } - return filteredState, nil + return resolvedState, nil } -func (r resolver) resolveInputSource(inputSourceNode ast.Node, - currentState types.NMState) (types.NMState, error) { +func (r resolver) resolveInputSource(inputSourceNode ast.Node) (types.NMState, error) { if ast.CurrentStateIdentity().DeepEqual(inputSourceNode.Terminal) { - return currentState, nil + return r.currentState, nil + } else if inputSourceNode.Path != nil { + resolvedPath, err := r.resolvePath(inputSourceNode) + if err != nil { + return nil, err + } + if resolvedPath.captureEntryName == "" { + return nil, fmt.Errorf("invalid path input source, only capture reference is supported") + } + capturedState, err := r.resolveCaptureEntryName(resolvedPath.captureEntryName) + if err != nil { + return nil, err + } + return capturedState, nil } - return nil, fmt.Errorf("not supported input source %v. Only the current state is supported", inputSourceNode) + return nil, fmt.Errorf("invalid input source %v, only current state or capture reference is supported", inputSourceNode) } -func (r resolver) resolveFilteredValue(filteredValueNode ast.Node) (*ast.Node, error) { +func (r resolver) resolveStringOrCaptureEntryPath(filteredValueNode ast.Node) (*ast.Node, error) { if filteredValueNode.String != nil { return &filteredValueNode, nil } else if filteredValueNode.Path != nil { @@ -143,7 +174,7 @@ func (r resolver) resolveFilteredValue(filteredValueNode ast.Node) (*ast.Node, e Terminal: *terminal, }, nil } else { - return nil, fmt.Errorf("not supported filtered value. Only string or paths are supported") + return nil, fmt.Errorf("not supported value. Only string or capture entry path are supported") } }