github.com/banzaicloud/operator-tools@v0.28.10/pkg/resources/overlay.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  	"emperror.dev/errors"
    19  	ypatch "github.com/cppforlife/go-patch/patch"
    20  	"gopkg.in/yaml.v2"
    21  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    22  	"k8s.io/apimachinery/pkg/runtime"
    23  	"k8s.io/apimachinery/pkg/runtime/schema"
    24  	k8syaml "sigs.k8s.io/yaml"
    25  
    26  	"github.com/banzaicloud/operator-tools/pkg/types"
    27  	"github.com/banzaicloud/operator-tools/pkg/utils"
    28  )
    29  
    30  type GroupVersionKind struct {
    31  	Group   string `json:"group,omitempty"`
    32  	Version string `json:"version,omitempty"`
    33  	Kind    string `json:"kind,omitempty"`
    34  }
    35  
    36  // +kubebuilder:object:generate=true
    37  type K8SResourceOverlay struct {
    38  	GVK       *GroupVersionKind         `json:"groupVersionKind,omitempty"`
    39  	ObjectKey types.ObjectKey           `json:"objectKey,omitempty"`
    40  	Patches   []K8SResourceOverlayPatch `json:"patches,omitempty"`
    41  }
    42  
    43  // +kubebuilder:object:generate=true
    44  type OverlayPatchType string
    45  
    46  const (
    47  	ReplaceOverlayPatchType OverlayPatchType = "replace"
    48  	DeleteOverlayPatchType  OverlayPatchType = "remove"
    49  )
    50  
    51  // +kubebuilder:object:generate=true
    52  type K8SResourceOverlayPatch struct {
    53  	Type       OverlayPatchType `json:"type,omitempty"`
    54  	Path       *string          `json:"path,omitempty"`
    55  	Value      *string          `json:"value,omitempty"`
    56  	ParseValue bool             `json:"parseValue,omitempty"`
    57  }
    58  
    59  func PatchYAMLModifier(overlay K8SResourceOverlay, parser *ObjectParser) (ObjectModifierFunc, error) {
    60  	if len(overlay.Patches) == 0 {
    61  		return func(o runtime.Object) (runtime.Object, error) {
    62  			return o, nil
    63  		}, nil
    64  	}
    65  
    66  	var opsDefinitions []ypatch.OpDefinition
    67  	for _, patch := range overlay.Patches {
    68  		var value interface{}
    69  		if patch.ParseValue {
    70  			err := yaml.Unmarshal([]byte(utils.PointerToString(patch.Value)), &value)
    71  			if err != nil {
    72  				return nil, errors.WrapIf(err, "could not unmarshal value")
    73  			}
    74  		} else {
    75  			value = interface{}(patch.Value)
    76  		}
    77  
    78  		op := ypatch.OpDefinition{
    79  			Type: string(patch.Type),
    80  			Path: patch.Path,
    81  		}
    82  		if patch.Type == ReplaceOverlayPatchType {
    83  			op.Value = &value
    84  		}
    85  		opsDefinitions = append(opsDefinitions, op)
    86  	}
    87  
    88  	return func(o runtime.Object) (runtime.Object, error) {
    89  		var ok bool
    90  		var meta metav1.Object
    91  
    92  		if overlay.GVK != nil {
    93  			gvk := o.GetObjectKind().GroupVersionKind()
    94  			if overlay.GVK.Group != "" && overlay.GVK.Group != gvk.Group {
    95  				return o, nil
    96  			}
    97  			if overlay.GVK.Version != "" && overlay.GVK.Version != gvk.Version {
    98  				return o, nil
    99  			}
   100  			if overlay.GVK.Kind != "" && overlay.GVK.Kind != gvk.Kind {
   101  				return o, nil
   102  			}
   103  		}
   104  
   105  		if meta, ok = o.(metav1.Object); !ok {
   106  			return o, nil
   107  		}
   108  
   109  		if (overlay.ObjectKey.Name != "" && meta.GetName() != overlay.ObjectKey.Name) || (overlay.ObjectKey.Namespace != "" && meta.GetNamespace() != overlay.ObjectKey.Namespace) {
   110  			return o, nil
   111  		}
   112  
   113  		y, err := k8syaml.Marshal(o)
   114  		if err != nil {
   115  			return o, errors.WrapIf(err, "could not marshal runtime object")
   116  		}
   117  
   118  		ops, err := ypatch.NewOpsFromDefinitions(opsDefinitions)
   119  		if err != nil {
   120  			return o, errors.WrapIf(err, "could not init patch ops from definitions")
   121  		}
   122  
   123  		var in interface{}
   124  		err = yaml.Unmarshal(y, &in)
   125  		if err != nil {
   126  			return o, errors.WrapIf(err, "could not unmarshal resource yaml")
   127  		}
   128  
   129  		res, err := ops.Apply(in)
   130  		if err != nil {
   131  			return o, errors.WrapIf(err, "could not apply patch ops")
   132  		}
   133  
   134  		y, err = yaml.Marshal(res)
   135  		if err != nil {
   136  			return o, errors.WrapIf(err, "could not marshal patched object to yaml")
   137  		}
   138  
   139  		o, err = parser.ParseYAMLToK8sObject(y)
   140  		if err != nil {
   141  			return o, errors.WrapIf(err, "could not parse runtime object from yaml")
   142  		}
   143  
   144  		return o, nil
   145  	}, nil
   146  }
   147  
   148  func ConvertGVK(gvk schema.GroupVersionKind) GroupVersionKind {
   149  	return GroupVersionKind{
   150  		Group:   gvk.Group,
   151  		Version: gvk.Version,
   152  		Kind:    gvk.Kind,
   153  	}
   154  }