diff --git a/docs/content/en/schemas/v2beta7.json b/docs/content/en/schemas/v2beta7.json index ef1aabff5da..d08ce2bc844 100755 --- a/docs/content/en/schemas/v2beta7.json +++ b/docs/content/en/schemas/v2beta7.json @@ -1623,8 +1623,8 @@ } }, "preferredOrder": [ - "dir", "applyDir", + "dir", "fn", "live" ], @@ -1639,15 +1639,45 @@ "description": "a directory to read functions from instead of the configuration directory.", "x-intellij-html-description": "a directory to read functions from instead of the configuration directory." }, + "globalScope": { + "type": "boolean", + "description": "sets global scope for functions.", + "x-intellij-html-description": "sets global scope for functions.", + "default": "false" + }, "image": { "type": "string", "description": "an image to be run as a function in lieu of running functions from a directory.", "x-intellij-html-description": "an image to be run as a function in lieu of running functions from a directory." + }, + "mount": { + "items": { + "type": "string" + }, + "type": "array", + "description": "a list of storage options to mount to the fn image.", + "x-intellij-html-description": "a list of storage options to mount to the fn image.", + "default": "[]" + }, + "network": { + "type": "boolean", + "description": "enables network access for functions that declare it.", + "x-intellij-html-description": "enables network access for functions that declare it.", + "default": "false" + }, + "networkName": { + "type": "string", + "description": "docker network to run the container in (default \"bridge\").", + "x-intellij-html-description": "docker network to run the container in (default "bridge")." } }, "preferredOrder": [ "fnPath", - "image" + "image", + "networkName", + "globalScope", + "network", + "mount" ], "additionalProperties": false, "description": "adds additional configurations used when calling `kpt fn`.", @@ -1656,21 +1686,62 @@ "KptLive": { "properties": { "apply": { - "items": { - "type": "string" - }, - "type": "array", - "description": "additional flags passed on creations (`kpt live apply`).", - "x-intellij-html-description": "additional flags passed on creations (kpt live apply).", - "default": "[]" + "$ref": "#/definitions/KptLiveApply", + "description": "adds additional configurations for `kpt live apply` commands.", + "x-intellij-html-description": "adds additional configurations for kpt live apply commands." + }, + "inventoryID": { + "type": "string", + "description": "identifier for a group of applied resources. This configuration is used when users do not specify `KptDeploy.ApplyDir` and `.kpt-hydrated/inventory-template.yaml` does not exist.", + "x-intellij-html-description": "identifier for a group of applied resources. This configuration is used when users do not specify KptDeploy.ApplyDir and .kpt-hydrated/inventory-template.yaml does not exist." + }, + "inventoryNamespace": { + "type": "string", + "description": "sets the namespace scope for `kpt live init`.", + "x-intellij-html-description": "sets the namespace scope for kpt live init." + } + }, + "preferredOrder": [ + "apply", + "inventoryID", + "inventoryNamespace" + ], + "additionalProperties": false, + "description": "adds additional configurations used when calling `kpt live`.", + "x-intellij-html-description": "adds additional configurations used when calling kpt live." + }, + "KptLiveApply": { + "properties": { + "pollPeriod": { + "type": "string", + "description": "sets for the polling period for resource statuses. Default to 2s.", + "x-intellij-html-description": "sets for the polling period for resource statuses. Default to 2s." + }, + "prunePropagationPolicy": { + "type": "string", + "description": "sets the propagation policy for pruning. Possible settings are Background, Foreground, Orphan. Default to \"Background\".", + "x-intellij-html-description": "sets the propagation policy for pruning. Possible settings are Background, Foreground, Orphan. Default to "Background"." + }, + "pruneTimeout": { + "type": "string", + "description": "sets the time threshold to wait for all pruned resources to be deleted.", + "x-intellij-html-description": "sets the time threshold to wait for all pruned resources to be deleted." + }, + "reconcileTimeout": { + "type": "string", + "description": "sets the time threshold to wait for all resources to reach the current status.", + "x-intellij-html-description": "sets the time threshold to wait for all resources to reach the current status." } }, "preferredOrder": [ - "apply" + "pollPeriod", + "prunePropagationPolicy", + "pruneTimeout", + "reconcileTimeout" ], "additionalProperties": false, - "description": "adds additional configurations used when calling `kpt live` on every command (Global), creations (Apply), or deletions (Destroy).", - "x-intellij-html-description": "adds additional configurations used when calling kpt live on every command (Global), creations (Apply), or deletions (Destroy)." + "description": "adds additional configurations used when calling `kpt live apply`.", + "x-intellij-html-description": "adds additional configurations used when calling kpt live apply." }, "KubectlDeploy": { "properties": { diff --git a/pkg/skaffold/deploy/kpt.go b/pkg/skaffold/deploy/kpt.go index d7d82d96934..b77f9390928 100644 --- a/pkg/skaffold/deploy/kpt.go +++ b/pkg/skaffold/deploy/kpt.go @@ -87,7 +87,7 @@ func (k *KptDeployer) Deploy(ctx context.Context, out io.Writer, builds []build. outputRenderedManifests(manifests.String(), filepath.Join(applyDir, "resources.yaml"), out) - cmd := exec.CommandContext(ctx, "kpt", kptCommandArgs(applyDir, []string{"live", "apply"}, k.Live.Apply, nil)...) + cmd := exec.CommandContext(ctx, "kpt", kptCommandArgs(applyDir, []string{"live", "apply"}, k.getKptLiveApplyArgs(), nil)...) cmd.Stdout = out cmd.Stderr = out if err := util.RunCmd(cmd); err != nil { @@ -253,21 +253,9 @@ func (k *KptDeployer) kustomizeBuild(ctx context.Context) error { func (k *KptDeployer) kptFnRun(ctx context.Context) (deploy.ManifestList, error) { var manifests deploy.ManifestList - // --dry-run sets the pipeline's output to STDOUT, otherwise output is set to sinkDir. - // For now, k.Dir will be treated as sinkDir (and sourceDir). - flags := []string{"--dry-run"} - count := 0 - - if len(k.Fn.FnPath) > 0 { - flags = append(flags, "--fn-path", k.Fn.FnPath) - count++ - } - if len(k.Fn.Image) > 0 { - flags = append(flags, "--image", k.Fn.Image) - count++ - } - if count > 1 { - return nil, errors.New("only one of `fn-path` or `image` configs can be specified at most") + flags, err := k.getKptFnRunArgs() + if err != nil { + return nil, fmt.Errorf("getting kpt fn run args: %w", err) } cmd := exec.CommandContext(ctx, "kpt", kptCommandArgs(pipeline, []string{"fn", "run"}, flags, nil)...) @@ -300,7 +288,7 @@ func (k *KptDeployer) getApplyDir(ctx context.Context) (string, error) { } if _, err := os.Stat(filepath.Join(kptHydrated, inventoryTemplate)); os.IsNotExist(err) { - cmd := exec.CommandContext(ctx, "kpt", kptCommandArgs(kptHydrated, []string{"live", "init"}, nil, nil)...) + cmd := exec.CommandContext(ctx, "kpt", kptCommandArgs(kptHydrated, []string{"live", "init"}, k.getKptLiveInitArgs(), nil)...) if _, err := util.RunCmdOut(cmd); err != nil { return "", err } @@ -360,3 +348,82 @@ func getResources(root string) ([]string, error) { return files, err } + +// getKptFnRunArgs returns a list of arguments that the user specified for the `kpt fn run` command. +func (k *KptDeployer) getKptFnRunArgs() ([]string, error) { + // --dry-run sets the pipeline's output to STDOUT, otherwise output is set to sinkDir. + // For now, k.Dir will be treated as sinkDir (and sourceDir). + flags := []string{"--dry-run"} + + if k.Fn.GlobalScope { + flags = append(flags, "--global-scope") + } + + if len(k.Fn.Mount) > 0 { + flags = append(flags, "--mount", strings.Join(k.Fn.Mount, ",")) + } + + if k.Fn.Network { + flags = append(flags, "--network") + } + + if len(k.Fn.NetworkName) > 0 { + flags = append(flags, "--network-name", k.Fn.NetworkName) + } + + count := 0 + + if len(k.Fn.FnPath) > 0 { + flags = append(flags, "--fn-path", k.Fn.FnPath) + count++ + } + + if len(k.Fn.Image) > 0 { + flags = append(flags, "--image", k.Fn.Image) + count++ + } + + if count > 1 { + return nil, errors.New("only one of `fn-path` or `image` may be specified") + } + + return flags, nil +} + +// getKptLiveApplyArgs returns a list of arguments that the user specified for the `kpt live apply` command. +func (k *KptDeployer) getKptLiveApplyArgs() []string { + var flags []string + + if len(k.Live.Apply.PollPeriod) > 0 { + flags = append(flags, "--poll-period", k.Live.Apply.PollPeriod) + } + + if len(k.Live.Apply.PrunePropagationPolicy) > 0 { + flags = append(flags, "--prune-propagation-policy", k.Live.Apply.PrunePropagationPolicy) + } + + if len(k.Live.Apply.PruneTimeout) > 0 { + flags = append(flags, "--prune-timeout", k.Live.Apply.PruneTimeout) + } + + if len(k.Live.Apply.ReconcileTimeout) > 0 { + flags = append(flags, "--reconcile-timeout", k.Live.Apply.ReconcileTimeout) + } + + return flags +} + +// getKptLiveInitArgs returns a list of arguments that the user specified for the `kpt live init` command. +func (k *KptDeployer) getKptLiveInitArgs() []string { + var flags []string + + if len(k.Live.InventoryID) > 0 { + flags = append(flags, "--inventory-id", k.Live.InventoryID) + } + + if len(k.Live.InventoryNamespace) > 0 { + flags = append(flags, "--namespace", k.Live.InventoryNamespace) + } + + return flags +} diff --git a/pkg/skaffold/deploy/kpt_test.go b/pkg/skaffold/deploy/kpt_test.go index 489bbedc567..c35bae8b5b0 100644 --- a/pkg/skaffold/deploy/kpt_test.go +++ b/pkg/skaffold/deploy/kpt_test.go @@ -117,6 +117,78 @@ spec: AndRunErr("kpt live apply .kpt-hydrated", errors.New("BUG")), shouldErr: true, }, + { + description: "user specifies reconcile timeout and poll period", + cfg: &latest.KptDeploy{ + Dir: ".", + ApplyDir: "valid_path", + Live: latest.KptLive{ + Apply: latest.KptLiveApply{ + PollPeriod: "5s", + ReconcileTimeout: "2m", + }, + }, + }, + commands: testutil. + CmdRunOut("kpt fn source .", ``). + AndRunOut("kpt fn sink .pipeline", ``). + AndRunOut("kpt fn run .pipeline --dry-run", output). + AndRun("kpt live apply valid_path --poll-period 5s --reconcile-timeout 2m"), + }, + { + description: "user specifies invalid reconcile timeout and poll period", + cfg: &latest.KptDeploy{ + Dir: ".", + ApplyDir: "valid_path", + Live: latest.KptLive{ + Apply: latest.KptLiveApply{ + PollPeriod: "foo", + ReconcileTimeout: "bar", + }, + }, + }, + commands: testutil. + CmdRunOut("kpt fn source .", ``). + AndRunOut("kpt fn sink .pipeline", ``). + AndRunOut("kpt fn run .pipeline --dry-run", output). + AndRun("kpt live apply valid_path --poll-period foo --reconcile-timeout bar"), + }, + { + description: "user specifies prune propagation policy and prune timeout", + cfg: &latest.KptDeploy{ + Dir: ".", + ApplyDir: "valid_path", + Live: latest.KptLive{ + Apply: latest.KptLiveApply{ + PrunePropagationPolicy: "Orphan", + PruneTimeout: "2m", + }, + }, + }, + commands: testutil. + CmdRunOut("kpt fn source .", ``). + AndRunOut("kpt fn sink .pipeline", ``). + AndRunOut("kpt fn run .pipeline --dry-run", output). + AndRun("kpt live apply valid_path --prune-propagation-policy Orphan --prune-timeout 2m"), + }, + { + description: "user specifies invalid prune propagation policy and prune timeout", + cfg: &latest.KptDeploy{ + Dir: ".", + ApplyDir: "valid_path", + Live: latest.KptLive{ + Apply: latest.KptLiveApply{ + PrunePropagationPolicy: "foo", + PruneTimeout: "bar", + }, + }, + }, + commands: testutil. + CmdRunOut("kpt fn source .", ``). + AndRunOut("kpt fn sink .pipeline", ``). + AndRunOut("kpt fn run .pipeline --dry-run", output). + AndRun("kpt live apply valid_path --prune-propagation-policy foo --prune-timeout bar"), + }, } for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { @@ -277,6 +349,7 @@ func TestKpt_Cleanup(t *testing.T) { tests := []struct { description string applyDir string + globalFlags []string commands util.Command shouldErr bool }{ @@ -604,6 +677,67 @@ spec: AndRunOutErr("kpt fn run .pipeline --dry-run", "invalid pipeline", errors.New("BUG")), shouldErr: true, }, + { + description: "kpt fn run with --global-scope", + cfg: &latest.KptDeploy{ + Dir: ".", + Fn: latest.KptFn{ + Image: "gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", + GlobalScope: true, + }, + }, + commands: testutil. + CmdRunOut("kpt fn source .", ``). + AndRunOut("kpt fn sink .pipeline", ``). + AndRunOut("kpt fn run .pipeline --dry-run --global-scope --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", ``), + expected: "\n", + }, + { + description: "kpt fn run with --mount arguments", + cfg: &latest.KptDeploy{ + Dir: ".", + Fn: latest.KptFn{ + Image: "gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", + Mount: []string{"type=bind", "src=$(pwd)", "dst=/source"}, + }, + }, + commands: testutil. + CmdRunOut("kpt fn source .", ``). + AndRunOut("kpt fn sink .pipeline", ``). + AndRunOut("kpt fn run .pipeline --dry-run --mount type=bind,src=$(pwd),dst=/source --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", ``), + expected: "\n", + }, + { + description: "kpt fn run with invalid --mount arguments", + cfg: &latest.KptDeploy{ + Dir: ".", + Fn: latest.KptFn{ + Image: "gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", + Mount: []string{"foo", "", "bar"}, + }, + }, + commands: testutil. + CmdRunOut("kpt fn source .", ``). + AndRunOut("kpt fn sink .pipeline", ``). + AndRunOut("kpt fn run .pipeline --dry-run --mount foo,,bar --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", ``), + expected: "\n", + }, + { + description: "kpt fn run flag with --network and --network-name arguments", + cfg: &latest.KptDeploy{ + Dir: ".", + Fn: latest.KptFn{ + Image: "gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", + Network: true, + NetworkName: "foo", + }, + }, + commands: testutil. + CmdRunOut("kpt fn source .", ``). + AndRunOut("kpt fn sink .pipeline", ``). + AndRunOut("kpt fn run .pipeline --dry-run --network --network-name foo --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", ``), + expected: "\n", + }, } for _, test := range tests { testutil.Run(t, test.description, func(t *testutil.T) { @@ -639,6 +773,7 @@ func TestKpt_GetApplyDir(t *testing.T) { tests := []struct { description string applyDir string + live latest.KptLive expected string commands util.Command shouldErr bool @@ -658,6 +793,15 @@ func TestKpt_GetApplyDir(t *testing.T) { expected: ".kpt-hydrated", commands: testutil.CmdRunOut("kpt live init .kpt-hydrated", ""), }, + { + description: "unspecified applyDir with specified inventory-id and namespace", + live: latest.KptLive{ + InventoryID: "1a23bcde-4f56-7891-a2bc-de34fabcde5f6", + InventoryNamespace: "foo", + }, + expected: ".kpt-hydrated", + commands: testutil.CmdRunOut("kpt live init .kpt-hydrated --inventory-id 1a23bcde-4f56-7891-a2bc-de34fabcde5f6 --namespace foo", ""), + }, { description: "existing template resource in .kpt-hydrated", expected: ".kpt-hydrated", @@ -685,6 +829,7 @@ func TestKpt_GetApplyDir(t *testing.T) { DeployType: latest.DeployType{ KptDeploy: &latest.KptDeploy{ ApplyDir: test.applyDir, + Live: test.live, }, }, }, @@ -722,8 +867,8 @@ func TestKpt_KptCommandArgs(t *testing.T) { description: "empty dir", commands: []string{"live", "apply"}, flags: []string{"--fn-path", "kpt-func.yaml"}, - globalFlags: []string{"-h"}, - expected: strings.Split("live apply --fn-path kpt-func.yaml -h", " "), + globalFlags: []string{"-v", "3"}, + expected: strings.Split("live apply --fn-path kpt-func.yaml -v 3", " "), }, { description: "empty commands", diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index 20a14fd5723..93551bf5c18 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -528,12 +528,12 @@ type KustomizeDeploy struct { // KptDeploy *beta* uses the `kpt` CLI to manage and deploy manifests. type KptDeploy struct { - // Dir is the path to the directory to run kpt functions against. - Dir string `yaml:"dir,omitempty"` - // ApplyDir is the path to the directory to deploy to the cluster. ApplyDir string `yaml:"applyDir,omitempty"` + // Dir is the path to the directory to run kpt functions against. + Dir string `yaml:"dir,omitempty"` + // Fn adds additional configurations for `kpt fn`. Fn KptFn `yaml:"fn,omitempty"` @@ -548,13 +548,49 @@ type KptFn struct { // Image is an image to be run as a function in lieu of running functions from a directory. Image string `yaml:"image,omitempty"` + + // NetworkName is the docker network to run the container in (default "bridge"). + NetworkName string `yaml:"networkName,omitempty"` + + // GlobalScope sets global scope for functions. + GlobalScope bool `yaml:"globalScope,omitempty"` + + // Network enables network access for functions that declare it. + Network bool `yaml:"network,omitempty"` + + // Mount is a list of storage options to mount to the fn image. + Mount []string `yaml:"mount,omitempty"` } -// KptLive adds additional configurations used when calling `kpt live` -// on every command (Global), creations (Apply), or deletions (Destroy). +// KptLive adds additional configurations used when calling `kpt live`. type KptLive struct { - // Apply are additional flags passed on creations (`kpt live apply`). - Apply []string `yaml:"apply,omitempty"` + // Apply adds additional configurations for `kpt live apply` commands. + Apply KptLiveApply `yaml:"apply,omitempty"` + + // InventoryID is the identifier for a group of applied resources. + // This configuration is used when users do not specify `KptDeploy.ApplyDir` + // and `.kpt-hydrated/inventory-template.yaml` does not exist. + InventoryID string `yaml:"inventoryID,omitempty"` + + // InventoryNamespace sets the namespace scope for `kpt live init`. + InventoryNamespace string `yaml:"inventoryNamespace,omitempty"` +} + +// KptLiveApply adds additional configurations used when calling `kpt live apply`. +type KptLiveApply struct { + // PollPeriod sets for the polling period for resource statuses. Default to 2s. + PollPeriod string `yaml:"pollPeriod,omitempty"` + + // PrunePropagationPolicy sets the propagation policy for pruning. + // Possible settings are Background, Foreground, Orphan. + // Default to "Background". + PrunePropagationPolicy string `yaml:"prunePropagationPolicy,omitempty"` + + // PruneTimeout sets the time threshold to wait for all pruned resources to be deleted. + PruneTimeout string `yaml:"pruneTimeout,omitempty"` + + // ReconcileTimeout sets the time threshold to wait for all resources to reach the current status. + ReconcileTimeout string `yaml:"reconcileTimeout,omitempty"` } // HelmRelease describes a helm release to be deployed.