11package cmd
22
33import (
4- "bytes"
5- "encoding/json"
64 "errors"
75 "fmt"
86 "log"
@@ -11,19 +9,9 @@ import (
119 "strconv"
1210 "strings"
1311
14- jsonpatch "github.com/evanphx/json-patch/v5"
15- jsoniterator "github.com/json-iterator/go"
1612 "github.com/spf13/cobra"
1713 "helm.sh/helm/v3/pkg/action"
1814 "helm.sh/helm/v3/pkg/cli"
19- "helm.sh/helm/v3/pkg/kube"
20- apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
21- apierrors "k8s.io/apimachinery/pkg/api/errors"
22- "k8s.io/apimachinery/pkg/runtime"
23- "k8s.io/apimachinery/pkg/types"
24- "k8s.io/apimachinery/pkg/util/strategicpatch"
25- "k8s.io/cli-runtime/pkg/resource"
26- "sigs.k8s.io/yaml"
2715
2816 "github.com/databus23/helm-diff/v3/diff"
2917 "github.com/databus23/helm-diff/v3/manifest"
@@ -117,7 +105,6 @@ perform.
117105`
118106
119107var envSettings = cli .New ()
120- var yamlSeperator = []byte ("\n ---\n " )
121108
122109func newChartCommand () * cobra.Command {
123110 diff := diffCmd {
@@ -308,15 +295,7 @@ func (d *diffCmd) runHelm3() error {
308295 if err := actionConfig .KubeClient .IsReachable (); err != nil {
309296 return err
310297 }
311- original , err := actionConfig .KubeClient .Build (bytes .NewBuffer (releaseManifest ), false )
312- if err != nil {
313- return fmt .Errorf ("unable to build kubernetes objects from original release manifest: %w" , err )
314- }
315- target , err := actionConfig .KubeClient .Build (bytes .NewBuffer (installManifest ), false )
316- if err != nil {
317- return fmt .Errorf ("unable to build kubernetes objects from new release manifest: %w" , err )
318- }
319- releaseManifest , installManifest , err = genManifest (original , target )
298+ releaseManifest , installManifest , err = manifest .Generate (actionConfig , releaseManifest , installManifest )
320299 if err != nil {
321300 return fmt .Errorf ("unable to generate manifests: %w" , err )
322301 }
@@ -334,14 +313,14 @@ func (d *diffCmd) runHelm3() error {
334313 if d .includeTests {
335314 currentSpecs = manifest .Parse (string (releaseManifest ), d .namespace , d .normalizeManifests )
336315 } else {
337- currentSpecs = manifest .Parse (string (releaseManifest ), d .namespace , d .normalizeManifests , helm3TestHook , helm2TestSuccessHook )
316+ currentSpecs = manifest .Parse (string (releaseManifest ), d .namespace , d .normalizeManifests , manifest . Helm3TestHook , manifest . Helm2TestSuccessHook )
338317 }
339318 }
340319 var newSpecs map [string ]* manifest.MappingResult
341320 if d .includeTests {
342321 newSpecs = manifest .Parse (string (installManifest ), d .namespace , d .normalizeManifests )
343322 } else {
344- newSpecs = manifest .Parse (string (installManifest ), d .namespace , d .normalizeManifests , helm3TestHook , helm2TestSuccessHook )
323+ newSpecs = manifest .Parse (string (installManifest ), d .namespace , d .normalizeManifests , manifest . Helm3TestHook , manifest . Helm2TestSuccessHook )
345324 }
346325 seenAnyChanges := diff .Manifests (currentSpecs , newSpecs , & d .Options , os .Stdout )
347326
@@ -354,216 +333,3 @@ func (d *diffCmd) runHelm3() error {
354333
355334 return nil
356335}
357-
358- func genManifest (original , target kube.ResourceList ) ([]byte , []byte , error ) {
359- var err error
360- releaseManifest , installManifest := make ([]byte , 0 ), make ([]byte , 0 )
361-
362- // to be deleted
363- targetResources := make (map [string ]bool )
364- for _ , r := range target {
365- targetResources [objectKey (r )] = true
366- }
367- for _ , r := range original {
368- if ! targetResources [objectKey (r )] {
369- out , _ := yaml .Marshal (r .Object )
370- releaseManifest = append (releaseManifest , yamlSeperator ... )
371- releaseManifest = append (releaseManifest , out ... )
372- }
373- }
374-
375- existingResources := make (map [string ]bool )
376- for _ , r := range original {
377- existingResources [objectKey (r )] = true
378- }
379-
380- var toBeCreated kube.ResourceList
381- for _ , r := range target {
382- if ! existingResources [objectKey (r )] {
383- toBeCreated = append (toBeCreated , r )
384- }
385- }
386-
387- toBeUpdated , err := existingResourceConflict (toBeCreated )
388- if err != nil {
389- return nil , nil , fmt .Errorf ("rendered manifests contain a resource that already exists. Unable to continue with update: %w" , err )
390- }
391-
392- _ = toBeUpdated .Visit (func (r * resource.Info , err error ) error {
393- if err != nil {
394- return err
395- }
396- original .Append (r )
397- return nil
398- })
399-
400- err = target .Visit (func (info * resource.Info , err error ) error {
401- if err != nil {
402- return err
403- }
404- kind := info .Mapping .GroupVersionKind .Kind
405-
406- // Fetch the current object for the three way merge
407- helper := resource .NewHelper (info .Client , info .Mapping )
408- currentObj , err := helper .Get (info .Namespace , info .Name )
409- if err != nil {
410- if ! apierrors .IsNotFound (err ) {
411- return fmt .Errorf ("could not get information about the resource: %w" , err )
412- }
413- // to be created
414- out , _ := yaml .Marshal (info .Object )
415- installManifest = append (installManifest , yamlSeperator ... )
416- installManifest = append (installManifest , out ... )
417- return nil
418- }
419- // to be updated
420- out , _ := jsoniterator .ConfigCompatibleWithStandardLibrary .Marshal (currentObj )
421- pruneObj , err := deleteStatusAndTidyMetadata (out )
422- if err != nil {
423- return fmt .Errorf ("prune current obj %q with kind %s: %w" , info .Name , kind , err )
424- }
425- pruneOut , err := yaml .Marshal (pruneObj )
426- if err != nil {
427- return fmt .Errorf ("prune current out %q with kind %s: %w" , info .Name , kind , err )
428- }
429- releaseManifest = append (releaseManifest , yamlSeperator ... )
430- releaseManifest = append (releaseManifest , pruneOut ... )
431-
432- originalInfo := original .Get (info )
433- if originalInfo == nil {
434- return fmt .Errorf ("could not find %q" , info .Name )
435- }
436-
437- patch , patchType , err := createPatch (originalInfo .Object , currentObj , info )
438- if err != nil {
439- return err
440- }
441-
442- helper .ServerDryRun = true
443- targetObj , err := helper .Patch (info .Namespace , info .Name , patchType , patch , nil )
444- if err != nil {
445- return fmt .Errorf ("cannot patch %q with kind %s: %w" , info .Name , kind , err )
446- }
447- out , _ = jsoniterator .ConfigCompatibleWithStandardLibrary .Marshal (targetObj )
448- pruneObj , err = deleteStatusAndTidyMetadata (out )
449- if err != nil {
450- return fmt .Errorf ("prune current obj %q with kind %s: %w" , info .Name , kind , err )
451- }
452- pruneOut , err = yaml .Marshal (pruneObj )
453- if err != nil {
454- return fmt .Errorf ("prune current out %q with kind %s: %w" , info .Name , kind , err )
455- }
456- installManifest = append (installManifest , yamlSeperator ... )
457- installManifest = append (installManifest , pruneOut ... )
458- return nil
459- })
460-
461- return releaseManifest , installManifest , err
462- }
463-
464- func createPatch (originalObj , currentObj runtime.Object , target * resource.Info ) ([]byte , types.PatchType , error ) {
465- oldData , err := json .Marshal (originalObj )
466- if err != nil {
467- return nil , types .StrategicMergePatchType , fmt .Errorf ("serializing current configuration: %w" , err )
468- }
469- newData , err := json .Marshal (target .Object )
470- if err != nil {
471- return nil , types .StrategicMergePatchType , fmt .Errorf ("serializing target configuration: %w" , err )
472- }
473-
474- // Even if currentObj is nil (because it was not found), it will marshal just fine
475- currentData , err := json .Marshal (currentObj )
476- if err != nil {
477- return nil , types .StrategicMergePatchType , fmt .Errorf ("serializing live configuration: %w" , err )
478- }
479- // kind := target.Mapping.GroupVersionKind.Kind
480- // if kind == "Deployment" {
481- // curr, _ := yaml.Marshal(currentObj)
482- // fmt.Println(string(curr))
483- // }
484-
485- // Get a versioned object
486- versionedObject := kube .AsVersioned (target )
487-
488- // Unstructured objects, such as CRDs, may not have an not registered error
489- // returned from ConvertToVersion. Anything that's unstructured should
490- // use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported
491- // on objects like CRDs.
492- _ , isUnstructured := versionedObject .(runtime.Unstructured )
493-
494- // On newer K8s versions, CRDs aren't unstructured but has this dedicated type
495- _ , isCRD := versionedObject .(* apiextv1.CustomResourceDefinition )
496-
497- if isUnstructured || isCRD {
498- // fall back to generic JSON merge patch
499- patch , err := jsonpatch .CreateMergePatch (oldData , newData )
500- return patch , types .MergePatchType , err
501- }
502-
503- patchMeta , err := strategicpatch .NewPatchMetaFromStruct (versionedObject )
504- if err != nil {
505- return nil , types .StrategicMergePatchType , fmt .Errorf ("unable to create patch metadata from object: %w" , err )
506- }
507-
508- patch , err := strategicpatch .CreateThreeWayMergePatch (oldData , newData , currentData , patchMeta , true )
509- return patch , types .StrategicMergePatchType , err
510- }
511-
512- func objectKey (r * resource.Info ) string {
513- gvk := r .Object .GetObjectKind ().GroupVersionKind ()
514- return fmt .Sprintf ("%s/%s/%s/%s" , gvk .GroupVersion ().String (), gvk .Kind , r .Namespace , r .Name )
515- }
516-
517- func existingResourceConflict (resources kube.ResourceList ) (kube.ResourceList , error ) {
518- var requireUpdate kube.ResourceList
519-
520- err := resources .Visit (func (info * resource.Info , err error ) error {
521- if err != nil {
522- return err
523- }
524-
525- helper := resource .NewHelper (info .Client , info .Mapping )
526- _ , err = helper .Get (info .Namespace , info .Name )
527- if err != nil {
528- if apierrors .IsNotFound (err ) {
529- return nil
530- }
531- return fmt .Errorf ("could not get information about the resource: %w" , err )
532- }
533-
534- requireUpdate .Append (info )
535- return nil
536- })
537-
538- return requireUpdate , err
539- }
540-
541- func deleteStatusAndTidyMetadata (obj []byte ) (map [string ]interface {}, error ) {
542- var objectMap map [string ]interface {}
543- err := jsoniterator .Unmarshal (obj , & objectMap )
544- if err != nil {
545- return nil , fmt .Errorf ("could not unmarshal byte sequence: %w" , err )
546- }
547-
548- delete (objectMap , "status" )
549-
550- metadata := objectMap ["metadata" ].(map [string ]interface {})
551-
552- delete (metadata , "managedFields" )
553- delete (metadata , "generation" )
554-
555- // See the below for the goal of this metadata tidy logic.
556- // https://github.com/databus23/helm-diff/issues/326#issuecomment-1008253274
557- if a := metadata ["annotations" ]; a != nil {
558- annotations := a .(map [string ]interface {})
559- delete (annotations , "meta.helm.sh/release-name" )
560- delete (annotations , "meta.helm.sh/release-namespace" )
561- delete (annotations , "deployment.kubernetes.io/revision" )
562-
563- if len (annotations ) == 0 {
564- delete (metadata , "annotations" )
565- }
566- }
567-
568- return objectMap , nil
569- }
0 commit comments