1/* 2Copyright 2015 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package unstructured 18 19import ( 20 "bytes" 21 gojson "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "strings" 26 27 "github.com/golang/glog" 28 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/apimachinery/pkg/types" 33 "k8s.io/apimachinery/pkg/util/json" 34) 35 36// Unstructured allows objects that do not have Golang structs registered to be manipulated 37// generically. This can be used to deal with the API objects from a plug-in. Unstructured 38// objects still have functioning TypeMeta features-- kind, version, etc. 39// 40// WARNING: This object has accessors for the v1 standard metadata. You *MUST NOT* use this 41// type if you are dealing with objects that are not in the server meta v1 schema. 42// 43// TODO: make the serialization part of this type distinct from the field accessors. 44type Unstructured struct { 45 // Object is a JSON compatible map with string, float, int, bool, []interface{}, or 46 // map[string]interface{} 47 // children. 48 Object map[string]interface{} 49} 50 51var _ metav1.Object = &Unstructured{} 52var _ runtime.Unstructured = &Unstructured{} 53var _ runtime.Unstructured = &UnstructuredList{} 54 55func (obj *Unstructured) GetObjectKind() schema.ObjectKind { return obj } 56func (obj *UnstructuredList) GetObjectKind() schema.ObjectKind { return obj } 57 58func (obj *Unstructured) IsUnstructuredObject() {} 59func (obj *UnstructuredList) IsUnstructuredObject() {} 60 61func (obj *Unstructured) IsList() bool { 62 if obj.Object != nil { 63 _, ok := obj.Object["items"] 64 return ok 65 } 66 return false 67} 68func (obj *UnstructuredList) IsList() bool { return true } 69 70func (obj *Unstructured) UnstructuredContent() map[string]interface{} { 71 if obj.Object == nil { 72 obj.Object = make(map[string]interface{}) 73 } 74 return obj.Object 75} 76 77// UnstructuredContent returns a map contain an overlay of the Items field onto 78// the Object field. Items always overwrites overlay. Changing "items" in the 79// returned object will affect items in the underlying Items field, but changing 80// the "items" slice itself will have no effect. 81// TODO: expose SetUnstructuredContent on runtime.Unstructured that allows 82// items to be changed. 83func (obj *UnstructuredList) UnstructuredContent() map[string]interface{} { 84 out := obj.Object 85 if out == nil { 86 out = make(map[string]interface{}) 87 } 88 items := make([]interface{}, len(obj.Items)) 89 for i, item := range obj.Items { 90 items[i] = item.Object 91 } 92 out["items"] = items 93 return out 94} 95 96// MarshalJSON ensures that the unstructured object produces proper 97// JSON when passed to Go's standard JSON library. 98func (u *Unstructured) MarshalJSON() ([]byte, error) { 99 var buf bytes.Buffer 100 err := UnstructuredJSONScheme.Encode(u, &buf) 101 return buf.Bytes(), err 102} 103 104// UnmarshalJSON ensures that the unstructured object properly decodes 105// JSON when passed to Go's standard JSON library. 106func (u *Unstructured) UnmarshalJSON(b []byte) error { 107 _, _, err := UnstructuredJSONScheme.Decode(b, nil, u) 108 return err 109} 110 111func getNestedField(obj map[string]interface{}, fields ...string) interface{} { 112 var val interface{} = obj 113 for _, field := range fields { 114 if _, ok := val.(map[string]interface{}); !ok { 115 return nil 116 } 117 val = val.(map[string]interface{})[field] 118 } 119 return val 120} 121 122func getNestedString(obj map[string]interface{}, fields ...string) string { 123 if str, ok := getNestedField(obj, fields...).(string); ok { 124 return str 125 } 126 return "" 127} 128 129func getNestedSlice(obj map[string]interface{}, fields ...string) []string { 130 if m, ok := getNestedField(obj, fields...).([]interface{}); ok { 131 strSlice := make([]string, 0, len(m)) 132 for _, v := range m { 133 if str, ok := v.(string); ok { 134 strSlice = append(strSlice, str) 135 } 136 } 137 return strSlice 138 } 139 return nil 140} 141 142func getNestedMap(obj map[string]interface{}, fields ...string) map[string]string { 143 if m, ok := getNestedField(obj, fields...).(map[string]interface{}); ok { 144 strMap := make(map[string]string, len(m)) 145 for k, v := range m { 146 if str, ok := v.(string); ok { 147 strMap[k] = str 148 } 149 } 150 return strMap 151 } 152 return nil 153} 154 155func setNestedField(obj map[string]interface{}, value interface{}, fields ...string) { 156 m := obj 157 if len(fields) > 1 { 158 for _, field := range fields[0 : len(fields)-1] { 159 if _, ok := m[field].(map[string]interface{}); !ok { 160 m[field] = make(map[string]interface{}) 161 } 162 m = m[field].(map[string]interface{}) 163 } 164 } 165 m[fields[len(fields)-1]] = value 166} 167 168func setNestedSlice(obj map[string]interface{}, value []string, fields ...string) { 169 m := make([]interface{}, 0, len(value)) 170 for _, v := range value { 171 m = append(m, v) 172 } 173 setNestedField(obj, m, fields...) 174} 175 176func setNestedMap(obj map[string]interface{}, value map[string]string, fields ...string) { 177 m := make(map[string]interface{}, len(value)) 178 for k, v := range value { 179 m[k] = v 180 } 181 setNestedField(obj, m, fields...) 182} 183 184func (u *Unstructured) setNestedField(value interface{}, fields ...string) { 185 if u.Object == nil { 186 u.Object = make(map[string]interface{}) 187 } 188 setNestedField(u.Object, value, fields...) 189} 190 191func (u *Unstructured) setNestedSlice(value []string, fields ...string) { 192 if u.Object == nil { 193 u.Object = make(map[string]interface{}) 194 } 195 setNestedSlice(u.Object, value, fields...) 196} 197 198func (u *Unstructured) setNestedMap(value map[string]string, fields ...string) { 199 if u.Object == nil { 200 u.Object = make(map[string]interface{}) 201 } 202 setNestedMap(u.Object, value, fields...) 203} 204 205func extractOwnerReference(src interface{}) metav1.OwnerReference { 206 v := src.(map[string]interface{}) 207 // though this field is a *bool, but when decoded from JSON, it's 208 // unmarshalled as bool. 209 var controllerPtr *bool 210 controller, ok := (getNestedField(v, "controller")).(bool) 211 if !ok { 212 controllerPtr = nil 213 } else { 214 controllerCopy := controller 215 controllerPtr = &controllerCopy 216 } 217 var blockOwnerDeletionPtr *bool 218 blockOwnerDeletion, ok := (getNestedField(v, "blockOwnerDeletion")).(bool) 219 if !ok { 220 blockOwnerDeletionPtr = nil 221 } else { 222 blockOwnerDeletionCopy := blockOwnerDeletion 223 blockOwnerDeletionPtr = &blockOwnerDeletionCopy 224 } 225 return metav1.OwnerReference{ 226 Kind: getNestedString(v, "kind"), 227 Name: getNestedString(v, "name"), 228 APIVersion: getNestedString(v, "apiVersion"), 229 UID: (types.UID)(getNestedString(v, "uid")), 230 Controller: controllerPtr, 231 BlockOwnerDeletion: blockOwnerDeletionPtr, 232 } 233} 234 235func setOwnerReference(src metav1.OwnerReference) map[string]interface{} { 236 ret := make(map[string]interface{}) 237 controllerPtr := src.Controller 238 if controllerPtr != nil { 239 controller := *controllerPtr 240 controllerPtr = &controller 241 } 242 blockOwnerDeletionPtr := src.BlockOwnerDeletion 243 if blockOwnerDeletionPtr != nil { 244 blockOwnerDeletion := *blockOwnerDeletionPtr 245 blockOwnerDeletionPtr = &blockOwnerDeletion 246 } 247 setNestedField(ret, src.Kind, "kind") 248 setNestedField(ret, src.Name, "name") 249 setNestedField(ret, src.APIVersion, "apiVersion") 250 setNestedField(ret, string(src.UID), "uid") 251 setNestedField(ret, controllerPtr, "controller") 252 setNestedField(ret, blockOwnerDeletionPtr, "blockOwnerDeletion") 253 return ret 254} 255 256func getOwnerReferences(object map[string]interface{}) ([]map[string]interface{}, error) { 257 field := getNestedField(object, "metadata", "ownerReferences") 258 if field == nil { 259 return nil, fmt.Errorf("cannot find field metadata.ownerReferences in %v", object) 260 } 261 ownerReferences, ok := field.([]map[string]interface{}) 262 if ok { 263 return ownerReferences, nil 264 } 265 // TODO: This is hacky... 266 interfaces, ok := field.([]interface{}) 267 if !ok { 268 return nil, fmt.Errorf("expect metadata.ownerReferences to be a slice in %#v", object) 269 } 270 ownerReferences = make([]map[string]interface{}, 0, len(interfaces)) 271 for i := 0; i < len(interfaces); i++ { 272 r, ok := interfaces[i].(map[string]interface{}) 273 if !ok { 274 return nil, fmt.Errorf("expect element metadata.ownerReferences to be a map[string]interface{} in %#v", object) 275 } 276 ownerReferences = append(ownerReferences, r) 277 } 278 return ownerReferences, nil 279} 280 281func (u *Unstructured) GetOwnerReferences() []metav1.OwnerReference { 282 original, err := getOwnerReferences(u.Object) 283 if err != nil { 284 glog.V(6).Info(err) 285 return nil 286 } 287 ret := make([]metav1.OwnerReference, 0, len(original)) 288 for i := 0; i < len(original); i++ { 289 ret = append(ret, extractOwnerReference(original[i])) 290 } 291 return ret 292} 293 294func (u *Unstructured) SetOwnerReferences(references []metav1.OwnerReference) { 295 var newReferences = make([]map[string]interface{}, 0, len(references)) 296 for i := 0; i < len(references); i++ { 297 newReferences = append(newReferences, setOwnerReference(references[i])) 298 } 299 u.setNestedField(newReferences, "metadata", "ownerReferences") 300} 301 302func (u *Unstructured) GetAPIVersion() string { 303 return getNestedString(u.Object, "apiVersion") 304} 305 306func (u *Unstructured) SetAPIVersion(version string) { 307 u.setNestedField(version, "apiVersion") 308} 309 310func (u *Unstructured) GetKind() string { 311 return getNestedString(u.Object, "kind") 312} 313 314func (u *Unstructured) SetKind(kind string) { 315 u.setNestedField(kind, "kind") 316} 317 318func (u *Unstructured) GetNamespace() string { 319 return getNestedString(u.Object, "metadata", "namespace") 320} 321 322func (u *Unstructured) SetNamespace(namespace string) { 323 u.setNestedField(namespace, "metadata", "namespace") 324} 325 326func (u *Unstructured) GetName() string { 327 return getNestedString(u.Object, "metadata", "name") 328} 329 330func (u *Unstructured) SetName(name string) { 331 u.setNestedField(name, "metadata", "name") 332} 333 334func (u *Unstructured) GetGenerateName() string { 335 return getNestedString(u.Object, "metadata", "generateName") 336} 337 338func (u *Unstructured) SetGenerateName(name string) { 339 u.setNestedField(name, "metadata", "generateName") 340} 341 342func (u *Unstructured) GetUID() types.UID { 343 return types.UID(getNestedString(u.Object, "metadata", "uid")) 344} 345 346func (u *Unstructured) SetUID(uid types.UID) { 347 u.setNestedField(string(uid), "metadata", "uid") 348} 349 350func (u *Unstructured) GetResourceVersion() string { 351 return getNestedString(u.Object, "metadata", "resourceVersion") 352} 353 354func (u *Unstructured) SetResourceVersion(version string) { 355 u.setNestedField(version, "metadata", "resourceVersion") 356} 357 358func (u *Unstructured) GetSelfLink() string { 359 return getNestedString(u.Object, "metadata", "selfLink") 360} 361 362func (u *Unstructured) SetSelfLink(selfLink string) { 363 u.setNestedField(selfLink, "metadata", "selfLink") 364} 365 366func (u *Unstructured) GetCreationTimestamp() metav1.Time { 367 var timestamp metav1.Time 368 timestamp.UnmarshalQueryParameter(getNestedString(u.Object, "metadata", "creationTimestamp")) 369 return timestamp 370} 371 372func (u *Unstructured) SetCreationTimestamp(timestamp metav1.Time) { 373 ts, _ := timestamp.MarshalQueryParameter() 374 u.setNestedField(ts, "metadata", "creationTimestamp") 375} 376 377func (u *Unstructured) GetDeletionTimestamp() *metav1.Time { 378 var timestamp metav1.Time 379 timestamp.UnmarshalQueryParameter(getNestedString(u.Object, "metadata", "deletionTimestamp")) 380 if timestamp.IsZero() { 381 return nil 382 } 383 return ×tamp 384} 385 386func (u *Unstructured) SetDeletionTimestamp(timestamp *metav1.Time) { 387 ts, _ := timestamp.MarshalQueryParameter() 388 u.setNestedField(ts, "metadata", "deletionTimestamp") 389} 390 391func (u *Unstructured) GetLabels() map[string]string { 392 return getNestedMap(u.Object, "metadata", "labels") 393} 394 395func (u *Unstructured) SetLabels(labels map[string]string) { 396 u.setNestedMap(labels, "metadata", "labels") 397} 398 399func (u *Unstructured) GetAnnotations() map[string]string { 400 return getNestedMap(u.Object, "metadata", "annotations") 401} 402 403func (u *Unstructured) SetAnnotations(annotations map[string]string) { 404 u.setNestedMap(annotations, "metadata", "annotations") 405} 406 407func (u *Unstructured) SetGroupVersionKind(gvk schema.GroupVersionKind) { 408 u.SetAPIVersion(gvk.GroupVersion().String()) 409 u.SetKind(gvk.Kind) 410} 411 412func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind { 413 gv, err := schema.ParseGroupVersion(u.GetAPIVersion()) 414 if err != nil { 415 return schema.GroupVersionKind{} 416 } 417 gvk := gv.WithKind(u.GetKind()) 418 return gvk 419} 420 421func (u *Unstructured) GetFinalizers() []string { 422 return getNestedSlice(u.Object, "metadata", "finalizers") 423} 424 425func (u *Unstructured) SetFinalizers(finalizers []string) { 426 u.setNestedSlice(finalizers, "metadata", "finalizers") 427} 428 429func (u *Unstructured) GetClusterName() string { 430 return getNestedString(u.Object, "metadata", "clusterName") 431} 432 433func (u *Unstructured) SetClusterName(clusterName string) { 434 u.setNestedField(clusterName, "metadata", "clusterName") 435} 436 437// UnstructuredList allows lists that do not have Golang structs 438// registered to be manipulated generically. This can be used to deal 439// with the API lists from a plug-in. 440type UnstructuredList struct { 441 Object map[string]interface{} 442 443 // Items is a list of unstructured objects. 444 Items []*Unstructured `json:"items"` 445} 446 447// MarshalJSON ensures that the unstructured list object produces proper 448// JSON when passed to Go's standard JSON library. 449func (u *UnstructuredList) MarshalJSON() ([]byte, error) { 450 var buf bytes.Buffer 451 err := UnstructuredJSONScheme.Encode(u, &buf) 452 return buf.Bytes(), err 453} 454 455// UnmarshalJSON ensures that the unstructured list object properly 456// decodes JSON when passed to Go's standard JSON library. 457func (u *UnstructuredList) UnmarshalJSON(b []byte) error { 458 _, _, err := UnstructuredJSONScheme.Decode(b, nil, u) 459 return err 460} 461 462func (u *UnstructuredList) setNestedField(value interface{}, fields ...string) { 463 if u.Object == nil { 464 u.Object = make(map[string]interface{}) 465 } 466 setNestedField(u.Object, value, fields...) 467} 468 469func (u *UnstructuredList) GetAPIVersion() string { 470 return getNestedString(u.Object, "apiVersion") 471} 472 473func (u *UnstructuredList) SetAPIVersion(version string) { 474 u.setNestedField(version, "apiVersion") 475} 476 477func (u *UnstructuredList) GetKind() string { 478 return getNestedString(u.Object, "kind") 479} 480 481func (u *UnstructuredList) SetKind(kind string) { 482 u.setNestedField(kind, "kind") 483} 484 485func (u *UnstructuredList) GetResourceVersion() string { 486 return getNestedString(u.Object, "metadata", "resourceVersion") 487} 488 489func (u *UnstructuredList) SetResourceVersion(version string) { 490 u.setNestedField(version, "metadata", "resourceVersion") 491} 492 493func (u *UnstructuredList) GetSelfLink() string { 494 return getNestedString(u.Object, "metadata", "selfLink") 495} 496 497func (u *UnstructuredList) SetSelfLink(selfLink string) { 498 u.setNestedField(selfLink, "metadata", "selfLink") 499} 500 501func (u *UnstructuredList) SetGroupVersionKind(gvk schema.GroupVersionKind) { 502 u.SetAPIVersion(gvk.GroupVersion().String()) 503 u.SetKind(gvk.Kind) 504} 505 506func (u *UnstructuredList) GroupVersionKind() schema.GroupVersionKind { 507 gv, err := schema.ParseGroupVersion(u.GetAPIVersion()) 508 if err != nil { 509 return schema.GroupVersionKind{} 510 } 511 gvk := gv.WithKind(u.GetKind()) 512 return gvk 513} 514 515// UnstructuredJSONScheme is capable of converting JSON data into the Unstructured 516// type, which can be used for generic access to objects without a predefined scheme. 517// TODO: move into serializer/json. 518var UnstructuredJSONScheme runtime.Codec = unstructuredJSONScheme{} 519 520type unstructuredJSONScheme struct{} 521 522func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { 523 var err error 524 if obj != nil { 525 err = s.decodeInto(data, obj) 526 } else { 527 obj, err = s.decode(data) 528 } 529 530 if err != nil { 531 return nil, nil, err 532 } 533 534 gvk := obj.GetObjectKind().GroupVersionKind() 535 if len(gvk.Kind) == 0 { 536 return nil, &gvk, runtime.NewMissingKindErr(string(data)) 537 } 538 539 return obj, &gvk, nil 540} 541 542func (unstructuredJSONScheme) Encode(obj runtime.Object, w io.Writer) error { 543 switch t := obj.(type) { 544 case *Unstructured: 545 return json.NewEncoder(w).Encode(t.Object) 546 case *UnstructuredList: 547 items := make([]map[string]interface{}, 0, len(t.Items)) 548 for _, i := range t.Items { 549 items = append(items, i.Object) 550 } 551 t.Object["items"] = items 552 defer func() { delete(t.Object, "items") }() 553 return json.NewEncoder(w).Encode(t.Object) 554 case *runtime.Unknown: 555 // TODO: Unstructured needs to deal with ContentType. 556 _, err := w.Write(t.Raw) 557 return err 558 default: 559 return json.NewEncoder(w).Encode(t) 560 } 561} 562 563func (s unstructuredJSONScheme) decode(data []byte) (runtime.Object, error) { 564 type detector struct { 565 Items gojson.RawMessage 566 } 567 var det detector 568 if err := json.Unmarshal(data, &det); err != nil { 569 return nil, err 570 } 571 572 if det.Items != nil { 573 list := &UnstructuredList{} 574 err := s.decodeToList(data, list) 575 return list, err 576 } 577 578 // No Items field, so it wasn't a list. 579 unstruct := &Unstructured{} 580 err := s.decodeToUnstructured(data, unstruct) 581 return unstruct, err 582} 583 584func (s unstructuredJSONScheme) decodeInto(data []byte, obj runtime.Object) error { 585 switch x := obj.(type) { 586 case *Unstructured: 587 return s.decodeToUnstructured(data, x) 588 case *UnstructuredList: 589 return s.decodeToList(data, x) 590 case *runtime.VersionedObjects: 591 o, err := s.decode(data) 592 if err == nil { 593 x.Objects = []runtime.Object{o} 594 } 595 return err 596 default: 597 return json.Unmarshal(data, x) 598 } 599} 600 601func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error { 602 m := make(map[string]interface{}) 603 if err := json.Unmarshal(data, &m); err != nil { 604 return err 605 } 606 607 unstruct.Object = m 608 609 return nil 610} 611 612func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList) error { 613 type decodeList struct { 614 Items []gojson.RawMessage 615 } 616 617 var dList decodeList 618 if err := json.Unmarshal(data, &dList); err != nil { 619 return err 620 } 621 622 if err := json.Unmarshal(data, &list.Object); err != nil { 623 return err 624 } 625 626 // For typed lists, e.g., a PodList, API server doesn't set each item's 627 // APIVersion and Kind. We need to set it. 628 listAPIVersion := list.GetAPIVersion() 629 listKind := list.GetKind() 630 itemKind := strings.TrimSuffix(listKind, "List") 631 632 delete(list.Object, "items") 633 list.Items = nil 634 for _, i := range dList.Items { 635 unstruct := &Unstructured{} 636 if err := s.decodeToUnstructured([]byte(i), unstruct); err != nil { 637 return err 638 } 639 // This is hacky. Set the item's Kind and APIVersion to those inferred 640 // from the List. 641 if len(unstruct.GetKind()) == 0 && len(unstruct.GetAPIVersion()) == 0 { 642 unstruct.SetKind(itemKind) 643 unstruct.SetAPIVersion(listAPIVersion) 644 } 645 list.Items = append(list.Items, unstruct) 646 } 647 return nil 648} 649 650// UnstructuredObjectConverter is an ObjectConverter for use with 651// Unstructured objects. Since it has no schema or type information, 652// it will only succeed for no-op conversions. This is provided as a 653// sane implementation for APIs that require an object converter. 654type UnstructuredObjectConverter struct{} 655 656func (UnstructuredObjectConverter) Convert(in, out, context interface{}) error { 657 unstructIn, ok := in.(*Unstructured) 658 if !ok { 659 return fmt.Errorf("input type %T in not valid for unstructured conversion", in) 660 } 661 662 unstructOut, ok := out.(*Unstructured) 663 if !ok { 664 return fmt.Errorf("output type %T in not valid for unstructured conversion", out) 665 } 666 667 // maybe deep copy the map? It is documented in the 668 // ObjectConverter interface that this function is not 669 // guaranteeed to not mutate the input. Or maybe set the input 670 // object to nil. 671 unstructOut.Object = unstructIn.Object 672 return nil 673} 674 675func (UnstructuredObjectConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) { 676 if kind := in.GetObjectKind().GroupVersionKind(); !kind.Empty() { 677 gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{kind}) 678 if !ok { 679 // TODO: should this be a typed error? 680 return nil, fmt.Errorf("%v is unstructured and is not suitable for converting to %q", kind, target) 681 } 682 in.GetObjectKind().SetGroupVersionKind(gvk) 683 } 684 return in, nil 685} 686 687func (UnstructuredObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) { 688 return "", "", errors.New("unstructured cannot convert field labels") 689} 690