github.com/replicatedcom/ship@v0.50.0/pkg/patch/patcher.go (about) 1 package patch 2 3 import ( 4 "encoding/json" 5 "path" 6 "path/filepath" 7 "reflect" 8 "strconv" 9 10 "github.com/ghodss/yaml" 11 "github.com/go-kit/kit/log" 12 "github.com/go-kit/kit/log/level" 13 "github.com/pkg/errors" 14 "github.com/spf13/afero" 15 yamlv3 "gopkg.in/yaml.v3" 16 "k8s.io/apimachinery/pkg/util/strategicpatch" 17 "k8s.io/client-go/kubernetes/scheme" 18 kustomizepatch "sigs.k8s.io/kustomize/pkg/patch" 19 k8stypes "sigs.k8s.io/kustomize/pkg/types" 20 21 "github.com/replicatedhq/ship/pkg/api" 22 "github.com/replicatedhq/ship/pkg/util" 23 ) 24 25 const PATCH_TOKEN = "TO_BE_MODIFIED" 26 27 const TempYamlPath = "temp.yaml" 28 29 type Patcher interface { 30 CreateTwoWayMergePatch(original, modified []byte) ([]byte, error) 31 MergePatches(original []byte, path []string, step api.Kustomize, resource string) ([]byte, error) 32 ApplyPatch(patch []byte, step api.Kustomize, resource string) ([]byte, error) 33 ModifyField(original []byte, path []string) ([]byte, error) 34 RunKustomize(kustomizationPath string) ([]byte, error) 35 } 36 37 type ShipPatcher struct { 38 Logger log.Logger 39 FS afero.Afero 40 } 41 42 func NewShipPatcher(logger log.Logger, fs afero.Afero) Patcher { 43 return &ShipPatcher{ 44 Logger: logger, 45 FS: fs, 46 } 47 } 48 49 func (p *ShipPatcher) writeHeaderToPatch(originalJSON, patchJSON []byte) ([]byte, error) { 50 original := map[string]interface{}{} 51 patch := map[string]interface{}{} 52 53 err := json.Unmarshal(originalJSON, &original) 54 if err != nil { 55 return nil, errors.Wrap(err, "unmarshal original json") 56 } 57 58 err = json.Unmarshal(patchJSON, &patch) 59 if err != nil { 60 return nil, errors.Wrap(err, "unmarshal patch json") 61 } 62 63 originalAPIVersion, ok := original["apiVersion"] 64 if !ok { 65 return nil, errors.New("no apiVersion key present in original") 66 } 67 68 originalKind, ok := original["kind"] 69 if !ok { 70 return nil, errors.New("no kind key present in original") 71 } 72 73 originalMetadata, ok := original["metadata"] 74 if !ok { 75 return nil, errors.New("no metadata key present in original") 76 } 77 78 patch["apiVersion"] = originalAPIVersion 79 patch["kind"] = originalKind 80 patch["metadata"] = originalMetadata 81 82 modifiedPatch, err := json.Marshal(patch) 83 if err != nil { 84 return nil, errors.Wrap(err, "marshal modified patch json") 85 } 86 87 return modifiedPatch, nil 88 } 89 90 func (p *ShipPatcher) CreateTwoWayMergePatch(original, modified []byte) ([]byte, error) { 91 debug := level.Debug(log.With(p.Logger, "struct", "patcher", "handler", "createTwoWayMergePatch")) 92 93 debug.Log("event", "convert.original") 94 originalJSON, err := yaml.YAMLToJSON(original) 95 if err != nil { 96 return nil, errors.Wrap(err, "convert original file to json") 97 } 98 99 debug.Log("event", "convert.modified") 100 modifiedJSON, err := yaml.YAMLToJSON(modified) 101 if err != nil { 102 return nil, errors.Wrap(err, "convert modified file to json") 103 } 104 105 debug.Log("event", "createKubeResource.original") 106 r, err := util.NewKubernetesResource(originalJSON) 107 if err != nil { 108 return nil, errors.Wrap(err, "create kube resource with original json") 109 } 110 111 versionedObj, err := scheme.Scheme.New(util.ToGroupVersionKind(r.Id().Gvk())) 112 if err != nil { 113 return nil, errors.Wrap(err, "read group, version kind from kube resource") 114 } 115 116 patchBytes, err := strategicpatch.CreateTwoWayMergePatch(originalJSON, modifiedJSON, versionedObj) 117 if err != nil { 118 return nil, errors.Wrap(err, "create two way merge patch") 119 } 120 121 modifiedPatchJSON, err := p.writeHeaderToPatch(originalJSON, patchBytes) 122 if err != nil { 123 return nil, errors.Wrap(err, "write original header to patch") 124 } 125 126 patch, err := yaml.JSONToYAML(modifiedPatchJSON) 127 if err != nil { 128 return nil, errors.Wrap(err, "convert merge patch json to yaml") 129 } 130 131 return patch, nil 132 } 133 134 func (p *ShipPatcher) MergePatches(original []byte, path []string, step api.Kustomize, resource string) ([]byte, error) { 135 debug := level.Debug(log.With(p.Logger, "struct", "patcher", "handler", "mergePatches")) 136 137 debug.Log("event", "applyPatch") 138 modified, err := p.ApplyPatch(original, step, resource) 139 if err != nil { 140 return nil, errors.Wrap(err, "apply patch") 141 } 142 143 debug.Log("event", "modifyField") 144 dirtied, err := p.ModifyField(modified, path) 145 if err != nil { 146 return nil, errors.Wrap(err, "dirty modified") 147 } 148 149 debug.Log("event", "readOriginal") 150 originalYaml, err := p.FS.ReadFile(resource) 151 if err != nil { 152 return nil, errors.Wrap(err, "read original yaml") 153 } 154 155 debug.Log("event", "createNewPatch") 156 finalPatch, err := p.CreateTwoWayMergePatch(originalYaml, dirtied) 157 if err != nil { 158 return nil, errors.Wrap(err, "create patch") 159 } 160 161 return finalPatch, nil 162 } 163 164 func (p *ShipPatcher) ApplyPatch(patch []byte, step api.Kustomize, resource string) ([]byte, error) { 165 debug := level.Debug(log.With(p.Logger, "struct", "patcher", "handler", "applyPatch")) 166 167 defer p.deleteTempKustomization(step) 168 169 if err := p.FS.MkdirAll(step.TempRenderPath(), 0777); err != nil { 170 return nil, errors.Wrap(err, "ensure temp patch overlay dir exists") 171 } 172 173 debug.Log("event", "writeFile.tempPatch") 174 if err := p.FS.WriteFile(path.Join(step.TempRenderPath(), TempYamlPath), patch, 0755); err != nil { 175 return nil, errors.Wrap(err, "write temp patch overlay") 176 } 177 178 debug.Log("event", "relPath") 179 relativePathToResource, err := filepath.Rel(step.TempRenderPath(), resource) 180 if err != nil { 181 return nil, errors.Wrap(err, "failed to find relative path") 182 } 183 184 kustomizationYaml := k8stypes.Kustomization{ 185 Resources: []string{relativePathToResource}, 186 PatchesStrategicMerge: []kustomizepatch.StrategicMerge{TempYamlPath}, 187 } 188 189 kustomizationYamlBytes, err := yamlv3.Marshal(kustomizationYaml) 190 if err != nil { 191 return nil, errors.Wrap(err, "marshal kustomization yaml") 192 } 193 194 debug.Log("event", "writeFile.tempKustomizationYaml") 195 if err := p.FS.WriteFile(path.Join(step.TempRenderPath(), "kustomization.yaml"), kustomizationYamlBytes, 0755); err != nil { 196 return nil, errors.Wrap(err, "write temp kustomization yaml") 197 } 198 199 debug.Log("event", "run.kustomizeBuild") 200 merged, err := p.RunKustomize(step.TempRenderPath()) 201 if err != nil { 202 return nil, err 203 } 204 205 return merged, nil 206 } 207 208 func (p *ShipPatcher) deleteTempKustomization(step api.Kustomize) error { 209 debug := level.Debug(log.With(p.Logger, "struct", "patcher", "handler", "deleteTempKustomization")) 210 211 tempKustomizationPath := path.Join(step.TempRenderPath(), "kustomization.yaml") 212 213 debug.Log("event", "remove.tempKustomizationYaml") 214 err := p.FS.Remove(tempKustomizationPath) 215 if err != nil { 216 return errors.Wrap(err, "remove temp base kustomization.yaml") 217 } 218 219 err = p.FS.Remove(path.Join(step.TempRenderPath(), TempYamlPath)) 220 if err != nil { 221 return errors.Wrap(err, "remove temp patch yaml") 222 } 223 224 return nil 225 } 226 227 func (p *ShipPatcher) ModifyField(original []byte, path []string) ([]byte, error) { 228 debug := level.Debug(log.With(p.Logger, "struct", "patcher", "handler", "modifyField")) 229 230 originalMap := map[string]interface{}{} 231 232 debug.Log("event", "convert original yaml to json") 233 originalJSON, err := yaml.YAMLToJSON(original) 234 if err != nil { 235 return nil, errors.Wrap(err, "original yaml to json") 236 } 237 238 debug.Log("event", "unmarshal original yaml") 239 if err := json.Unmarshal(originalJSON, &originalMap); err != nil { 240 return nil, errors.Wrap(err, "unmarshal original yaml") 241 } 242 243 debug.Log("event", "modify field") 244 modified, err := p.modifyField(originalMap, []string{}, path) 245 if err != nil { 246 return nil, errors.Wrap(err, "error modifying value") 247 } 248 249 debug.Log("event", "marshal modified") 250 modifiedJSON, err := json.Marshal(modified) 251 if err != nil { 252 return nil, errors.Wrap(err, "marshal modified json") 253 } 254 255 debug.Log("event", "convert modified yaml to json") 256 modifiedYAML, err := yaml.JSONToYAML(modifiedJSON) 257 if err != nil { 258 return nil, errors.Wrap(err, "modified json to yaml") 259 } 260 261 return modifiedYAML, nil 262 } 263 264 func (p *ShipPatcher) modifyField(original interface{}, current []string, path []string) (interface{}, error) { 265 originalType := reflect.TypeOf(original) 266 if original == nil { 267 return nil, nil 268 } 269 switch originalType.Kind() { 270 case reflect.Map: 271 typedOriginal, ok := original.(map[string]interface{}) 272 modifiedMap := make(map[string]interface{}) 273 if !ok { 274 return nil, errors.New("error asserting map") 275 } 276 for key, value := range typedOriginal { 277 modifiedValue, err := p.modifyField(value, append(current, key), path) 278 if err != nil { 279 return nil, err 280 } 281 modifiedMap[key] = modifiedValue 282 } 283 return modifiedMap, nil 284 case reflect.Slice: 285 typedOriginal, ok := original.([]interface{}) 286 modifiedSlice := make([]interface{}, len(typedOriginal)) 287 if !ok { 288 return nil, errors.New("error asserting slice") 289 } 290 for key, value := range typedOriginal { 291 modifiedValue, err := p.modifyField(value, append(current, strconv.Itoa(key)), path) 292 if err != nil { 293 return nil, err 294 } 295 modifiedSlice[key] = modifiedValue 296 } 297 return modifiedSlice, nil 298 default: 299 for idx := range path { 300 if current[idx] != path[idx] { 301 return original, nil 302 } 303 } 304 return PATCH_TOKEN, nil 305 } 306 }