github.com/banzaicloud/operator-tools@v0.28.10/pkg/resources/object.go (about) 1 // Copyright © 2020 Banzai Cloud 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package resources 16 17 import ( 18 "bufio" 19 "bytes" 20 "strings" 21 22 "emperror.dev/errors" 23 "k8s.io/apimachinery/pkg/api/meta" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apimachinery/pkg/runtime/serializer/json" 28 k8syaml "k8s.io/apimachinery/pkg/util/yaml" 29 30 "github.com/banzaicloud/operator-tools/pkg/logger" 31 ) 32 33 var log = logger.Log 34 35 const ( 36 // YAMLSeparator is a separator for multi-document YAML files. 37 YAMLSeparator = "---" 38 ) 39 40 type Objects []runtime.Object 41 42 // ToMap returns a map of K8sObject hash to K8sObject. 43 func (os Objects) ToMap() map[string]runtime.Object { 44 ret := make(map[string]runtime.Object) 45 for _, oo := range os { 46 if isValid(oo) { 47 ret[GetHash(oo)] = oo 48 } 49 } 50 return ret 51 } 52 53 func GetHash(o runtime.Object) string { 54 var name, namespace string 55 if m, ok := o.(interface { 56 GetName() string 57 GetNamespace() string 58 }); ok { 59 name = m.GetName() 60 namespace = m.GetNamespace() 61 } 62 63 return strings.Join([]string{o.GetObjectKind().GroupVersionKind().String(), namespace, name}, ":") 64 } 65 66 // Valid checks returns true if Kind and ComponentName of K8sObject are both not empty. 67 func isValid(o runtime.Object) bool { 68 if m, ok := o.(metav1.Object); ok { 69 if o.GetObjectKind().GroupVersionKind().Kind == "" || m.GetName() == "" { 70 return false 71 } 72 73 return true 74 } 75 76 return false 77 } 78 79 type ObjectParser struct { 80 scheme *runtime.Scheme 81 } 82 83 func NewObjectParser(scheme *runtime.Scheme) *ObjectParser { 84 return &ObjectParser{ 85 scheme: scheme, 86 } 87 } 88 89 func (p *ObjectParser) ParseYAMLManifest(manifest string, modifiers ...YAMLModifierFuncs) ([]runtime.Object, error) { 90 var b bytes.Buffer 91 92 var yamls []string 93 scanner := bufio.NewScanner(strings.NewReader(manifest)) 94 for scanner.Scan() { 95 line := scanner.Text() 96 if strings.TrimSpace(line) == YAMLSeparator { 97 yamls = append(yamls, b.String()) 98 b.Reset() 99 } else { 100 if _, err := b.WriteString(line); err != nil { 101 return nil, err 102 } 103 if _, err := b.WriteString("\n"); err != nil { 104 return nil, err 105 } 106 } 107 } 108 yamls = append(yamls, b.String()) 109 110 var objects []runtime.Object 111 112 for _, yaml := range yamls { 113 yaml = p.removeNonYAMLLines(yaml) 114 if yaml == "" { 115 continue 116 } 117 o, err := p.ParseYAMLToK8sObject([]byte(yaml), modifiers...) 118 if err != nil { 119 log.Error(err, "failed to parse YAML to a k8s object") 120 continue 121 } 122 123 objects = append(objects, o) 124 } 125 126 return objects, nil 127 } 128 129 func (p *ObjectParser) ParseYAMLToK8sObject(yaml []byte, yamlModifiers ...YAMLModifierFuncs) (runtime.Object, error) { 130 for _, modifierFunc := range yamlModifiers { 131 yaml = modifierFunc(yaml) 132 } 133 134 if p.scheme != nil { 135 s := json.NewYAMLSerializer(json.DefaultMetaFactory, p.scheme, p.scheme) 136 o, _, err := s.Decode(yaml, nil, nil) 137 if err == nil { 138 return o, nil 139 } 140 } 141 142 r := bytes.NewReader(yaml) 143 decoder := k8syaml.NewYAMLOrJSONDecoder(r, 1024) 144 145 out := &unstructured.Unstructured{} 146 err := decoder.Decode(out) 147 if err != nil { 148 return nil, errors.WrapIf(err, "error decoding object as unstructured") 149 } 150 return out, nil 151 } 152 153 func (p *ObjectParser) removeNonYAMLLines(yms string) string { 154 out := "" 155 for _, s := range strings.Split(yms, "\n") { 156 if strings.HasPrefix(s, "#") { 157 continue 158 } 159 out += s + "\n" 160 } 161 162 return strings.TrimSpace(out) 163 } 164 165 // IsObjectBeingDeleted returns true, if the given object is being deleted with finalizers still 166 // existing on it (this is the only case when deleteion timestamp is non-zero) 167 func IsObjectBeingDeleted(object runtime.Object) (bool, error) { 168 objMeta, err := meta.Accessor(object) 169 if err != nil { 170 return false, err 171 } 172 173 return !objMeta.GetDeletionTimestamp().IsZero(), nil 174 }