github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/kustomize/patch.go (about)

     1  package kustomize
     2  
     3  import (
     4  	"encoding/json"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/go-kit/kit/log"
    11  	"github.com/go-kit/kit/log/level"
    12  	"github.com/pkg/errors"
    13  	yaml "gopkg.in/yaml.v3"
    14  	"k8s.io/client-go/kubernetes/scheme"
    15  	kustomizepatch "sigs.k8s.io/kustomize/pkg/patch"
    16  	k8stypes "sigs.k8s.io/kustomize/pkg/types"
    17  
    18  	"github.com/replicatedhq/ship/pkg/api"
    19  	"github.com/replicatedhq/ship/pkg/constants"
    20  	"github.com/replicatedhq/ship/pkg/util"
    21  )
    22  
    23  type k8sMetadataLabelsOnly struct {
    24  	Metadata struct {
    25  		Labels map[string]interface{} `yaml:"labels"`
    26  	} `yaml:"metadata"`
    27  }
    28  
    29  type patchOperation struct {
    30  	Op        string `json:"op"`
    31  	Path      string `json:"path"`
    32  	Value     string `json:"value,omitempty"`
    33  	writePath string
    34  }
    35  
    36  var (
    37  	removeHeritagePatch = patchOperation{
    38  		Op:        "remove",
    39  		Path:      "/metadata/labels/heritage",
    40  		writePath: "heritage-patch.json",
    41  	}
    42  	removeChartPatch = patchOperation{
    43  		Op:        "remove",
    44  		Path:      "/metadata/labels/chart",
    45  		writePath: "chart-patch.json",
    46  	}
    47  )
    48  
    49  // generateTillerPatches writes a kustomization.yaml including JSON6902 patches to remove
    50  // the chart and heritage metadata labels.
    51  func (l *Kustomizer) generateTillerPatches(step api.Kustomize) error {
    52  	debug := level.Debug(log.With(l.Logger, "struct", "kustomizer", "handler", "generateTillerPatches"))
    53  
    54  	debug.Log("event", "mkdir.DefaultOverlaysPath")
    55  	if err := l.FS.MkdirAll(constants.DefaultOverlaysPath, 0755); err != nil {
    56  		return errors.Wrapf(err, "create default overlays path at %s", constants.DefaultOverlaysPath)
    57  	}
    58  
    59  	defaultPatches := []patchOperation{removeChartPatch, removeHeritagePatch}
    60  	for idx, defaultPatch := range defaultPatches {
    61  		defaultPatchAsSlice := []patchOperation{defaultPatch}
    62  
    63  		patchesB, err := json.Marshal(defaultPatchAsSlice)
    64  		if err != nil {
    65  			return errors.Wrapf(err, "marshal default patch idx %d", idx)
    66  		}
    67  
    68  		if err := l.FS.WriteFile(path.Join(constants.DefaultOverlaysPath, defaultPatch.writePath), patchesB, 0755); err != nil {
    69  			return errors.Wrapf(err, "write default patch idx %d", idx)
    70  		}
    71  	}
    72  
    73  	relativePathToBases, err := filepath.Rel(constants.DefaultOverlaysPath, step.Base)
    74  	if err != nil {
    75  		return errors.Wrap(err, "relative path to bases")
    76  	}
    77  
    78  	var excludedBases []string
    79  	state, err := l.State.CachedState()
    80  	if err != nil {
    81  		return errors.Wrap(err, "load state")
    82  	}
    83  	if state.V1 != nil && state.CurrentKustomize() != nil {
    84  		excludedBases = state.CurrentKustomize().Ship().ExcludedBases
    85  	}
    86  
    87  	json6902Patches := []kustomizepatch.Json6902{}
    88  	if err := l.FS.Walk(
    89  		step.Base,
    90  		func(targetPath string, info os.FileInfo, err error) error {
    91  			if err != nil {
    92  				debug.Log("event", "walk.fail", "path", targetPath)
    93  				return errors.Wrap(err, "walk path")
    94  			}
    95  
    96  			// this ignores non-k8s resources and things included in the list of excluded bases
    97  			if !l.shouldAddFileToBase(step.Base, excludedBases, targetPath) {
    98  				return nil
    99  			}
   100  
   101  			fileB, err := l.FS.ReadFile(targetPath)
   102  			if err != nil {
   103  				return errors.Wrapf(err, "read file %s", targetPath)
   104  			}
   105  
   106  			resource, err := util.NewKubernetesResource(fileB)
   107  			if err != nil {
   108  				// Ignore all non-k8s resources
   109  				return nil
   110  			}
   111  
   112  			if _, err := scheme.Scheme.New(util.ToGroupVersionKind(resource.Id().Gvk())); err != nil {
   113  				// Ignore all non-k8s resources
   114  				return nil
   115  			}
   116  
   117  			fileMetadataOnly := k8sMetadataLabelsOnly{}
   118  			if err := yaml.Unmarshal(fileB, &fileMetadataOnly); err != nil {
   119  				return errors.Wrap(err, "unmarshal k8s metadata only")
   120  			}
   121  
   122  			for _, excluded := range excludedBases {
   123  				if info.Name() == excluded {
   124  					// don't add this to defaultPatches
   125  					debug.Log("skipping default patches", info.Name())
   126  					return nil
   127  				}
   128  			}
   129  
   130  			for _, defaultPatch := range defaultPatches {
   131  				splitDefaultPath := strings.Split(defaultPatch.Path, "/")
   132  				patchLabel := splitDefaultPath[len(splitDefaultPath)-1]
   133  
   134  				if l.hasMetadataLabel(patchLabel, fileMetadataOnly) {
   135  					json6902Patches = append(json6902Patches, kustomizepatch.Json6902{
   136  						Target: &kustomizepatch.Target{
   137  							Gvk:       resource.GetGvk(),
   138  							Namespace: resource.Id().Namespace(),
   139  							Name:      resource.GetName(),
   140  						},
   141  						Path: defaultPatch.writePath,
   142  					})
   143  				}
   144  			}
   145  
   146  			return nil
   147  		},
   148  	); err != nil {
   149  		return err
   150  	}
   151  
   152  	kustomizationYaml := k8stypes.Kustomization{
   153  		Bases:           []string{relativePathToBases},
   154  		PatchesJson6902: json6902Patches,
   155  	}
   156  
   157  	kustomizationYamlB, err := util.MarshalIndent(2, kustomizationYaml)
   158  	if err != nil {
   159  		return errors.Wrap(err, "marshal kustomization yaml")
   160  	}
   161  
   162  	debug.Log("event", "writeFile.kustomization")
   163  	if err := l.FS.WriteFile(path.Join(constants.DefaultOverlaysPath, "kustomization.yaml"), kustomizationYamlB, 0755); err != nil {
   164  		return errors.Wrap(err, "write temp kustomization")
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  func (l *Kustomizer) hasMetadataLabel(label string, k8sFile k8sMetadataLabelsOnly) bool {
   171  	if k8sFile.Metadata.Labels == nil {
   172  		return false
   173  	}
   174  
   175  	return k8sFile.Metadata.Labels[label] != nil
   176  }