2019-05-21 18:27:56 +03:00
package engine
import (
"encoding/json"
2019-06-25 18:16:02 -07:00
"errors"
2019-05-22 22:34:25 +01:00
"fmt"
2019-05-22 18:28:38 +01:00
"reflect"
2019-05-23 12:42:58 +03:00
"strconv"
2019-11-27 17:51:33 -08:00
"strings"
2019-08-23 18:34:23 -07:00
"time"
2019-05-22 22:34:25 +01:00
2019-06-25 18:16:02 -07:00
"github.com/golang/glog"
2019-08-23 18:34:23 -07:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-06-25 18:16:02 -07:00
2019-05-22 22:34:25 +01:00
jsonpatch "github.com/evanphx/json-patch"
2019-11-13 13:41:08 -08:00
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
2019-10-21 14:22:31 -07:00
"github.com/nirmata/kyverno/pkg/engine/anchor"
2019-05-21 18:27:56 +03:00
)
2019-09-05 09:37:57 -07:00
// processOverlay processes validation patterns on the resource
func processOverlay ( rule kyverno . Rule , resource unstructured . Unstructured ) ( response RuleResponse , patchedResource unstructured . Unstructured ) {
2019-08-23 18:34:23 -07:00
startTime := time . Now ( )
glog . V ( 4 ) . Infof ( "started applying overlay rule %q (%v)" , rule . Name , startTime )
response . Name = rule . Name
response . Type = Mutation . String ( )
defer func ( ) {
response . RuleStats . ProcessingTime = time . Since ( startTime )
glog . V ( 4 ) . Infof ( "finished applying overlay rule %q (%v)" , response . Name , response . RuleStats . ProcessingTime )
} ( )
2019-11-06 16:16:29 -08:00
patches , overlayerr := processOverlayPatches ( resource . UnstructuredContent ( ) , rule . Mutation . Overlay )
2019-11-05 16:27:06 -08:00
// resource does not satisfy the overlay pattern, we don't apply this rule
2019-11-06 16:16:29 -08:00
if ! reflect . DeepEqual ( overlayerr , overlayError { } ) {
switch overlayerr . statusCode {
2019-11-27 19:40:47 -08:00
// condition key is not present in the resource, don't apply this rule
// consider as success
case conditionNotPresent :
2019-12-09 19:28:34 -08:00
glog . V ( 3 ) . Infof ( "Skip applying rule '%s' on resource '%s/%s/%s': %s" , rule . Name , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , overlayerr . ErrorMsg ( ) )
2019-11-27 19:40:47 -08:00
response . Success = true
return response , resource
2019-11-05 16:27:06 -08:00
// conditions are not met, don't apply this rule
2019-11-06 16:16:29 -08:00
case conditionFailure :
2019-12-09 19:28:34 -08:00
glog . V ( 3 ) . Infof ( "Skip applying rule '%s' on resource '%s/%s/%s': %s" , rule . Name , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , overlayerr . ErrorMsg ( ) )
2019-11-05 16:27:06 -08:00
//TODO: send zero response and not consider this as applied?
2019-12-09 19:28:34 -08:00
response . Success = true
2019-11-06 17:14:32 -08:00
response . Message = overlayerr . ErrorMsg ( )
2019-11-06 16:16:29 -08:00
return response , resource
// rule application failed
case overlayFailure :
glog . Errorf ( "Resource %s/%s/%s: failed to process overlay: %v in the rule %s" , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , overlayerr . ErrorMsg ( ) , rule . Name )
response . Success = false
response . Message = fmt . Sprintf ( "failed to process overlay: %v" , overlayerr . ErrorMsg ( ) )
return response , resource
default :
glog . Errorf ( "Resource %s/%s/%s: Unknown type of error: %v" , resource . GetKind ( ) , resource . GetNamespace ( ) , resource . GetName ( ) , overlayerr . Error ( ) )
response . Success = false
response . Message = fmt . Sprintf ( "Unknown type of error: %v" , overlayerr . Error ( ) )
2019-11-05 16:27:06 -08:00
return response , resource
}
2019-08-23 18:34:23 -07:00
}
2019-11-13 17:56:56 -08:00
if len ( patches ) == 0 {
response . Success = true
return response , resource
}
2019-08-23 18:34:23 -07:00
// convert to RAW
resourceRaw , err := resource . MarshalJSON ( )
if err != nil {
response . Success = false
glog . Infof ( "unable to marshall resource: %v" , err )
response . Message = fmt . Sprintf ( "failed to process JSON patches: %v" , err )
return response , resource
}
var patchResource [ ] byte
2019-08-29 18:48:58 -07:00
patchResource , err = ApplyPatches ( resourceRaw , patches )
if err != nil {
2019-11-13 17:56:56 -08:00
msg := fmt . Sprintf ( "failed to apply JSON patches: %v" , err )
2019-11-27 17:51:33 -08:00
glog . V ( 2 ) . Infof ( "%s, patches=%s" , msg , string ( JoinPatches ( patches ) ) )
2019-08-29 18:48:58 -07:00
response . Success = false
2019-11-13 17:56:56 -08:00
response . Message = msg
2019-08-29 18:48:58 -07:00
return response , resource
}
2019-11-13 17:56:56 -08:00
2019-08-23 18:34:23 -07:00
err = patchedResource . UnmarshalJSON ( patchResource )
if err != nil {
glog . Infof ( "failed to unmarshall resource to undstructured: %v" , err )
response . Success = false
response . Message = fmt . Sprintf ( "failed to process JSON patches: %v" , err )
return response , resource
}
// rule application succesfuly
response . Success = true
2019-11-05 10:16:07 -08:00
response . Message = fmt . Sprintf ( "successfully processed overlay" )
2019-08-23 18:34:23 -07:00
response . Patches = patches
// apply the patches to the resource
return response , patchedResource
}
2019-11-01 11:14:58 -07:00
2019-11-06 16:16:29 -08:00
func processOverlayPatches ( resource , overlay interface { } ) ( [ ] [ ] byte , overlayError ) {
if path , overlayerr := meetConditions ( resource , overlay ) ; ! reflect . DeepEqual ( overlayerr , overlayError { } ) {
2019-11-27 19:40:47 -08:00
switch overlayerr . statusCode {
// anchor key does not exist in the resource, skip applying policy
case conditionNotPresent :
glog . V ( 4 ) . Infof ( "Mutate rule: skip applying policy: %v at %s" , overlayerr , path )
2019-12-09 19:28:34 -08:00
return nil , newOverlayError ( overlayerr . statusCode , fmt . Sprintf ( "Policy not applied, condition tag not present: %v at %s" , overlayerr . ErrorMsg ( ) , path ) )
2019-11-27 19:40:47 -08:00
// anchor key is not satisfied in the resource, skip applying policy
case conditionFailure :
2019-11-11 21:03:34 -08:00
// anchor key is not satisfied in the resource, skip applying policy
2019-11-06 16:16:29 -08:00
glog . V ( 4 ) . Infof ( "Mutate rule: failed to validate condition at %s, err: %v" , path , overlayerr )
2019-12-09 19:28:34 -08:00
return nil , newOverlayError ( overlayerr . statusCode , fmt . Sprintf ( "Policy not applied, conditions are not met at %s, %v" , path , overlayerr ) )
2019-11-05 16:27:06 -08:00
}
2019-11-06 16:16:29 -08:00
}
2019-11-05 16:27:06 -08:00
2019-11-06 16:16:29 -08:00
patchBytes , err := mutateResourceWithOverlay ( resource , overlay )
if err != nil {
return patchBytes , newOverlayError ( overlayFailure , err . Error ( ) )
2019-05-21 18:27:56 +03:00
}
2019-11-06 16:16:29 -08:00
return patchBytes , overlayError { }
2019-05-21 18:27:56 +03:00
}
2019-06-11 16:54:19 +03:00
// mutateResourceWithOverlay is a start of overlaying process
2019-07-05 15:20:43 -07:00
func mutateResourceWithOverlay ( resource , pattern interface { } ) ( [ ] [ ] byte , error ) {
2019-06-12 12:21:52 +03:00
// It assumes that mutation is started from root, so "/" is passed
2019-06-11 16:54:19 +03:00
return applyOverlay ( resource , pattern , "/" )
}
// applyOverlay detects type of current item and goes down through overlay and resource trees applying overlay
2019-07-05 15:20:43 -07:00
func applyOverlay ( resource , overlay interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-05-22 18:28:38 +01:00
// resource item exists but has different type - replace
// all subtree within this path by overlay
if reflect . TypeOf ( resource ) != reflect . TypeOf ( overlay ) {
2019-06-25 18:16:02 -07:00
patch , err := replaceSubtree ( overlay , path )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-05-22 18:28:38 +01:00
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-08-23 18:34:23 -07:00
//TODO : check if return is needed ?
2019-06-25 18:16:02 -07:00
}
2019-06-11 16:54:19 +03:00
return applyOverlayForSameTypes ( resource , overlay , path )
}
// applyOverlayForSameTypes is applyOverlay for cases when TypeOf(resource) == TypeOf(overlay)
2019-07-05 15:20:43 -07:00
func applyOverlayForSameTypes ( resource , overlay interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-06-11 16:54:19 +03:00
// detect the type of resource and overlay and select corresponding handler
2019-05-21 18:27:56 +03:00
switch typedOverlay := overlay . ( type ) {
2019-06-11 16:54:19 +03:00
// map
2019-05-21 18:27:56 +03:00
case map [ string ] interface { } :
typedResource := resource . ( map [ string ] interface { } )
2019-06-25 18:16:02 -07:00
patches , err := applyOverlayToMap ( typedResource , typedOverlay , path )
if err != nil {
return nil , err
2019-05-21 18:27:56 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-06-11 16:54:19 +03:00
// array
2019-05-21 18:27:56 +03:00
case [ ] interface { } :
typedResource := resource . ( [ ] interface { } )
2019-06-25 18:16:02 -07:00
patches , err := applyOverlayToArray ( typedResource , typedOverlay , path )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-06-11 16:54:19 +03:00
// elementary types
2019-05-27 18:21:23 +03:00
case string , float64 , int64 , bool :
2019-06-25 18:16:02 -07:00
patch , err := replaceSubtree ( overlay , path )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-05-23 17:31:34 +03:00
default :
2019-06-25 18:16:02 -07:00
return nil , fmt . Errorf ( "Overlay has unsupported type: %T" , overlay )
2019-05-21 18:27:56 +03:00
}
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-05-21 18:27:56 +03:00
}
2019-06-11 16:54:19 +03:00
// for each overlay and resource map elements applies overlay
2019-07-05 15:20:43 -07:00
func applyOverlayToMap ( resourceMap , overlayMap map [ string ] interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-06-11 16:54:19 +03:00
for key , value := range overlayMap {
// skip anchor element because it has condition, not
// the value that must replace resource value
2019-10-21 14:22:31 -07:00
if anchor . IsConditionAnchor ( key ) {
2019-06-11 16:54:19 +03:00
continue
}
2019-06-14 17:19:32 +03:00
noAnchorKey := removeAnchor ( key )
currentPath := path + noAnchorKey + "/"
resourcePart , ok := resourceMap [ noAnchorKey ]
2019-06-11 16:54:19 +03:00
2019-10-21 14:22:31 -07:00
if ok && ! anchor . IsAddingAnchor ( key ) {
2019-06-11 16:54:19 +03:00
// Key exists - go down through the overlay and resource trees
2019-06-25 18:16:02 -07:00
patches , err := applyOverlay ( resourcePart , value , currentPath )
if err != nil {
return nil , err
2019-06-11 16:54:19 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-06-14 17:19:32 +03:00
}
if ! ok {
2019-06-11 16:54:19 +03:00
// Key does not exist - insert entire overlay subtree
2019-06-25 18:16:02 -07:00
patch , err := insertSubtree ( value , currentPath )
if err != nil {
return nil , err
2019-06-11 16:54:19 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-06-11 16:54:19 +03:00
}
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-06-11 16:54:19 +03:00
}
// for each overlay and resource array elements applies overlay
2019-07-05 15:20:43 -07:00
func applyOverlayToArray ( resource , overlay [ ] interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-06-11 16:54:19 +03:00
if 0 == len ( overlay ) {
2019-06-25 18:16:02 -07:00
return nil , errors . New ( "Empty array detected in the overlay" )
2019-06-11 16:54:19 +03:00
}
if 0 == len ( resource ) {
2019-06-12 12:21:52 +03:00
// If array resource is empty, insert part from overlay
2019-06-25 18:16:02 -07:00
patch , err := insertSubtree ( overlay , path )
if err != nil {
return nil , err
2019-06-11 16:54:19 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-06-11 16:54:19 +03:00
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-05-22 22:34:25 +01:00
}
if reflect . TypeOf ( resource [ 0 ] ) != reflect . TypeOf ( overlay [ 0 ] ) {
2019-06-25 18:16:02 -07:00
return nil , fmt . Errorf ( "Overlay array and resource array have elements of different types: %T and %T" , overlay [ 0 ] , resource [ 0 ] )
2019-05-22 22:34:25 +01:00
}
2019-06-11 16:54:19 +03:00
return applyOverlayToArrayOfSameTypes ( resource , overlay , path )
}
2019-06-12 12:21:52 +03:00
// applyOverlayToArrayOfSameTypes applies overlay to array elements if they (resource and overlay elements) have same type
2019-07-05 15:20:43 -07:00
func applyOverlayToArrayOfSameTypes ( resource , overlay [ ] interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-06-11 16:54:19 +03:00
2019-05-21 18:27:56 +03:00
switch overlay [ 0 ] . ( type ) {
case map [ string ] interface { } :
2019-06-11 16:54:19 +03:00
return applyOverlayToArrayOfMaps ( resource , overlay , path )
2019-05-21 18:27:56 +03:00
default :
2019-06-11 16:54:19 +03:00
lastElementIdx := len ( resource )
// Add elements to the end
for i , value := range overlay {
currentPath := path + strconv . Itoa ( lastElementIdx + i ) + "/"
2019-06-12 12:21:52 +03:00
// currentPath example: /spec/template/spec/containers/3/
2019-06-25 18:16:02 -07:00
patch , err := insertSubtree ( value , currentPath )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-05-21 18:27:56 +03:00
}
}
2019-05-22 22:34:25 +01:00
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-05-22 22:34:25 +01:00
}
2019-06-12 13:12:36 +03:00
// Array of maps needs special handling as far as it can have anchors.
2019-07-05 15:20:43 -07:00
func applyOverlayToArrayOfMaps ( resource , overlay [ ] interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-05-22 22:34:25 +01:00
2019-06-11 16:54:19 +03:00
lastElementIdx := len ( resource )
for i , overlayElement := range overlay {
typedOverlay := overlayElement . ( map [ string ] interface { } )
anchors := getAnchorsFromMap ( typedOverlay )
2019-05-22 22:34:25 +01:00
2019-06-11 16:54:19 +03:00
if len ( anchors ) > 0 {
// If we have anchors - choose corresponding resource element and mutate it
2019-06-25 18:16:02 -07:00
patches , err := applyOverlayWithAnchors ( resource , overlayElement , anchors , path )
if err != nil {
return nil , err
2019-06-11 16:54:19 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-06-11 16:54:19 +03:00
} else if hasNestedAnchors ( overlayElement ) {
// If we have anchors on the lower level - continue traversing overlay and resource trees
for j , resourceElement := range resource {
currentPath := path + strconv . Itoa ( j ) + "/"
2019-06-12 12:21:52 +03:00
// currentPath example: /spec/template/spec/containers/3/
2019-06-25 18:16:02 -07:00
patches , err := applyOverlay ( resourceElement , overlayElement , currentPath )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-05-22 22:34:25 +01:00
}
2019-06-11 16:54:19 +03:00
} else {
// Overlay subtree has no anchors - insert new element
currentPath := path + strconv . Itoa ( lastElementIdx + i ) + "/"
2019-06-12 12:21:52 +03:00
// currentPath example: /spec/template/spec/containers/3/
2019-06-25 18:16:02 -07:00
patch , err := insertSubtree ( overlayElement , currentPath )
if err != nil {
return nil , err
2019-05-22 22:34:25 +01:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patch )
2019-05-22 18:28:38 +01:00
}
2019-05-21 18:27:56 +03:00
}
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-06-11 16:54:19 +03:00
}
2019-07-05 15:20:43 -07:00
func applyOverlayWithAnchors ( resource [ ] interface { } , overlay interface { } , anchors map [ string ] interface { } , path string ) ( [ ] [ ] byte , error ) {
var appliedPatches [ ] [ ] byte
2019-06-11 16:54:19 +03:00
for i , resourceElement := range resource {
typedResource := resourceElement . ( map [ string ] interface { } )
currentPath := path + strconv . Itoa ( i ) + "/"
2019-06-12 12:21:52 +03:00
// currentPath example: /spec/template/spec/containers/3/
2019-06-11 16:54:19 +03:00
if ! skipArrayObject ( typedResource , anchors ) {
2019-06-25 18:16:02 -07:00
patches , err := applyOverlay ( resourceElement , overlay , currentPath )
if err != nil {
return nil , err
2019-06-11 16:54:19 +03:00
}
2019-06-25 18:16:02 -07:00
appliedPatches = append ( appliedPatches , patches ... )
2019-06-11 16:54:19 +03:00
}
}
2019-06-25 18:16:02 -07:00
return appliedPatches , nil
2019-05-21 18:27:56 +03:00
}
2019-07-05 15:20:43 -07:00
func insertSubtree ( overlay interface { } , path string ) ( [ ] byte , error ) {
2019-06-11 16:54:19 +03:00
return processSubtree ( overlay , path , "add" )
2019-05-22 22:34:25 +01:00
}
2019-07-05 15:20:43 -07:00
func replaceSubtree ( overlay interface { } , path string ) ( [ ] byte , error ) {
2019-06-11 16:54:19 +03:00
return processSubtree ( overlay , path , "replace" )
2019-05-22 22:34:25 +01:00
}
2019-07-05 15:20:43 -07:00
func processSubtree ( overlay interface { } , path string , op string ) ( [ ] byte , error ) {
2019-06-11 16:54:19 +03:00
2019-05-22 22:34:25 +01:00
if len ( path ) > 1 && path [ len ( path ) - 1 ] == '/' {
path = path [ : len ( path ) - 1 ]
}
2019-11-27 17:51:33 -08:00
path = preparePath ( path )
2019-05-22 22:34:25 +01:00
value := prepareJSONValue ( overlay )
patchStr := fmt . Sprintf ( ` { "op": "%s", "path": "%s", "value": %s } ` , op , path , value )
2019-05-21 18:27:56 +03:00
2019-05-22 22:34:25 +01:00
// check the patch
_ , err := jsonpatch . DecodePatch ( [ ] byte ( "[" + patchStr + "]" ) )
if err != nil {
2019-06-25 18:16:02 -07:00
glog . V ( 3 ) . Info ( err )
return nil , fmt . Errorf ( "Failed to make '%s' patch from an overlay '%s' for path %s" , op , value , path )
2019-05-22 22:34:25 +01:00
}
2019-07-05 15:20:43 -07:00
return [ ] byte ( patchStr ) , nil
2019-05-21 18:27:56 +03:00
}
2019-11-27 17:51:33 -08:00
func preparePath ( path string ) string {
if path == "" {
path = "/"
}
annPath := "/metadata/annotations/"
// escape slash in annotation patch
if strings . Contains ( path , annPath ) {
p := path [ len ( annPath ) : ]
path = annPath + strings . ReplaceAll ( p , "/" , "~1" )
}
return path
}
2019-05-23 17:31:34 +03:00
// converts overlay to JSON string to be inserted into the JSON Patch
2019-05-22 22:34:25 +01:00
func prepareJSONValue ( overlay interface { } ) string {
2019-07-25 13:14:55 -04:00
var err error
2019-06-11 16:54:19 +03:00
if err != nil || hasOnlyAnchors ( overlay ) {
2019-06-25 18:16:02 -07:00
glog . V ( 3 ) . Info ( err )
2019-05-22 22:34:25 +01:00
return ""
}
2019-07-25 13:14:55 -04:00
// Need to remove anchors from the overlay struct
overlayWithoutAnchors := removeAnchorFromSubTree ( overlay )
jsonOverlay , err := json . Marshal ( overlayWithoutAnchors )
2019-06-11 16:54:19 +03:00
return string ( jsonOverlay )
2019-05-22 22:34:25 +01:00
}
2019-07-25 13:14:55 -04:00
func removeAnchorFromSubTree ( overlay interface { } ) interface { } {
var result interface { }
switch typed := overlay . ( type ) {
case map [ string ] interface { } :
// assuming only keys have anchors
result = removeAnchroFromMap ( typed )
case [ ] interface { } :
arrayResult := make ( [ ] interface { } , 0 )
for _ , value := range typed {
arrayResult = append ( arrayResult , removeAnchorFromSubTree ( value ) )
}
result = arrayResult
default :
result = overlay
}
return result
}
func removeAnchroFromMap ( overlay map [ string ] interface { } ) map [ string ] interface { } {
result := make ( map [ string ] interface { } , 0 )
for k , v := range overlay {
result [ getRawKeyIfWrappedWithAttributes ( k ) ] = removeAnchorFromSubTree ( v )
}
return result
}
2019-06-11 16:54:19 +03:00
// Anchor has pattern value, so resource shouldn't be mutated with it
2019-06-12 12:21:52 +03:00
// If entire subtree has only anchor keys - we should skip inserting it
2019-05-22 22:34:25 +01:00
func hasOnlyAnchors ( overlay interface { } ) bool {
switch typed := overlay . ( type ) {
case map [ string ] interface { } :
2019-06-05 17:43:59 -07:00
if anchors := getAnchorsFromMap ( typed ) ; len ( anchors ) == len ( typed ) {
2019-05-22 22:34:25 +01:00
return true
}
for _ , value := range typed {
if ! hasOnlyAnchors ( value ) {
return false
}
}
2019-06-11 16:54:19 +03:00
case [ ] interface { } :
for _ , value := range typed {
if ! hasOnlyAnchors ( value ) {
return false
}
}
2019-05-22 22:34:25 +01:00
default :
return false
}
2019-06-11 16:54:19 +03:00
return true
2019-05-22 22:34:25 +01:00
}
2019-06-11 16:54:19 +03:00
// Checks if subtree has anchors
2019-05-22 22:34:25 +01:00
func hasNestedAnchors ( overlay interface { } ) bool {
switch typed := overlay . ( type ) {
case map [ string ] interface { } :
2019-06-05 17:43:59 -07:00
if anchors := getAnchorsFromMap ( typed ) ; len ( anchors ) > 0 {
2019-05-22 22:34:25 +01:00
return true
}
for _ , value := range typed {
if hasNestedAnchors ( value ) {
return true
}
}
2019-05-23 14:51:41 +03:00
return false
case [ ] interface { } :
for _ , value := range typed {
if hasNestedAnchors ( value ) {
return true
}
}
2019-05-22 22:34:25 +01:00
return false
default :
return false
}
2019-05-21 18:27:56 +03:00
}