github.com/splunk/dan1-qbec@v0.7.3/internal/eval/object-extract.go (about)

     1  /*
     2     Copyright 2019 Splunk Inc.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package eval
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"reflect"
    23  
    24  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    25  )
    26  
    27  func tolerantJSON(data interface{}) string {
    28  	b, _ := json.MarshalIndent(data, "", "  ")
    29  	return string(b)
    30  }
    31  
    32  func str(data map[string]interface{}, attr string) string {
    33  	v := data[attr]
    34  	if s, ok := v.(string); ok {
    35  		return s
    36  	}
    37  	return ""
    38  }
    39  
    40  type rawObjectType = int
    41  
    42  const (
    43  	unknownType rawObjectType = iota
    44  	leafType
    45  	arrayType
    46  )
    47  
    48  func getRawObjectType(data map[string]interface{}) rawObjectType {
    49  	kind := str(data, "kind")
    50  	apiVersion := str(data, "apiVersion")
    51  	if kind == "" || apiVersion == "" {
    52  		return unknownType
    53  	}
    54  	_, isArray := data["items"].([]interface{})
    55  	if isArray { // kubernetes list, not primitive
    56  		return arrayType
    57  	}
    58  	return leafType
    59  }
    60  
    61  func walk(data interface{}) ([]map[string]interface{}, error) {
    62  	return walkObjects("$", data, data)
    63  }
    64  
    65  func walkObjects(path string, data interface{}, ctx interface{}) ([]map[string]interface{}, error) {
    66  	var ret []map[string]interface{}
    67  	if data == nil {
    68  		return ret, nil
    69  	}
    70  	switch t := data.(type) {
    71  	case []interface{}:
    72  		for i, o := range t {
    73  			objects, err := walkObjects(fmt.Sprintf("%s[%d]", path, i), o, data)
    74  			if err != nil {
    75  				return nil, err
    76  			}
    77  			ret = append(ret, objects...)
    78  		}
    79  	case map[string]interface{}:
    80  		rt := getRawObjectType(t)
    81  		switch rt {
    82  		case arrayType:
    83  			array := t["items"].([]interface{})
    84  			objects, err := walkObjects(fmt.Sprintf("%s.items", path), array, data)
    85  			if err != nil {
    86  				return nil, err
    87  			}
    88  			ret = append(ret, objects...)
    89  		case leafType:
    90  			u := unstructured.Unstructured{Object: t}
    91  			name := u.GetName()
    92  			genName := u.GetGenerateName()
    93  			if name == "" && genName == "" {
    94  				return nil, fmt.Errorf("object (%v) did not have a name at path %q, (json=\n%s)",
    95  					reflect.TypeOf(data),
    96  					path,
    97  					tolerantJSON(data))
    98  			}
    99  			ret = append(ret, t)
   100  		default:
   101  			for k, v := range t {
   102  				objects, err := walkObjects(fmt.Sprintf("%s.%s", path, k), v, data)
   103  				if err != nil {
   104  					return nil, err
   105  				}
   106  				ret = append(ret, objects...)
   107  			}
   108  		}
   109  	default:
   110  		return nil, fmt.Errorf("unexpected type for object (%v) at path %q, (json=\n%s)",
   111  			reflect.TypeOf(data),
   112  			path,
   113  			tolerantJSON(ctx))
   114  	}
   115  	return ret, nil
   116  }