@@ -30,7 +30,6 @@ import (
30
30
31
31
"golang.org/x/mod/semver"
32
32
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33
- "sigs.k8s.io/kustomize/kyaml/fn/framework"
34
33
k8syaml "sigs.k8s.io/yaml"
35
34
36
35
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
@@ -52,8 +51,9 @@ const (
52
51
kptFnAnnotation = "config.kubernetes.io/function"
53
52
kptFnLocalConfig = "config.kubernetes.io/local-config"
54
53
55
- kptDownloadLink = "https://googlecontainertools.github.io/kpt/installation/"
56
- kptMinVersion = "0.34.0"
54
+ kptDownloadLink = "https://googlecontainertools.github.io/kpt/installation/"
55
+ kptMinVersionInclusive = "v0.38.1"
56
+ kptMaxVersionExclusive = "v1.0.0"
57
57
58
58
kustomizeDownloadLink = "https://kubernetes-sigs.github.io/kustomize/installation/"
59
59
kustomizeMinVersion = "v3.2.3"
@@ -67,6 +67,7 @@ type Deployer struct {
67
67
insecureRegistries map [string ]bool
68
68
labels map [string ]string
69
69
globalConfig string
70
+ hasKustomization func (string ) bool
70
71
kubeContext string
71
72
kubeConfig string
72
73
namespace string
@@ -83,6 +84,7 @@ func NewDeployer(cfg Config, labels map[string]string, d *latestV1.KptDeploy) *D
83
84
insecureRegistries : cfg .GetInsecureRegistries (),
84
85
labels : labels ,
85
86
globalConfig : cfg .GlobalConfig (),
87
+ hasKustomization : hasKustomization ,
86
88
kubeContext : cfg .GetKubeContext (),
87
89
kubeConfig : cfg .GetKubeConfig (),
88
90
namespace : cfg .GetKubeNamespace (),
@@ -99,22 +101,23 @@ func versionCheck(dir string, stdout io.Writer) error {
99
101
return fmt .Errorf ("kpt is not installed yet\n See kpt installation: %v" ,
100
102
kptDownloadLink )
101
103
}
102
- version := strings .TrimSuffix (string (out ), "\n " )
103
104
// kpt follows semver but does not have "v" prefix.
104
- if ! semver .IsValid ("v" + version ) {
105
- return fmt .Errorf ("unknown kpt version %v\n Please upgrade your " +
106
- "local kpt CLI to a version >= %v\n See kpt installation: %v" ,
107
- string (out ), kptMinVersion , kptDownloadLink )
105
+ version := "v" + strings .TrimSuffix (string (out ), "\n " )
106
+ if ! semver .IsValid (version ) {
107
+ return fmt .Errorf ("unknown kpt version %v\n Please install " +
108
+ "kpt %v <= version < %v\n See kpt installation: %v" ,
109
+ string (out ), kptMinVersionInclusive , kptMaxVersionExclusive , kptDownloadLink )
108
110
}
109
- if semver .Compare ("v" + version , "v" + kptMinVersion ) < 0 {
110
- return fmt .Errorf ("you are using kpt %q\n Please update your kpt version to" +
111
- " >= %v\n See kpt installation: %v" , version [0 ], kptMinVersion , kptDownloadLink )
111
+ if semver .Compare (version , kptMinVersionInclusive ) < 0 ||
112
+ semver .Compare (version , kptMaxVersionExclusive ) >= 0 {
113
+ return fmt .Errorf ("you are using kpt %q\n Please install " +
114
+ "kpt %v <= version < %v\n See kpt installation: %v" ,
115
+ version , kptMinVersionInclusive , kptMaxVersionExclusive , kptDownloadLink )
112
116
}
113
117
114
118
// Users can choose not to use kustomize in kpt deployer mode. We only check the kustomize
115
119
// version when kustomization.yaml config is directed under .deploy.kpt.dir path.
116
- _ , err = kustomize .FindKustomizationConfig (dir )
117
- if err == nil {
120
+ if hasKustomization (dir ) {
118
121
kustomizeCmd := exec .Command ("kustomize" , "version" )
119
122
out , err := util .RunCmdOut (kustomizeCmd )
120
123
if err != nil {
@@ -154,16 +157,8 @@ func (k *Deployer) Deploy(ctx context.Context, out io.Writer, builds []graph.Art
154
157
}
155
158
endTrace ()
156
159
157
- _ , endTrace = instrumentation .StartTrace (ctx , "Deploy_getKptFnRunArgs" )
158
- flags , err := k .getKptFnRunArgs ()
159
- if err != nil {
160
- endTrace (instrumentation .TraceEndError (err ))
161
- return []string {}, err
162
- }
163
- endTrace ()
164
-
165
160
childCtx , endTrace := instrumentation .StartTrace (ctx , "Deploy_renderManifests" )
166
- manifests , err := k .renderManifests (childCtx , out , builds , flags )
161
+ manifests , err := k .renderManifests (childCtx , builds )
167
162
if err != nil {
168
163
endTrace (instrumentation .TraceEndError (err ))
169
164
return nil , err
@@ -191,7 +186,9 @@ func (k *Deployer) Deploy(ctx context.Context, out io.Writer, builds []graph.Art
191
186
endTrace ()
192
187
193
188
_ , endTrace = instrumentation .StartTrace (ctx , "Deploy_manifest.Write" )
194
- manifest .Write (manifests .String (), filepath .Join (applyDir , "resources.yaml" ), out )
189
+ if err = sink (ctx , []byte (manifests .String ()), applyDir ); err != nil {
190
+ return nil , err
191
+ }
195
192
endTrace ()
196
193
197
194
childCtx , endTrace = instrumentation .StartTrace (ctx , "Deploy_execKptCommand" )
@@ -278,18 +275,9 @@ func (k *Deployer) Render(ctx context.Context, out io.Writer, builds []graph.Art
278
275
endTrace (instrumentation .TraceEndError (err ))
279
276
return err
280
277
}
281
- endTrace ()
282
-
283
- _ , endTrace = instrumentation .StartTrace (ctx , "Render_getKptFnRunArgs" )
284
- flags , err := k .getKptFnRunArgs ()
285
- if err != nil {
286
- endTrace (instrumentation .TraceEndError (err ))
287
- return err
288
- }
289
- endTrace ()
290
278
291
279
childCtx , endTrace := instrumentation .StartTrace (ctx , "Render_renderManifests" )
292
- manifests , err := k .renderManifests (childCtx , out , builds , flags )
280
+ manifests , err := k .renderManifests (childCtx , builds )
293
281
if err != nil {
294
282
endTrace (instrumentation .TraceEndError (err ))
295
283
return err
@@ -304,9 +292,13 @@ func (k *Deployer) Render(ctx context.Context, out io.Writer, builds []graph.Art
304
292
// renderManifests handles a majority of the hydration process for manifests.
305
293
// This involves reading configs from a source directory, running kustomize build, running kpt pipelines,
306
294
// adding image digests, and adding run-id labels.
307
- func (k * Deployer ) renderManifests (ctx context.Context , _ io.Writer , builds []graph.Artifact ,
308
- flags []string ) (manifest.ManifestList , error ) {
309
- var err error
295
+ func (k * Deployer ) renderManifests (ctx context.Context , builds []graph.Artifact ) (
296
+ manifest.ManifestList , error ) {
297
+ flags , err := k .getKptFnRunArgs ()
298
+ if err != nil {
299
+ return nil , err
300
+ }
301
+
310
302
debugHelpersRegistry , err := config .GetDebugHelpersRegistry (k .globalConfig )
311
303
if err != nil {
312
304
return nil , fmt .Errorf ("retrieving debug helpers registry: %w" , err )
@@ -317,112 +309,64 @@ func (k *Deployer) renderManifests(ctx context.Context, _ io.Writer, builds []gr
317
309
cmd := exec .CommandContext (
318
310
ctx , "kpt" , kptCommandArgs (k .Dir , []string {"fn" , "source" },
319
311
nil , nil )... )
320
- buf , err = util .RunCmdOut (cmd )
321
- if err != nil {
312
+ if buf , err = util .RunCmdOut (cmd ); err != nil {
322
313
return nil , fmt .Errorf ("reading config manifests: %w" , err )
323
314
}
324
315
325
- // A workaround for issue https://github.com/GoogleContainerTools/kpt/issues/1149
326
- // Problem: fn-path cannot be recognized in kpt pipeline mode, and it results in that
327
- // the kpt functions in are ignored.
328
- // Solution: pull kpt functions specifically from the kpt source inputs (getKptFunc) and
329
- // adds it back to the pipeline after kustomize build finishes (append kptFn).
330
- var kptFnBuf []byte
331
- if len (k .Fn .FnPath ) > 0 {
332
- cmd = exec .CommandContext (
333
- ctx , "kpt" , kptCommandArgs (k .Fn .FnPath , []string {"fn" , "source" },
334
- nil , nil )... )
335
- if kptFnBuf , err = util .RunCmdOut (cmd ); err != nil {
336
- return nil , fmt .Errorf ("kpt source the fn-path config %v" , err )
337
- }
338
- } else {
339
- kptFnBuf = buf
340
- }
341
- kptFn , err := k .getKptFunc (kptFnBuf )
342
- if err != nil {
343
- return nil , err
316
+ // Run kpt functions against the manifests read from source.
317
+ cmd = exec .CommandContext (ctx , "kpt" , kptCommandArgs ("" , []string {"fn" , "run" }, flags , nil )... )
318
+ cmd .Stdin = bytes .NewBuffer (buf )
319
+ if buf , err = util .RunCmdOut (cmd ); err != nil {
320
+ return nil , fmt .Errorf ("running kpt functions: %w" , err )
344
321
}
345
322
346
- // Hydrate the manifests source.
347
- // Note: kustomize cannot be used as a kpt fn yet and thus we add a kustomize cmd step in the kpt pipeline:
348
- // kpt source --> (workaround) kustomize build --> kpt run --> kpt sink.
349
- _ , err = kustomize .FindKustomizationConfig (k .Dir )
350
- // Only run kustomize if kustomization.yaml is found.
351
- if err == nil {
352
- // Note: A tmp dir is used to provide kustomize the manifest directory.
353
- // Once the unified kpt/kustomize is done, kustomize can be run as a kpt fn step and
354
- // this additional directory creation/deletion will no longer be needed.
355
- if err := os .RemoveAll (tmpKustomizeDir ); err != nil {
356
- return nil , fmt .Errorf ("removing %v:%w" , tmpKustomizeDir , err )
357
- }
358
- if err := os .MkdirAll (tmpKustomizeDir , os .ModePerm ); err != nil {
359
- return nil , err
360
- }
361
- defer func () {
362
- os .RemoveAll (tmpKustomizeDir )
363
- }()
364
-
365
- err = k .sink (ctx , buf , tmpKustomizeDir )
366
- if err != nil {
367
- return nil , err
323
+ // Run kustomize on the output from the kpt functions if a kustomization is found.
324
+ // Note: kustomize cannot be used as a kpt fn yet and thus we run kustomize in a temp dir
325
+ // in the kpt pipeline:
326
+ // kpt source --> kpt run --> (workaround if kustomization exists) kustomize build --> kpt sink.
327
+ //
328
+ // Note: Optimally the user would be able to control the order in which kpt functions and
329
+ // Kustomize build happens, and even have Kustomize build happen between Kpt fn invocations.
330
+ // However, since we currently don't expose an API supporting that level of control running
331
+ // Kustomize build last seems like the best option.
332
+ // Pros:
333
+ // - Kustomize will remove all comments which breaks any Kpt fn relying on YAML comments. This
334
+ // includes https://github.com/GoogleContainerTools/kpt-functions-catalog/tree/master/functions/go/apply-setters
335
+ // which is the upcoming replacement for kpt cfg set and it will likely receive wide usage.
336
+ // This is the main reason for Kustomize last approach winning.
337
+ // - Kustomize mangles the directory structure so any Kpt fn relying on th relative file
338
+ // location of a resource as described by the config.kubernetes.io/path annotation will break
339
+ // if run after Kustomize.
340
+ // - This allows Kpt fns to modify and even create Kustomizations.
341
+ // Cons:
342
+ // - Kpt fns that expects the output of kustomize build as input might not work as expected,
343
+ // especially if the Kustomization references resources outside of the kpt dir.
344
+ // - Kpt fn run chokes on JSON patch files because the root node is an array. This can be worked
345
+ // around by avoiding the file extensions kpt fn reads from for such files (.yaml, .yml and
346
+ // .json) or inlining the patch.
347
+ defer func () {
348
+ if err = os .RemoveAll (tmpKustomizeDir ); err != nil {
349
+ fmt .Printf ("Unable to delete temporary Kusomize directory: %v\n " , err )
368
350
}
351
+ }()
352
+ if err = sink (ctx , buf , tmpKustomizeDir ); err != nil {
353
+ return nil , err
354
+ }
369
355
370
- cmd := exec .CommandContext (ctx , "kustomize" , append ([]string {"build" }, tmpKustomizeDir )... )
371
- buf , err = util .RunCmdOut (cmd )
372
- if err != nil {
356
+ // Only run kustomize if kustomization.yaml is found in the output from the kpt functions.
357
+ if k .hasKustomization (tmpKustomizeDir ) {
358
+ cmd = exec .CommandContext (ctx , "kustomize" , append ([]string {"build" }, tmpKustomizeDir )... )
359
+ if buf , err = util .RunCmdOut (cmd ); err != nil {
373
360
return nil , fmt .Errorf ("kustomize build: %w" , err )
374
361
}
375
- } else {
376
- // Note: Here we use kpt ResourceList as the media to store the config source.
377
- // Eventually, skaffold should not need to construct a kpt inner resource but only use kpt commands and
378
- // the new Kptfile(v1) to establish a hydration pipeline.
379
- input := bytes .NewBufferString (string (buf ))
380
- rl := framework.ResourceList {
381
- Reader : input ,
382
- }
383
- // Manipulate the kustomize "Rnode"(Kustomize term) and pulls out the "Items"
384
- // from ResourceLists.
385
- if err := rl .Read (); err != nil {
386
- return nil , fmt .Errorf ("reading ResourceList %w" , err )
387
- }
388
-
389
- var newBuf []byte
390
- for i := range rl .Items {
391
- item , err := rl .Items [i ].String ()
392
- if err != nil {
393
- return nil , fmt .Errorf ("reading Item %w" , err )
394
- }
395
- newBuf = append (newBuf , []byte (item )... )
396
- }
397
- buf = newBuf
398
- }
399
-
400
- // Run kpt functions against the hydrated manifests.
401
- cmd = exec .CommandContext (ctx , "kpt" , kptCommandArgs ("" , []string {"fn" , "run" }, flags , nil )... )
402
- buf = append (buf , []byte ("---\n " )... )
403
- buf = append (buf , kptFn ... )
404
- cmd .Stdin = bytes .NewBuffer (buf )
405
- buf , err = util .RunCmdOut (cmd )
406
- if err != nil {
407
- return nil , fmt .Errorf ("running kpt functions: %w" , err )
408
362
}
409
363
410
364
// Store the manipulated manifests to the sink dir.
411
365
if k .Fn .SinkDir != "" {
412
- if err := os .RemoveAll (k .Fn .SinkDir ); err != nil {
413
- return nil , fmt .Errorf ("deleting sink directory %s: %w" , k .Fn .SinkDir , err )
414
- }
415
-
416
- if err := os .MkdirAll (k .Fn .SinkDir , os .ModePerm ); err != nil {
417
- return nil , fmt .Errorf ("creating sink directory %s: %w" , k .Fn .SinkDir , err )
418
- }
419
- err = k .sink (ctx , buf , k .Fn .SinkDir )
420
- if err != nil {
421
- return nil , fmt .Errorf ("sinking to directory %s: %w" , k .Fn .SinkDir , err )
366
+ if err = sink (ctx , buf , k .Fn .SinkDir ); err != nil {
367
+ return nil , err
422
368
}
423
- fmt .Printf (
424
- "Manipulated resources are flattended and stored in your sink directory: %v\n " ,
425
- k .Fn .SinkDir )
369
+ fmt .Printf ("Manipulated resources are stored in your sink directory: %v\n " , k .Fn .SinkDir )
426
370
}
427
371
428
372
var manifests manifest.ManifestList
@@ -445,43 +389,21 @@ func (k *Deployer) renderManifests(ctx context.Context, _ io.Writer, builds []gr
445
389
return manifests .SetLabels (k .labels )
446
390
}
447
391
448
- func (k * Deployer ) getKptFunc (buf []byte ) ([]byte , error ) {
449
- input := bytes .NewBufferString (string (buf ))
450
- rl := framework.ResourceList {
451
- Reader : input ,
392
+ func sink (ctx context.Context , buf []byte , sinkDir string ) error {
393
+ if err := os .RemoveAll (sinkDir ); err != nil {
394
+ return fmt .Errorf ("deleting sink directory %s: %w" , sinkDir , err )
452
395
}
453
- // Manipulate the kustomize "Rnode"(Kustomize term) and pulls out the "Items"
454
- // from ResourceLists.
455
- if err := rl .Read (); err != nil {
456
- return nil , fmt .Errorf ("reading ResourceList %w" , err )
457
- }
458
- var kptFn []byte
459
- for i := range rl .Items {
460
- item , err := rl .Items [i ].String ()
461
- if err != nil {
462
- return nil , fmt .Errorf ("reading Item %w" , err )
463
- }
464
- var obj unstructured.Unstructured
465
- jByte , err := k8syaml .YAMLToJSON ([]byte (item ))
466
- if err != nil {
467
- continue
468
- }
469
- if err := obj .UnmarshalJSON (jByte ); err != nil {
470
- continue
471
- }
472
- // Found kpt fn.
473
- if _ , ok := obj .GetAnnotations ()[kptFnAnnotation ]; ok {
474
- kptFn = append (kptFn , []byte (item )... )
475
- }
396
+
397
+ if err := os .MkdirAll (sinkDir , os .ModePerm ); err != nil {
398
+ return fmt .Errorf ("creating sink directory %s: %w" , sinkDir , err )
476
399
}
477
- return kptFn , nil
478
- }
479
400
480
- func (k * Deployer ) sink (ctx context.Context , buf []byte , sinkDir string ) error {
481
401
cmd := exec .CommandContext (ctx , "kpt" , kptCommandArgs (sinkDir , []string {"fn" , "sink" }, nil , nil )... )
482
402
cmd .Stdin = bytes .NewBuffer (buf )
483
- _ , err := util .RunCmdOut (cmd )
484
- return err
403
+ if _ , err := util .RunCmdOut (cmd ); err != nil {
404
+ return fmt .Errorf ("sinking to directory %s: %w" , sinkDir , err )
405
+ }
406
+ return nil
485
407
}
486
408
487
409
// excludeKptFn adds an annotation "config.kubernetes.io/local-config: 'true'" to kpt function.
@@ -606,9 +528,7 @@ func getResources(root string) ([]string, error) {
606
528
607
529
// getKptFnRunArgs returns a list of arguments that the user specified for the `kpt fn run` command.
608
530
func (k * Deployer ) getKptFnRunArgs () ([]string , error ) {
609
- // --dry-run sets the pipeline's output to STDOUT, otherwise output is set to sinkDir.
610
- // For now, k.Dir will be treated as sinkDir (and sourceDir).
611
- flags := []string {"--dry-run" }
531
+ var flags []string
612
532
613
533
if k .Fn .GlobalScope {
614
534
flags = append (flags , "--global-scope" )
@@ -627,8 +547,8 @@ func (k *Deployer) getKptFnRunArgs() ([]string, error) {
627
547
}
628
548
629
549
count := 0
630
- // fn-path is not supported due to kpt issue https://github.com/GoogleContainerTools/kpt/issues/1149
631
550
if len (k .Fn .FnPath ) > 0 {
551
+ flags = append (flags , "--fn-path" , k .Fn .FnPath )
632
552
count ++
633
553
}
634
554
@@ -697,3 +617,8 @@ func (k *Deployer) getGlobalFlags() []string {
697
617
698
618
return flags
699
619
}
620
+
621
+ func hasKustomization (dir string ) bool {
622
+ _ , err := kustomize .FindKustomizationConfig (dir )
623
+ return err == nil
624
+ }
0 commit comments