1
1
package cmd
2
2
3
3
import (
4
- "bytes"
5
- "encoding/json"
6
4
"errors"
7
5
"fmt"
8
6
"log"
@@ -11,19 +9,9 @@ import (
11
9
"strconv"
12
10
"strings"
13
11
14
- jsonpatch "github.com/evanphx/json-patch/v5"
15
- jsoniterator "github.com/json-iterator/go"
16
12
"github.com/spf13/cobra"
17
13
"helm.sh/helm/v3/pkg/action"
18
14
"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"
27
15
28
16
"github.com/databus23/helm-diff/v3/diff"
29
17
"github.com/databus23/helm-diff/v3/manifest"
@@ -117,7 +105,6 @@ perform.
117
105
`
118
106
119
107
var envSettings = cli .New ()
120
- var yamlSeperator = []byte ("\n ---\n " )
121
108
122
109
func newChartCommand () * cobra.Command {
123
110
diff := diffCmd {
@@ -308,15 +295,7 @@ func (d *diffCmd) runHelm3() error {
308
295
if err := actionConfig .KubeClient .IsReachable (); err != nil {
309
296
return err
310
297
}
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 )
320
299
if err != nil {
321
300
return fmt .Errorf ("unable to generate manifests: %w" , err )
322
301
}
@@ -334,14 +313,14 @@ func (d *diffCmd) runHelm3() error {
334
313
if d .includeTests {
335
314
currentSpecs = manifest .Parse (string (releaseManifest ), d .namespace , d .normalizeManifests )
336
315
} 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 )
338
317
}
339
318
}
340
319
var newSpecs map [string ]* manifest.MappingResult
341
320
if d .includeTests {
342
321
newSpecs = manifest .Parse (string (installManifest ), d .namespace , d .normalizeManifests )
343
322
} 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 )
345
324
}
346
325
seenAnyChanges := diff .Manifests (currentSpecs , newSpecs , & d .Options , os .Stdout )
347
326
@@ -354,216 +333,3 @@ func (d *diffCmd) runHelm3() error {
354
333
355
334
return nil
356
335
}
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