...

Source file src/github.com/datawire/ambassador/v2/pkg/k8s/resource.go

Documentation: github.com/datawire/ambassador/v2/pkg/k8s

     1  package k8s
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/datawire/ambassador/v2/pkg/kates"
     9  )
    10  
    11  // Map is a YAML/JSON-ish map with some convenience methods on it.
    12  type Map map[string]interface{}
    13  
    14  // GetMap returns m[name], type-asserted to be a map.  If m[name] is
    15  // not set, or is not a map, then an non-nil empty map is returned.
    16  //
    17  // That is: it always safely returns a usable map.
    18  func (m Map) GetMap(name string) map[string]interface{} {
    19  	v, ok := m[name].(map[string]interface{})
    20  	if !ok {
    21  		return map[string]interface{}{}
    22  	}
    23  	return v
    24  }
    25  
    26  // GetMaps returns m[name], type-asserted to be a list of maps.  If
    27  // m[name] is not set, or is not a list of maps, then a nil array is
    28  // returned.
    29  //
    30  // That is: it always safely returns a usable slice of maps.
    31  func (m Map) GetMaps(name string) []map[string]interface{} {
    32  	v, ok := m[name].([]interface{})
    33  	if !ok {
    34  		return nil
    35  	}
    36  	result := make([]map[string]interface{}, len(v))
    37  	for idx, obj := range v {
    38  		result[idx], ok = obj.(map[string]interface{})
    39  		if !ok {
    40  			result[idx] = map[string]interface{}{}
    41  		}
    42  	}
    43  	return result
    44  }
    45  
    46  // GetString returns m[key], type-asserted to be a string.  If m[key]
    47  // is not set, or it is not a string, then an empty string is
    48  // returned.
    49  //
    50  // That is: it always safely returns a usable string.
    51  func (m Map) GetString(key string) string {
    52  	v, ok := m[key].(string)
    53  	if !ok {
    54  		return ""
    55  	}
    56  	return v
    57  }
    58  
    59  // GetInt64 returns m[key], type-asserted to be an int64.  If m[key]
    60  // is not set, or it is not an int64, then 0 is returned.
    61  //
    62  // That is: it always safely returns a usable int64.
    63  func (m Map) GetInt64(key string) int64 {
    64  	v, ok := m[key].(int64)
    65  	if !ok {
    66  		return 0
    67  	}
    68  	return v
    69  }
    70  
    71  // GetBool returns m[key], type-asserted to be a bool.  If m[key] is
    72  // not set, or it is not a bool, then false is returned.
    73  //
    74  // That is: it always safely returns a usable bool.
    75  func (m Map) GetBool(key string) bool {
    76  	v, ok := m[key].(bool)
    77  	if !ok {
    78  		return false
    79  	}
    80  	return v
    81  }
    82  
    83  // Resource is map from strings to any with some convenience methods for
    84  // accessing typical Kubernetes resource fields.
    85  type Resource map[string]interface{}
    86  
    87  func (r Resource) Kind() string {
    88  	return Map(r).GetString("kind")
    89  }
    90  
    91  // Given a "group/version" string (i.g. from ".apiVersion") and a "kind" string, return a qualified
    92  // "kind.version.group" string.
    93  func QKind(gv, k string) string {
    94  	var g, v string
    95  	if slash := strings.IndexByte(gv, '/'); slash < 0 {
    96  		g = ""
    97  		v = gv
    98  	} else {
    99  		g = gv[:slash]
   100  		v = gv[slash+1:]
   101  	}
   102  	return strings.Join([]string{k, v, g}, ".")
   103  }
   104  
   105  // QKind returns a fully qualified resource kind with the following format: <kind>.<version>.<group>
   106  func (r Resource) QKind() string {
   107  	gv := Map(r).GetString("apiVersion")
   108  	k := Map(r).GetString("kind")
   109  	return QKind(gv, k)
   110  }
   111  
   112  func (r Resource) Empty() bool {
   113  	_, ok := r["kind"]
   114  	return !ok
   115  }
   116  
   117  func (r Resource) Status() Map {
   118  	return Map(r).GetMap("status")
   119  }
   120  
   121  func (r Resource) Data() Map {
   122  	return Map(r).GetMap("data")
   123  }
   124  
   125  func (r Resource) Spec() Map {
   126  	return Map(r).GetMap("spec")
   127  }
   128  
   129  type Metadata map[string]interface{}
   130  
   131  func (r Resource) Metadata() Metadata {
   132  	return Metadata(Map(r).GetMap("metadata"))
   133  }
   134  
   135  // Name returns the metadata "name".
   136  func (m Metadata) Name() string { return Map(m).GetString("name") }
   137  func (r Resource) Name() string { return r.Metadata().Name() }
   138  
   139  // Namespace returns the metadata "namespace".
   140  func (m Metadata) Namespace() string { return Map(m).GetString("namespace") }
   141  func (r Resource) Namespace() string { return r.Metadata().Namespace() }
   142  
   143  // ResourceVersion returns the metadata "resourceVersion".
   144  func (m Metadata) ResourceVersion() string { return Map(m).GetString("resourceVersion") }
   145  func (r Resource) ResourceVersion() string { return r.Metadata().ResourceVersion() }
   146  
   147  func (m Metadata) Annotations() map[string]interface{} {
   148  	return Map(m).GetMap("annotations")
   149  }
   150  
   151  func (m Metadata) QName() string {
   152  	ns := m.Namespace()
   153  	if ns == "" {
   154  		return m.Name()
   155  	} else {
   156  		return fmt.Sprintf("%s.%s", m.Name(), ns)
   157  	}
   158  }
   159  
   160  func (r Resource) QName() string { return r.Metadata().QName() }
   161  
   162  // This fixes objects parsed by yaml to objects that are compatible
   163  // with json by converting any map[interface{}]interface{} to
   164  // map[string]interface{}
   165  func fixup(obj interface{}) interface{} {
   166  	switch obj := obj.(type) {
   167  	case []interface{}:
   168  		return fixupList(obj)
   169  	case map[interface{}]interface{}:
   170  		return fixupMap(obj)
   171  	default:
   172  		return obj
   173  	}
   174  }
   175  
   176  func fixupList(obj []interface{}) []interface{} {
   177  	result := make([]interface{}, len(obj))
   178  	for i, v := range obj {
   179  		result[i] = fixup(v)
   180  	}
   181  	return result
   182  }
   183  
   184  func fixupMap(obj map[interface{}]interface{}) map[string]interface{} {
   185  	result := make(map[string]interface{})
   186  	for key, val := range obj {
   187  		if key, ok := key.(string); ok {
   188  			result[key] = fixup(val)
   189  		}
   190  	}
   191  	return result
   192  }
   193  
   194  // NewResourceFromYaml takes a (already-parsed) untyped YAML
   195  // structure, and fixes it up to be JSON-compatible, and returns it as
   196  // a Resource.
   197  func NewResourceFromYaml(yaml map[interface{}]interface{}) Resource {
   198  	return Resource(fixupMap(yaml))
   199  }
   200  
   201  func ParseResources(name, input string) (result []Resource, err error) {
   202  	resultWrongType, err := kates.ParseManifestsToUnstructured(input)
   203  	if err != nil {
   204  		return nil, fmt.Errorf("%s: %w", name, err)
   205  	}
   206  	bs, err := json.Marshal(resultWrongType)
   207  	if err != nil {
   208  		return nil, fmt.Errorf("%s: %w", name, err)
   209  	}
   210  	if err := json.Unmarshal(bs, &result); err != nil {
   211  		return nil, fmt.Errorf("%s: %w", name, err)
   212  	}
   213  	return result, nil
   214  }
   215  

View as plain text