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  }