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