Repository: google/kf
Pull request #700 (2019-09-20 14:27:21)

added forwarding headers
<!-- Include the issue number below -->

## Proposed Changes

* Adds forwarding headers to our VirtualService so the backing app knows which domain the request originally came from.

## Release Notes

<!-- kf follows the Keep A Changelog standard for release notes.

https://keepachangelog.com/en/1.0.0/

Changelog entries should be one per line and start with one of the following
words:

`Added` for new features.
`Changed` for changes in existing functionality.
`Deprecated` for soon-to-be removed features.
`Removed` for now removed features.
`Fixed` for any bug fixes.
`Security` in case of vulnerabilities.

If one of the changes is breaking include that as a second word e.g.

  Removed BREAKING support for manifests v1
-->

```release-note
Added forwarding headers `X-Forwarded-Host` and `Forwarded` to routes.
```



Patch:
diff --git a/docs/kf.dev/content/en/docs/developers/develop-and-manage-apps/kf-push/app-manifests/_index.md b/docs/kf.dev/content/en/docs/developers/develop-and-manage-apps/kf-push/app-manifests/_index.md
index f2d7bd6..2176d92 100644
--- a/docs/kf.dev/content/en/docs/developers/develop-and-manage-apps/kf-push/app-manifests/_index.md
+++ b/docs/kf.dev/content/en/docs/developers/develop-and-manage-apps/kf-push/app-manifests/_index.md
@@ -29,16 +29,21 @@ The following fields are valid for objects under `applications`:
 | **services** | string[] | A list of service instance names to automatically bind to the app. |
 | **disk_quota** | quantity | The amount of disk the application should get. Defaults to 1GiB. |
 | **memory** | quantity | The amount of RAM to provide the app. Defaults to 1GiB. |
-| **cpu** | quantity | The amount of CPU to provide the application. Defaults to 0.1 (1/10th of a CPU). |
+| **cpu** † | quantity | The amount of CPU to provide the application. Defaults to 0.1 (1/10th of a CPU). |
 | **instances** | int | The number of instances of the app to run. Defaults to 1. |
-| **min-scale** | int | The minimum number of instances to scale to. Valid only if instances is unset. |
-| **max-scale** | int | The maximum number of instances to scale to. Valid only if instances is unset. Blank means unlimited scaling. |
+| **min-scale** † | int | The minimum number of instances to scale to. Valid only if instances is unset. |
+| **max-scale** † | int | The maximum number of instances to scale to. Valid only if instances is unset. Blank means unlimited scaling. |
 | **routes** | object | A list of routes the app should listen on. See the Route Fields section for more. |
 | **no-route** | boolean | If set to true, the application will not be routable. |
 | **random-route** | boolean | If set to true, the app will be given a random route. |
 | **timeout** | int | The number of seconds to wait for the app to become healthy. |
 | **health-check-type** | string | The type of health-check to use `port`, `none`, or `http`. Default: `port` |
 | **health-check-http-endpoint** | string | The endpoint to target as part of the health-check. Only valid if `health-check-type` is `http`. |
+| **command** | string | The command that starts the app. If supplied, this will be passed to the container entrypoint. |
+| **entrypoint** † | string | Overrides the app container's entrypoint. |
+| **args** † | string[] | Overrides the arguments the app container. |
+
+† Unique to Kf
 
 
 ## Docker Fields
diff --git a/docs/kf.dev/content/en/docs/general-info/kf-cli/commands/kf_push.md b/docs/kf.dev/content/en/docs/general-info/kf-cli/commands/kf_push.md
index 87702fb..1467736 100644
--- a/docs/kf.dev/content/en/docs/general-info/kf-cli/commands/kf_push.md
+++ b/docs/kf.dev/content/en/docs/general-info/kf-cli/commands/kf_push.md
@@ -27,9 +27,12 @@ kf push APP_NAME [flags]
 ### Options
 
 ```
+      --args stringArray            Overwrite the args for the image. Can't be used with the command flag.
   -b, --buildpack string            Skip the 'detect' buildpack step and use the given name.
+  -c, --command string              Startup command for the app, this overrides the default command specified by the web process.
       --container-registry string   Container registry to push sources to. Required for buildpack builds not targeting a Kf space.
       --docker-image string         Docker image to deploy.
+      --entrypoint string           Overwrite the default entrypoint of the image. Can't be used with the command flag.
   -e, --env stringArray             Set environment variables. Multiple can be set by using the flag multiple times (e.g., NAME=VALUE).
       --grpc                        Setup the container to allow application to use gRPC.
   -u, --health-check-type string    Application health check type (http or port, default: port)
diff --git a/pkg/kf/apps/client.go b/pkg/kf/apps/client.go
index 2e7ce43..4eefc07 100644
--- a/pkg/kf/apps/client.go
+++ b/pkg/kf/apps/client.go
@@ -45,19 +45,17 @@ func NewClient(
 	return &appsClient{
 		coreClient: coreClient{
 			kclient: kclient,
-			upsertMutate: MutatorList{
-				func(app *v1alpha1.App) error {
-					// Dedupe Routes
-					// TODO(https://github.com/knative/pkg/issues/542): Route
-					// already exists and the webhook can't dedupe for us.
-					app.Spec.Routes = []v1alpha1.RouteSpecFields(
-						algorithms.Dedupe(
-							v1alpha1.RouteSpecFieldsSlice(app.Spec.Routes),
-						).(v1alpha1.RouteSpecFieldsSlice),
-					)
-
-					return nil
-				},
+			upsertMutate: func(app *v1alpha1.App) error {
+				// Dedupe Routes
+				// TODO(https://github.com/knative/pkg/issues/542): Route
+				// already exists and the webhook can't dedupe for us.
+				app.Spec.Routes = []v1alpha1.RouteSpecFields(
+					algorithms.Dedupe(
+						v1alpha1.RouteSpecFieldsSlice(app.Spec.Routes),
+					).(v1alpha1.RouteSpecFieldsSlice),
+				)
+
+				return nil
 			},
 		},
 		sourcesClient: sourcesClient,
diff --git a/pkg/kf/apps/kfapp.go b/pkg/kf/apps/kfapp.go
index 4f7bb2b..4668cda 100644
--- a/pkg/kf/apps/kfapp.go
+++ b/pkg/kf/apps/kfapp.go
@@ -243,6 +243,34 @@ func (k *KfApp) GetServiceBindings() []v1alpha1.AppSpecServiceBinding {
 	return k.Spec.ServiceBindings
 }
 
+// SetCommand sets the entrypoint for the app.
+func (k *KfApp) SetCommand(entrypoint []string) {
+	k.getOrCreateContainer().Command = entrypoint
+}
+
+// GetCommand gets the entrypoint for the app.
+func (k *KfApp) GetCommand() []string {
+	if container := k.getContainerOrNil(); container != nil {
+		return container.Command
+	}
+
+	return nil
+}
+
+// SetArgs sets the arguments for the app.
+func (k *KfApp) SetArgs(args []string) {
+	k.getOrCreateContainer().Args = args
+}
+
+// GetArgs gets the arguments for the app.
+func (k *KfApp) GetArgs() []string {
+	if container := k.getContainerOrNil(); container != nil {
+		return container.Args
+	}
+
+	return nil
+}
+
 // GetClusterURL gets the internal address of the app or the empty string if
 // unset.
 func (k *KfApp) GetClusterURL() string {
diff --git a/pkg/kf/apps/push-options.yml b/pkg/kf/apps/push-options.yml
index dc0b2b0..0c13854 100644
--- a/pkg/kf/apps/push-options.yml
+++ b/pkg/kf/apps/push-options.yml
@@ -68,4 +68,10 @@ configs:
   - name: ServiceBindings
     type: "[]v1alpha1.AppSpecServiceBinding"
     description: a list of Services to bind to the app
+  - name: Command
+    type: "[]string"
+    description: the app container entrypoint
+  - name: Args
+    type: "[]string"
+    description: the app container arguments
 - name: Deploy
diff --git a/pkg/kf/apps/push.go b/pkg/kf/apps/push.go
index c1aee92..b39bd1d 100644
--- a/pkg/kf/apps/push.go
+++ b/pkg/kf/apps/push.go
@@ -74,6 +74,8 @@ func newApp(appName string, opts ...PushOption) (*v1alpha1.App, error) {
 	app.SetHealthCheck(cfg.HealthCheck)
 	app.Spec.Routes = cfg.Routes
 	app.Spec.ServiceBindings = cfg.ServiceBindings
+	app.SetCommand(cfg.Command)
+	app.SetArgs(cfg.Args)
 
 	if cfg.Grpc {
 		app.SetContainerPorts([]corev1.ContainerPort{{Name: "h2c", ContainerPort: 8080}})
diff --git a/pkg/kf/apps/push_options.go b/pkg/kf/apps/push_options.go
index 9c7d94a..faac431 100644
--- a/pkg/kf/apps/push_options.go
+++ b/pkg/kf/apps/push_options.go
@@ -25,10 +25,14 @@ import (
 )
 
 type pushConfig struct {
+	// Args is the app container arguments
+	Args []string
 	// Buildpack is skip the detect buildpack step and use the given name
 	Buildpack string
 	// CPU is app CPU request
 	CPU *resource.Quantity
+	// Command is the app container entrypoint
+	Command []string
 	// ContainerImage is the container to deploy
 	ContainerImage string
 	// DefaultRouteDomain is Domain for a defaultroute. Only used if a route doesn't already exist
@@ -93,6 +97,12 @@ func (opts PushOptions) Extend(other PushOptions) PushOptions {
 	return out
 }
 
+// Args returns the last set value for Args or the empty value
+// if not set.
+func (opts PushOptions) Args() []string {
+	return opts.toConfig().Args
+}
+
 // Buildpack returns the last set value for Buildpack or the empty value
 // if not set.
 func (opts PushOptions) Buildpack() string {
@@ -105,6 +115,12 @@ func (opts PushOptions) CPU() *resource.Quantity {
 	return opts.toConfig().CPU
 }
 
+// Command returns the last set value for Command or the empty value
+// if not set.
+func (opts PushOptions) Command() []string {
+	return opts.toConfig().Command
+}
+
 // ContainerImage returns the last set value for ContainerImage or the empty value
 // if not set.
 func (opts PushOptions) ContainerImage() string {
@@ -213,6 +229,13 @@ func (opts PushOptions) Stack() string {
 	return opts.toConfig().Stack
 }
 
+// WithPushArgs creates an Option that sets the app container arguments
+func WithPushArgs(val []string) PushOption {
+	return func(cfg *pushConfig) {
+		cfg.Args = val
+	}
+}
+
 // WithPushBuildpack creates an Option that sets skip the detect buildpack step and use the given name
 func WithPushBuildpack(val string) PushOption {
 	return func(cfg *pushConfig) {
@@ -227,6 +250,13 @@ func WithPushCPU(val *resource.Quantity) PushOption {
 	}
 }
 
+// WithPushCommand creates an Option that sets the app container entrypoint
+func WithPushCommand(val []string) PushOption {
+	return func(cfg *pushConfig) {
+		cfg.Command = val
+	}
+}
+
 // WithPushContainerImage creates an Option that sets the container to deploy
 func WithPushContainerImage(val string) PushOption {
 	return func(cfg *pushConfig) {
diff --git a/pkg/kf/apps/zz_generated.client.go b/pkg/kf/apps/zz_generated.client.go
index b08fde4..ca69d49 100644
--- a/pkg/kf/apps/zz_generated.client.go
+++ b/pkg/kf/apps/zz_generated.client.go
@@ -51,19 +51,6 @@ const (
 // Predicate is a boolean function for a v1alpha1.App.
 type Predicate func(*v1alpha1.App) bool
 
-// AllPredicate is a predicate that passes if all children pass.
-func AllPredicate(children ...Predicate) Predicate {
-	return func(obj *v1alpha1.App) bool {
-		for _, filter := range children {
-			if !filter(obj) {
-				return false
-			}
-		}
-
-		return true
-	}
-}
-
 // Mutator is a function that changes v1alpha1.App.
 type Mutator func(*v1alpha1.App) error
 
@@ -117,51 +104,6 @@ func (list List) Filter(filter Predicate) (out List) {
 	return
 }
 
-// MutatorList is a list of mutators.
-type MutatorList []Mutator
-
-// Apply passes the given value to each of the mutators in the list failing if
-// one of them returns an error.
-func (list MutatorList) Apply(svc *v1alpha1.App) error {
-	for _, mutator := range list {
-		if err := mutator(svc); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// LabelSetMutator creates a mutator that sets the given labels on the object.
-func LabelSetMutator(labels map[string]string) Mutator {
-	return func(obj *v1alpha1.App) error {
-		if obj.Labels == nil {
-			obj.Labels = make(map[string]string)
-		}
-
-		for key, value := range labels {
-			obj.Labels[key] = value
-		}
-
-		return nil
-	}
-}
-
-// LabelEqualsPredicate validates that the given label exists exactly on the object.
-func LabelEqualsPredicate(key, value string) Predicate {
-	return func(obj *v1alpha1.App) bool {
-		return obj.Labels[key] == value
-	}
-}
-
-// LabelsContainsPredicate validates that the given label exists on the object.
-func LabelsContainsPredicate(key string) Predicate {
-	return func(obj *v1alpha1.App) bool {
-		_, ok := obj.Labels[key]
-		return ok
-	}
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Client
 ////////////////////////////////////////////////////////////////////////////////
@@ -184,15 +126,15 @@ type Client interface {
 
 type coreClient struct {
 	kclient      cv1alpha1.AppsGetter
-	upsertMutate MutatorList
+	upsertMutate Mutator
 }
 
 func (core *coreClient) preprocessUpsert(obj *v1alpha1.App) error {
-	if err := core.upsertMutate.Apply(obj); err != nil {
-		return err
+	if core.upsertMutate == nil {
+		return nil
 	}
 
-	return nil
+	return core.upsertMutate(obj)
 }
 
 // Create inserts the given v1alpha1.App into the cluster.
@@ -263,10 +205,6 @@ func (cfg deleteConfig) ToDeleteOptions() *metav1.DeleteOptions {
 		resp.PropagationPolicy = &propigationPolicy
 	}
 
-	if cfg.DeleteImmediately {
-		resp.GracePeriodSeconds = new(int64)
-	}
-
 	return &resp
 }
 
@@ -280,7 +218,11 @@ func (core *coreClient) List(namespace string, opts ...ListOption) ([]v1alpha1.A
 		return nil, fmt.Errorf("couldn't list Apps: %v", err)
 	}
 
-	return List(res.Items).Filter(AllPredicate(cfg.filters...)), nil
+	if cfg.filter == nil {
+		return res.Items, nil
+	}
+
+	return List(res.Items).Filter(cfg.filter), nil
 }
 
 func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
@@ -288,10 +230,6 @@ func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
 		resp.FieldSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.fieldSelector))
 	}
 
-	if cfg.labelSelector != nil {
-		resp.LabelSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.labelSelector))
-	}
-
 	return
 }
 
diff --git a/pkg/kf/apps/zz_generated.clientoptions.go b/pkg/kf/apps/zz_generated.clientoptions.go
index 85f8255..d15dd07 100644
--- a/pkg/kf/apps/zz_generated.clientoptions.go
+++ b/pkg/kf/apps/zz_generated.clientoptions.go
@@ -119,8 +119,6 @@ func GetOptionDefaults() GetOptions {
 }
 
 type deleteConfig struct {
-	// DeleteImmediately is If the resource should be deleted immediately.
-	DeleteImmediately bool
 	// ForegroundDeletion is If the resource should be deleted in the foreground.
 	ForegroundDeletion bool
 }
@@ -151,25 +149,12 @@ func (opts DeleteOptions) Extend(other DeleteOptions) DeleteOptions {
 	return out
 }
 
-// DeleteImmediately returns the last set value for DeleteImmediately or the empty value
-// if not set.
-func (opts DeleteOptions) DeleteImmediately() bool {
-	return opts.toConfig().DeleteImmediately
-}
-
 // ForegroundDeletion returns the last set value for ForegroundDeletion or the empty value
 // if not set.
 func (opts DeleteOptions) ForegroundDeletion() bool {
 	return opts.toConfig().ForegroundDeletion
 }
 
-// WithDeleteDeleteImmediately creates an Option that sets If the resource should be deleted immediately.
-func WithDeleteDeleteImmediately(val bool) DeleteOption {
-	return func(cfg *deleteConfig) {
-		cfg.DeleteImmediately = val
-	}
-}
-
 // WithDeleteForegroundDeletion creates an Option that sets If the resource should be deleted in the foreground.
 func WithDeleteForegroundDeletion(val bool) DeleteOption {
 	return func(cfg *deleteConfig) {
@@ -185,10 +170,8 @@ func DeleteOptionDefaults() DeleteOptions {
 type listConfig struct {
 	// fieldSelector is A selector on the resource's fields.
 	fieldSelector map[string]string
-	// filters is Additional filters to apply.
-	filters []Predicate
-	// labelSelector is A label selector.
-	labelSelector map[string]string
+	// filter is Filter to apply.
+	filter Predicate
 }
 
 // ListOption is a single option for configuring a listConfig
@@ -223,16 +206,10 @@ func (opts ListOptions) fieldSelector() map[string]string {
 	return opts.toConfig().fieldSelector
 }
 
-// filters returns the last set value for filters or the empty value
+// filter returns the last set value for filter or the empty value
 // if not set.
-func (opts ListOptions) filters() []Predicate {
-	return opts.toConfig().filters
-}
-
-// labelSelector returns the last set value for labelSelector or the empty value
-// if not set.
-func (opts ListOptions) labelSelector() map[string]string {
-	return opts.toConfig().labelSelector
+func (opts ListOptions) filter() Predicate {
+	return opts.toConfig().filter
 }
 
 // WithListFieldSelector creates an Option that sets A selector on the resource's fields.
@@ -242,17 +219,10 @@ func WithListFieldSelector(val map[string]string) ListOption {
 	}
 }
 
-// WithListFilters creates an Option that sets Additional filters to apply.
-func WithListFilters(val []Predicate) ListOption {
-	return func(cfg *listConfig) {
-		cfg.filters = val
-	}
-}
-
-// WithListLabelSelector creates an Option that sets A label selector.
-func WithListLabelSelector(val map[string]string) ListOption {
+// WithListFilter creates an Option that sets Filter to apply.
+func WithListFilter(val Predicate) ListOption {
 	return func(cfg *listConfig) {
-		cfg.labelSelector = val
+		cfg.filter = val
 	}
 }
 
diff --git a/pkg/kf/commands/apps/app.go b/pkg/kf/commands/apps/app.go
index 6e4cc8e..593c7df 100644
--- a/pkg/kf/commands/apps/app.go
+++ b/pkg/kf/commands/apps/app.go
@@ -23,8 +23,8 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 )
diff --git a/pkg/kf/commands/apps/apps.go b/pkg/kf/commands/apps/apps.go
index ac963e5..68e130c 100644
--- a/pkg/kf/commands/apps/apps.go
+++ b/pkg/kf/commands/apps/apps.go
@@ -22,8 +22,8 @@ import (
 
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 	corev1 "k8s.io/api/core/v1"
 )
diff --git a/pkg/kf/commands/apps/delete.go b/pkg/kf/commands/apps/delete.go
index 930428b..fa636d6 100644
--- a/pkg/kf/commands/apps/delete.go
+++ b/pkg/kf/commands/apps/delete.go
@@ -20,7 +20,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
diff --git a/pkg/kf/commands/apps/env.go b/pkg/kf/commands/apps/env.go
index 0303f10..d39e2cb 100644
--- a/pkg/kf/commands/apps/env.go
+++ b/pkg/kf/commands/apps/env.go
@@ -18,8 +18,8 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
diff --git a/pkg/kf/commands/apps/logs.go b/pkg/kf/commands/apps/logs.go
index 709dbfa..c11a14a 100644
--- a/pkg/kf/commands/apps/logs.go
+++ b/pkg/kf/commands/apps/logs.go
@@ -20,8 +20,7 @@ import (
 
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
-	kfi "github.com/google/kf/pkg/kf/internal/kf"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/logs"
 	"github.com/spf13/cobra"
 )
@@ -65,7 +64,7 @@ func NewLogsCommand(p *config.KfParams, tailer logs.Tailer) *cobra.Command {
 				logs.WithTailNumberLines(numberLines),
 				logs.WithTailFollow(shouldFollow),
 			); err != nil {
-				cmd.SilenceUsage = !kfi.ConfigError(err)
+				cmd.SilenceUsage = !utils.ConfigError(err)
 				return fmt.Errorf("failed to tail logs: %s", err)
 			}
 
diff --git a/pkg/kf/commands/apps/proxy.go b/pkg/kf/commands/apps/proxy.go
index b0b01c8..f15e149 100644
--- a/pkg/kf/commands/apps/proxy.go
+++ b/pkg/kf/commands/apps/proxy.go
@@ -22,7 +22,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/istio"
 	"github.com/spf13/cobra"
 )
diff --git a/pkg/kf/commands/apps/push.go b/pkg/kf/commands/apps/push.go
index 1eff62d..f98a05b 100644
--- a/pkg/kf/commands/apps/push.go
+++ b/pkg/kf/commands/apps/push.go
@@ -28,8 +28,7 @@ import (
 	"github.com/google/kf/pkg/internal/envutil"
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
-	kfi "github.com/google/kf/pkg/kf/internal/kf"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/manifest"
 	servicebindings "github.com/google/kf/pkg/kf/service-bindings"
 	"github.com/spf13/cobra"
@@ -73,25 +72,28 @@ func NewPushCommand(
 	serviceBindingClient servicebindings.ClientInterface,
 ) *cobra.Command {
 	var (
-		containerRegistry  string
-		sourceImage        string
-		containerImage     string
-		manifestFile       string
-		instances          int
-		minScale           int
-		maxScale           int
-		path               string
-		buildpack          string
-		stack              string
-		envs               []string
-		grpc               bool
-		noManifest         bool
-		noStart            bool
-		healthCheckType    string
-		healthCheckTimeout int
-		memoryRequest      *resource.Quantity
-		storageRequest     *resource.Quantity
-		cpuRequest         *resource.Quantity
+		containerRegistry   string
+		sourceImage         string
+		containerImage      string
+		manifestFile        string
+		instances           int
+		minScale            int
+		maxScale            int
+		path                string
+		buildpack           string
+		stack               string
+		envs                []string
+		grpc                bool
+		noManifest          bool
+		noStart             bool
+		healthCheckType     string
+		healthCheckTimeout  int
+		memoryRequest       *resource.Quantity
+		storageRequest      *resource.Quantity
+		cpuRequest          *resource.Quantity
+		startupCommand      string
+		containerEntrypoint string
+		containerArgs       []string
 
 		// Route Flags
 		rawRoutes         []string
@@ -163,6 +165,9 @@ func NewPushCommand(
 			{
 				overrides.Docker.Image = containerImage
 				overrides.Stack = stack
+				overrides.Command = startupCommand
+				overrides.Args = containerArgs
+				overrides.Entrypoint = containerEntrypoint
 
 				// Read environment variables from cli args
 				envVars, err := envutil.ParseCLIEnvVars(envs)
@@ -298,6 +303,8 @@ func NewPushCommand(
 					apps.WithPushHealthCheck(healthCheck),
 					apps.WithPushRandomRouteDomain(randomRouteDomain),
 					apps.WithPushDefaultRouteDomain(defaultRouteDomain),
+					apps.WithPushCommand(app.CommandEntrypoint()),
+					apps.WithPushArgs(app.CommandArgs()),
 				}
 
 				if app.Docker.Image == "" {
@@ -358,7 +365,7 @@ func NewPushCommand(
 
 				err = pusher.Push(app.Name, pushOpts...)
 
-				cmd.SilenceUsage = !kfi.ConfigError(err)
+				cmd.SilenceUsage = !utils.ConfigError(err)
 
 				if err != nil {
 					return err
@@ -514,6 +521,28 @@ func NewPushCommand(
 		"Use the routes flag to provide multiple HTTP and TCP routes. Each route for this app is created if it does not already exist.",
 	)
 
+	pushCmd.Flags().StringVarP(
+		&startupCommand,
+		"command",
+		"c",
+		"",
+		"Startup command for the app, this overrides the default command specified by the web process.",
+	)
+
+	pushCmd.Flags().StringVar(
+		&containerEntrypoint,
+		"entrypoint",
+		"",
+		"Overwrite the default entrypoint of the image. Can't be used with the command flag.",
+	)
+
+	pushCmd.Flags().StringArrayVar(
+		&containerArgs,
+		"args",
+		nil,
+		"Overwrite the args for the image. Can't be used with the command flag.",
+	)
+
 	return pushCmd
 }
 
diff --git a/pkg/kf/commands/apps/restage.go b/pkg/kf/commands/apps/restage.go
index 9f63f43..1ed2b6b 100644
--- a/pkg/kf/commands/apps/restage.go
+++ b/pkg/kf/commands/apps/restage.go
@@ -20,7 +20,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
diff --git a/pkg/kf/commands/apps/restart.go b/pkg/kf/commands/apps/restart.go
index ba48cfb..efa12fc 100644
--- a/pkg/kf/commands/apps/restart.go
+++ b/pkg/kf/commands/apps/restart.go
@@ -20,7 +20,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
diff --git a/pkg/kf/commands/apps/scale.go b/pkg/kf/commands/apps/scale.go
index 25fdb86..b341bb7 100644
--- a/pkg/kf/commands/apps/scale.go
+++ b/pkg/kf/commands/apps/scale.go
@@ -22,8 +22,8 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
diff --git a/pkg/kf/commands/apps/set_env.go b/pkg/kf/commands/apps/set_env.go
index 59153ba..5ef6d47 100644
--- a/pkg/kf/commands/apps/set_env.go
+++ b/pkg/kf/commands/apps/set_env.go
@@ -21,7 +21,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 	corev1 "k8s.io/api/core/v1"
 )
diff --git a/pkg/kf/commands/apps/start.go b/pkg/kf/commands/apps/start.go
index 9dca7c9..6a8f179 100644
--- a/pkg/kf/commands/apps/start.go
+++ b/pkg/kf/commands/apps/start.go
@@ -21,7 +21,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
diff --git a/pkg/kf/commands/apps/stop.go b/pkg/kf/commands/apps/stop.go
index 839c78c..2fd55e5 100644
--- a/pkg/kf/commands/apps/stop.go
+++ b/pkg/kf/commands/apps/stop.go
@@ -21,7 +21,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
diff --git a/pkg/kf/commands/apps/unset_env.go b/pkg/kf/commands/apps/unset_env.go
index af3871d..5474887 100644
--- a/pkg/kf/commands/apps/unset_env.go
+++ b/pkg/kf/commands/apps/unset_env.go
@@ -21,7 +21,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
diff --git a/pkg/kf/commands/buildpacks/buildpacks.go b/pkg/kf/commands/buildpacks/buildpacks.go
index 2760df9..510e32f 100644
--- a/pkg/kf/commands/buildpacks/buildpacks.go
+++ b/pkg/kf/commands/buildpacks/buildpacks.go
@@ -20,9 +20,8 @@ import (
 
 	"github.com/google/kf/pkg/kf/buildpacks"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
-	"github.com/google/kf/pkg/kf/internal/kf"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
@@ -53,7 +52,7 @@ func NewBuildpacksCommand(p *config.KfParams, l buildpacks.Client) *cobra.Comman
 
 			bps, err := l.List(space.Spec.BuildpackBuild.BuilderImage)
 			if err != nil {
-				cmd.SilenceUsage = !kf.ConfigError(err)
+				cmd.SilenceUsage = !utils.ConfigError(err)
 				return err
 			}
 
diff --git a/pkg/kf/commands/buildpacks/stacks.go b/pkg/kf/commands/buildpacks/stacks.go
index ade7205..96d9d1d 100644
--- a/pkg/kf/commands/buildpacks/stacks.go
+++ b/pkg/kf/commands/buildpacks/stacks.go
@@ -20,9 +20,8 @@ import (
 
 	"github.com/google/kf/pkg/kf/buildpacks"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
-	"github.com/google/kf/pkg/kf/internal/kf"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
@@ -53,7 +52,7 @@ func NewStacksCommand(p *config.KfParams, l buildpacks.Client) *cobra.Command {
 
 			stacks, err := l.Stacks(space.Spec.BuildpackBuild.BuilderImage)
 			if err != nil {
-				cmd.SilenceUsage = !kf.ConfigError(err)
+				cmd.SilenceUsage = !utils.ConfigError(err)
 				return err
 			}
 
diff --git a/pkg/kf/commands/builds/list.go b/pkg/kf/commands/builds/list.go
index 6f05080..c063f17 100644
--- a/pkg/kf/commands/builds/list.go
+++ b/pkg/kf/commands/builds/list.go
@@ -20,8 +20,8 @@ import (
 
 	"github.com/google/kf/pkg/apis/kf/v1alpha1"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/sources"
 	"github.com/spf13/cobra"
 	"k8s.io/apimachinery/pkg/api/meta/table"
diff --git a/pkg/kf/commands/builds/logs.go b/pkg/kf/commands/builds/logs.go
index 0c70a32..493c6d8 100644
--- a/pkg/kf/commands/builds/logs.go
+++ b/pkg/kf/commands/builds/logs.go
@@ -19,7 +19,7 @@ import (
 
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/sources"
 	"github.com/spf13/cobra"
 )
diff --git a/pkg/kf/commands/routes/create_route.go b/pkg/kf/commands/routes/create_route.go
index 8eb5a1f..cea4954 100644
--- a/pkg/kf/commands/routes/create_route.go
+++ b/pkg/kf/commands/routes/create_route.go
@@ -21,7 +21,7 @@ import (
 
 	"github.com/google/kf/pkg/apis/kf/v1alpha1"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/routeclaims"
 	"github.com/spf13/cobra"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/pkg/kf/commands/routes/delete_route.go b/pkg/kf/commands/routes/delete_route.go
index a4e9da7..0937375 100644
--- a/pkg/kf/commands/routes/delete_route.go
+++ b/pkg/kf/commands/routes/delete_route.go
@@ -21,7 +21,7 @@ import (
 	"github.com/google/kf/pkg/kf/algorithms"
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/routeclaims"
 	"github.com/spf13/cobra"
 )
@@ -63,14 +63,12 @@ func NewDeleteRouteCommand(
 			// routes with the proper labels first, then use their apps.
 			apps, err := a.List(
 				p.Namespace,
-				apps.WithListFilters([]apps.Predicate{
-					func(app *v1alpha1.App) bool {
-						return algorithms.Search(
-							0,
-							v1alpha1.RouteSpecFieldsSlice{route},
-							v1alpha1.RouteSpecFieldsSlice(app.Spec.Routes),
-						)
-					},
+				apps.WithListFilter(func(app *v1alpha1.App) bool {
+					return algorithms.Search(
+						0,
+						v1alpha1.RouteSpecFieldsSlice{route},
+						v1alpha1.RouteSpecFieldsSlice(app.Spec.Routes),
+					)
 				}),
 			)
 			if err != nil {
diff --git a/pkg/kf/commands/routes/map_route.go b/pkg/kf/commands/routes/map_route.go
index a0329ec..2278da2 100644
--- a/pkg/kf/commands/routes/map_route.go
+++ b/pkg/kf/commands/routes/map_route.go
@@ -21,7 +21,7 @@ import (
 	"github.com/google/kf/pkg/apis/kf/v1alpha1"
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
diff --git a/pkg/kf/commands/routes/proxy_route.go b/pkg/kf/commands/routes/proxy_route.go
index cca9679..e66e77e 100644
--- a/pkg/kf/commands/routes/proxy_route.go
+++ b/pkg/kf/commands/routes/proxy_route.go
@@ -21,7 +21,7 @@ import (
 
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/istio"
 	"github.com/spf13/cobra"
 )
diff --git a/pkg/kf/commands/routes/routes.go b/pkg/kf/commands/routes/routes.go
index 9c674d9..98e33ab 100644
--- a/pkg/kf/commands/routes/routes.go
+++ b/pkg/kf/commands/routes/routes.go
@@ -24,8 +24,8 @@ import (
 	"github.com/google/kf/pkg/kf/algorithms"
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/routeclaims"
 	"github.com/google/kf/pkg/kf/routes"
 	"github.com/spf13/cobra"
diff --git a/pkg/kf/commands/routes/unmap_route.go b/pkg/kf/commands/routes/unmap_route.go
index 24a837c..bf63c09 100644
--- a/pkg/kf/commands/routes/unmap_route.go
+++ b/pkg/kf/commands/routes/unmap_route.go
@@ -23,7 +23,7 @@ import (
 	"github.com/google/kf/pkg/kf/algorithms"
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 )
 
diff --git a/pkg/kf/commands/service-bindings/bind-service.go b/pkg/kf/commands/service-bindings/bind-service.go
index a87d551..432f6d9 100644
--- a/pkg/kf/commands/service-bindings/bind-service.go
+++ b/pkg/kf/commands/service-bindings/bind-service.go
@@ -19,7 +19,7 @@ import (
 	"fmt"
 
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	servicebindings "github.com/google/kf/pkg/kf/service-bindings"
 	"github.com/google/kf/pkg/kf/services"
 	"github.com/spf13/cobra"
diff --git a/pkg/kf/commands/service-bindings/bindings.go b/pkg/kf/commands/service-bindings/bindings.go
index 614f4dc..5b2e5e6 100644
--- a/pkg/kf/commands/service-bindings/bindings.go
+++ b/pkg/kf/commands/service-bindings/bindings.go
@@ -20,8 +20,8 @@ import (
 
 	kfv1alpha1 "github.com/google/kf/pkg/apis/kf/v1alpha1"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	servicebindings "github.com/google/kf/pkg/kf/service-bindings"
 	"github.com/spf13/cobra"
 )
diff --git a/pkg/kf/commands/service-bindings/unbind-service.go b/pkg/kf/commands/service-bindings/unbind-service.go
index 6312e44..d4fb3d9 100644
--- a/pkg/kf/commands/service-bindings/unbind-service.go
+++ b/pkg/kf/commands/service-bindings/unbind-service.go
@@ -18,7 +18,7 @@ import (
 	"fmt"
 
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	servicebindings "github.com/google/kf/pkg/kf/service-bindings"
 	"github.com/spf13/cobra"
 )
diff --git a/pkg/kf/commands/service-bindings/vcap_services.go b/pkg/kf/commands/service-bindings/vcap_services.go
index 97b3fa4..8a62e69 100644
--- a/pkg/kf/commands/service-bindings/vcap_services.go
+++ b/pkg/kf/commands/service-bindings/vcap_services.go
@@ -19,7 +19,7 @@ import (
 	"fmt"
 
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
diff --git a/pkg/kf/commands/service-brokers/create-service-broker.go b/pkg/kf/commands/service-brokers/create-service-broker.go
index 7054604..f936a67 100644
--- a/pkg/kf/commands/service-brokers/create-service-broker.go
+++ b/pkg/kf/commands/service-brokers/create-service-broker.go
@@ -19,7 +19,7 @@ import (
 
 	servicecatalogclient "github.com/google/kf/pkg/client/servicecatalog/clientset/versioned"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	servicecatalogv1beta1 "github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
diff --git a/pkg/kf/commands/service-brokers/delete-service-broker.go b/pkg/kf/commands/service-brokers/delete-service-broker.go
index 103f6c4..e716c44 100644
--- a/pkg/kf/commands/service-brokers/delete-service-broker.go
+++ b/pkg/kf/commands/service-brokers/delete-service-broker.go
@@ -21,7 +21,7 @@ import (
 	servicecatalogclient "github.com/google/kf/pkg/client/servicecatalog/clientset/versioned"
 	"github.com/google/kf/pkg/kf/commands/config"
 	installutil "github.com/google/kf/pkg/kf/commands/install/util"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/spf13/cobra"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
diff --git a/pkg/kf/commands/services/create-service.go b/pkg/kf/commands/services/create-service.go
index 9e903b1..035092a 100644
--- a/pkg/kf/commands/services/create-service.go
+++ b/pkg/kf/commands/services/create-service.go
@@ -20,8 +20,8 @@ import (
 
 	servicecatalogclient "github.com/google/kf/pkg/client/servicecatalog/clientset/versioned"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/services"
 	servicecatalogv1beta1 "github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
 	"github.com/spf13/cobra"
diff --git a/pkg/kf/commands/services/delete-service.go b/pkg/kf/commands/services/delete-service.go
index 8762ee1..150ff96 100644
--- a/pkg/kf/commands/services/delete-service.go
+++ b/pkg/kf/commands/services/delete-service.go
@@ -18,7 +18,7 @@ import (
 	"fmt"
 
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/services"
 	"github.com/spf13/cobra"
 )
diff --git a/pkg/kf/commands/services/marketplace.go b/pkg/kf/commands/services/marketplace.go
index a2b23c7..cb20a24 100644
--- a/pkg/kf/commands/services/marketplace.go
+++ b/pkg/kf/commands/services/marketplace.go
@@ -19,8 +19,8 @@ import (
 	"io"
 
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/services"
 	servicecatalog "github.com/poy/service-catalog/pkg/svcat/service-catalog"
 	"github.com/spf13/cobra"
diff --git a/pkg/kf/commands/services/service.go b/pkg/kf/commands/services/service.go
index 18d2191..2ca4670 100644
--- a/pkg/kf/commands/services/service.go
+++ b/pkg/kf/commands/services/service.go
@@ -16,8 +16,8 @@ package services
 
 import (
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/services"
 	"github.com/spf13/cobra"
 )
diff --git a/pkg/kf/commands/services/services.go b/pkg/kf/commands/services/services.go
index e2251c4..ba98e59 100644
--- a/pkg/kf/commands/services/services.go
+++ b/pkg/kf/commands/services/services.go
@@ -22,8 +22,8 @@ import (
 	"github.com/google/kf/pkg/apis/kf/v1alpha1"
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
 	"github.com/google/kf/pkg/kf/describe"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/services"
 	"github.com/spf13/cobra"
 )
diff --git a/pkg/kf/commands/spaces/delete.go b/pkg/kf/commands/spaces/delete.go
index dfa821c..fddaa8a 100644
--- a/pkg/kf/commands/spaces/delete.go
+++ b/pkg/kf/commands/spaces/delete.go
@@ -19,7 +19,7 @@ import (
 
 	"github.com/google/kf/pkg/kf/commands/completion"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/spaces"
 	"github.com/spf13/cobra"
 )
diff --git a/pkg/kf/internal/kf/utils.go b/pkg/kf/internal/kf/utils.go
deleted file mode 100644
index 500cb09..0000000
--- a/pkg/kf/internal/kf/utils.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package kf
-
-// ConfigErr is used to indicate that the returned error is due to a user's
-// invalid configuration.
-type ConfigErr struct {
-	// Reason holds the error message.
-	Reason string
-}
-
-// Error implements error.
-func (e ConfigErr) Error() string {
-	return e.Reason
-}
-
-// ConfigError returns true if the error is due to user error.
-func ConfigError(err error) bool {
-	_, ok := err.(ConfigErr)
-	return ok
-}
diff --git a/pkg/kf/internal/tools/clientgen/common-options.yml b/pkg/kf/internal/tools/clientgen/common-options.yml
index 15634d1..6e0b2ca 100644
--- a/pkg/kf/internal/tools/clientgen/common-options.yml
+++ b/pkg/kf/internal/tools/clientgen/common-options.yml
@@ -29,17 +29,11 @@ configs:
   - name: ForegroundDeletion
     type: bool
     description: If the resource should be deleted in the foreground.
-  - name: DeleteImmediately
-    type: bool
-    description: If the resource should be deleted immediately.
 - name: List
   options:
-  - name: labelSelector
-    type: "map[string]string"
-    description: A label selector.
   - name: fieldSelector
     type: "map[string]string"
     description: A selector on the resource's fields.
-  - name: filters
-    type: "[]Predicate"
-    description: Additional filters to apply.
+  - name: filter
+    type: "Predicate"
+    description: Filter to apply.
diff --git a/pkg/kf/internal/tools/clientgen/tmplclient.go b/pkg/kf/internal/tools/clientgen/tmplclient.go
index 2017452..f7e1b59 100644
--- a/pkg/kf/internal/tools/clientgen/tmplclient.go
+++ b/pkg/kf/internal/tools/clientgen/tmplclient.go
@@ -54,15 +54,15 @@ type Client interface {
 
 type coreClient struct {
 	kclient {{.ClientType}}
-	upsertMutate MutatorList
+	upsertMutate Mutator
 }
 
 func (core *coreClient) preprocessUpsert(obj *{{.Type}}) error {
-	if err := core.upsertMutate.Apply(obj); err != nil {
-		return err
+	if core.upsertMutate == nil {
+		return nil
 	}
 
-	return nil
+	return core.upsertMutate(obj)
 }
 
 // Create inserts the given {{.Type}} into the cluster.
@@ -133,10 +133,6 @@ func (cfg deleteConfig) ToDeleteOptions() (*metav1.DeleteOptions) {
 		resp.PropagationPolicy = &propigationPolicy
 	}
 
-	if cfg.DeleteImmediately {
-		resp.GracePeriodSeconds = new(int64)
-	}
-
 	return &resp
 }
 
@@ -150,7 +146,11 @@ func (core *coreClient) List({{ $nssig }} opts ...ListOption) ([]{{.Type}}, erro
 		return nil, fmt.Errorf("couldn't list {{.CF.Name}}s: %v", err)
 	}
 
-	return List(res.Items).Filter(AllPredicate(cfg.filters...)), nil
+	if cfg.filter == nil {
+		return res.Items, nil
+	}
+
+	return List(res.Items).Filter(cfg.filter), nil
 }
 
 func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
@@ -158,10 +158,6 @@ func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
 		resp.FieldSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.fieldSelector))
 	}
 
-	if cfg.labelSelector != nil {
-		resp.LabelSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.labelSelector))
-	}
-
 	return
 }
 
diff --git a/pkg/kf/internal/tools/clientgen/tmplfunctional.go b/pkg/kf/internal/tools/clientgen/tmplfunctional.go
index 62d46aa..361c76d 100644
--- a/pkg/kf/internal/tools/clientgen/tmplfunctional.go
+++ b/pkg/kf/internal/tools/clientgen/tmplfunctional.go
@@ -37,19 +37,6 @@ const (
 // Predicate is a boolean function for a {{.Type}}.
 type Predicate func(*{{.Type}}) bool
 
-// AllPredicate is a predicate that passes if all children pass.
-func AllPredicate(children ...Predicate) Predicate {
-	return func(obj *{{.Type}}) bool {
-		for _, filter := range children {
-			if !filter(obj) {
-				return false
-			}
-		}
-
-		return true
-	}
-}
-
 // Mutator is a function that changes {{.Type}}.
 type Mutator func(*{{.Type}}) error
 
@@ -103,48 +90,4 @@ func (list List) Filter(filter Predicate) (out List) {
 	return
 }
 
-// MutatorList is a list of mutators.
-type MutatorList []Mutator
-
-// Apply passes the given value to each of the mutators in the list failing if
-// one of them returns an error.
-func (list MutatorList) Apply(svc *{{.Type}}) error {
-	for _, mutator := range list {
-		if err := mutator(svc); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// LabelSetMutator creates a mutator that sets the given labels on the object.
-func LabelSetMutator(labels map[string]string) Mutator {
-	return func(obj *{{.Type}}) error {
-		if obj.Labels == nil {
-			obj.Labels = make(map[string]string)
-		}
-
-		for key, value := range labels {
-			obj.Labels[key] = value
-		}
-
-		return nil
-	}
-}
-
-// LabelEqualsPredicate validates that the given label exists exactly on the object.
-func LabelEqualsPredicate(key, value string) Predicate {
-	return func(obj *{{.Type}}) bool {
-		return obj.Labels[key] == value
-	}
-}
-
-// LabelsContainsPredicate validates that the given label exists on the object.
-func LabelsContainsPredicate(key string) Predicate {
-	return func(obj *{{.Type}}) bool {
-		_, ok := obj.Labels[key]
-		return ok
-	}
-}
 `))
diff --git a/pkg/kf/commands/utils/utils.go b/pkg/kf/internal/utils/cli/utils.go
similarity index 92%
rename from pkg/kf/commands/utils/utils.go
rename to pkg/kf/internal/utils/cli/utils.go
index 4a8b8a9..7c60bc3 100644
--- a/pkg/kf/commands/utils/utils.go
+++ b/pkg/kf/internal/utils/cli/utils.go
@@ -41,6 +41,24 @@ const (
 	AsyncLogSuffix = "\n(This is an asynchronous operation. See https://github.com/google/kf/issues/599 for updates on work to support synchronous commands.)\n"
 )
 
+// ConfigErr is used to indicate that the returned error is due to a user's
+// invalid configuration.
+type ConfigErr struct {
+	// Reason holds the error message.
+	Reason string
+}
+
+// Error implements error.
+func (e ConfigErr) Error() string {
+	return e.Reason
+}
+
+// ConfigError returns true if the error is due to user error.
+func ConfigError(err error) bool {
+	_, ok := err.(ConfigErr)
+	return ok
+}
+
 // KfParams stores everything needed to interact with the user and Knative.
 type KfParams struct {
 	Output    io.Writer
diff --git a/pkg/kf/manifest/manifest.go b/pkg/kf/manifest/manifest.go
index 7c599d7..177513d 100644
--- a/pkg/kf/manifest/manifest.go
+++ b/pkg/kf/manifest/manifest.go
@@ -15,6 +15,7 @@
 package manifest
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"io"
@@ -43,6 +44,11 @@ type Application struct {
 	CPU             string            `json:"cpu,omitempty"`
 	Instances       *int              `json:"instances,omitempty"`
 
+	// Container command configuration
+	Entrypoint string   `json:"entrypoint,omitempty"`
+	Args       []string `json:"args,omitempty"`
+	Command    string   `json:"command,omitempty"`
+
 	// TODO(#95): These aren't CF proper. How do we expose these in the
 	// manifest?
 	MinScale *int `json:"min-scale,omitempty"`
@@ -198,6 +204,10 @@ for more info.
 		app.Env = envutil.EnvVarsToMap(envutil.DeduplicateEnvVars(combined))
 	}
 
+	if err := app.Validate(context.Background()); err.Error() != "" {
+		return err
+	}
+
 	return nil
 }
 
@@ -211,3 +221,25 @@ func (app *Application) Buildpack() string {
 
 	return app.LegacyBuildpack
 }
+
+// CommandEntrypoint gets an override for the entrypoint of the container.
+func (app *Application) CommandEntrypoint() []string {
+	if app.Entrypoint != "" {
+		return []string{app.Entrypoint}
+	}
+
+	return nil
+}
+
+// CommandArgs returns the container args if they're defined or nil.
+func (app *Application) CommandArgs() []string {
+	if len(app.Args) > 0 {
+		return app.Args
+	}
+
+	if app.Command != "" {
+		return []string{app.Command}
+	}
+
+	return nil
+}
diff --git a/pkg/kf/manifest/manifest_validation.go b/pkg/kf/manifest/manifest_validation.go
new file mode 100644
index 0000000..341c109
--- /dev/null
+++ b/pkg/kf/manifest/manifest_validation.go
@@ -0,0 +1,44 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the License);
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an AS IS BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package manifest
+
+import (
+	"context"
+
+	"knative.dev/pkg/apis"
+)
+
+// Validate checks for errors in the Application's fields.
+func (app *Application) Validate(ctx context.Context) (errs *apis.FieldError) {
+	// validate container execution
+	if app.Command != "" {
+		if len(app.Args) > 0 {
+			errs = errs.Also(apis.ErrMultipleOneOf("command", "args"))
+		}
+
+		if app.Entrypoint != "" {
+			errs = errs.Also(apis.ErrMultipleOneOf("entrypoint", "command"))
+		}
+	}
+
+	// validate buildpacks
+	if len(app.Buildpacks) > 0 {
+		if app.LegacyBuildpack != "" {
+			errs = errs.Also(apis.ErrMultipleOneOf("buildpack", "buildpacks"))
+		}
+	}
+
+	return
+}
diff --git a/pkg/kf/routeclaims/client.go b/pkg/kf/routeclaims/client.go
index d3650bb..4120693 100644
--- a/pkg/kf/routeclaims/client.go
+++ b/pkg/kf/routeclaims/client.go
@@ -25,7 +25,6 @@ type ClientExtension interface {
 // NewClient creates a new RouteClaim client.
 func NewClient(kclient kf.KfV1alpha1Interface) Client {
 	return &coreClient{
-		kclient:      kclient,
-		upsertMutate: MutatorList{},
+		kclient: kclient,
 	}
 }
diff --git a/pkg/kf/routeclaims/zz_generated.client.go b/pkg/kf/routeclaims/zz_generated.client.go
index 2618cb1..b03bfe7 100644
--- a/pkg/kf/routeclaims/zz_generated.client.go
+++ b/pkg/kf/routeclaims/zz_generated.client.go
@@ -51,19 +51,6 @@ const (
 // Predicate is a boolean function for a v1alpha1.RouteClaim.
 type Predicate func(*v1alpha1.RouteClaim) bool
 
-// AllPredicate is a predicate that passes if all children pass.
-func AllPredicate(children ...Predicate) Predicate {
-	return func(obj *v1alpha1.RouteClaim) bool {
-		for _, filter := range children {
-			if !filter(obj) {
-				return false
-			}
-		}
-
-		return true
-	}
-}
-
 // Mutator is a function that changes v1alpha1.RouteClaim.
 type Mutator func(*v1alpha1.RouteClaim) error
 
@@ -117,51 +104,6 @@ func (list List) Filter(filter Predicate) (out List) {
 	return
 }
 
-// MutatorList is a list of mutators.
-type MutatorList []Mutator
-
-// Apply passes the given value to each of the mutators in the list failing if
-// one of them returns an error.
-func (list MutatorList) Apply(svc *v1alpha1.RouteClaim) error {
-	for _, mutator := range list {
-		if err := mutator(svc); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// LabelSetMutator creates a mutator that sets the given labels on the object.
-func LabelSetMutator(labels map[string]string) Mutator {
-	return func(obj *v1alpha1.RouteClaim) error {
-		if obj.Labels == nil {
-			obj.Labels = make(map[string]string)
-		}
-
-		for key, value := range labels {
-			obj.Labels[key] = value
-		}
-
-		return nil
-	}
-}
-
-// LabelEqualsPredicate validates that the given label exists exactly on the object.
-func LabelEqualsPredicate(key, value string) Predicate {
-	return func(obj *v1alpha1.RouteClaim) bool {
-		return obj.Labels[key] == value
-	}
-}
-
-// LabelsContainsPredicate validates that the given label exists on the object.
-func LabelsContainsPredicate(key string) Predicate {
-	return func(obj *v1alpha1.RouteClaim) bool {
-		_, ok := obj.Labels[key]
-		return ok
-	}
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Client
 ////////////////////////////////////////////////////////////////////////////////
@@ -184,15 +126,15 @@ type Client interface {
 
 type coreClient struct {
 	kclient      cv1alpha1.RouteClaimsGetter
-	upsertMutate MutatorList
+	upsertMutate Mutator
 }
 
 func (core *coreClient) preprocessUpsert(obj *v1alpha1.RouteClaim) error {
-	if err := core.upsertMutate.Apply(obj); err != nil {
-		return err
+	if core.upsertMutate == nil {
+		return nil
 	}
 
-	return nil
+	return core.upsertMutate(obj)
 }
 
 // Create inserts the given v1alpha1.RouteClaim into the cluster.
@@ -263,10 +205,6 @@ func (cfg deleteConfig) ToDeleteOptions() *metav1.DeleteOptions {
 		resp.PropagationPolicy = &propigationPolicy
 	}
 
-	if cfg.DeleteImmediately {
-		resp.GracePeriodSeconds = new(int64)
-	}
-
 	return &resp
 }
 
@@ -280,7 +218,11 @@ func (core *coreClient) List(namespace string, opts ...ListOption) ([]v1alpha1.R
 		return nil, fmt.Errorf("couldn't list RouteClaims: %v", err)
 	}
 
-	return List(res.Items).Filter(AllPredicate(cfg.filters...)), nil
+	if cfg.filter == nil {
+		return res.Items, nil
+	}
+
+	return List(res.Items).Filter(cfg.filter), nil
 }
 
 func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
@@ -288,10 +230,6 @@ func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
 		resp.FieldSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.fieldSelector))
 	}
 
-	if cfg.labelSelector != nil {
-		resp.LabelSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.labelSelector))
-	}
-
 	return
 }
 
diff --git a/pkg/kf/routeclaims/zz_generated.clientoptions.go b/pkg/kf/routeclaims/zz_generated.clientoptions.go
index 6c401bc..ac60308 100644
--- a/pkg/kf/routeclaims/zz_generated.clientoptions.go
+++ b/pkg/kf/routeclaims/zz_generated.clientoptions.go
@@ -119,8 +119,6 @@ func GetOptionDefaults() GetOptions {
 }
 
 type deleteConfig struct {
-	// DeleteImmediately is If the resource should be deleted immediately.
-	DeleteImmediately bool
 	// ForegroundDeletion is If the resource should be deleted in the foreground.
 	ForegroundDeletion bool
 }
@@ -151,25 +149,12 @@ func (opts DeleteOptions) Extend(other DeleteOptions) DeleteOptions {
 	return out
 }
 
-// DeleteImmediately returns the last set value for DeleteImmediately or the empty value
-// if not set.
-func (opts DeleteOptions) DeleteImmediately() bool {
-	return opts.toConfig().DeleteImmediately
-}
-
 // ForegroundDeletion returns the last set value for ForegroundDeletion or the empty value
 // if not set.
 func (opts DeleteOptions) ForegroundDeletion() bool {
 	return opts.toConfig().ForegroundDeletion
 }
 
-// WithDeleteDeleteImmediately creates an Option that sets If the resource should be deleted immediately.
-func WithDeleteDeleteImmediately(val bool) DeleteOption {
-	return func(cfg *deleteConfig) {
-		cfg.DeleteImmediately = val
-	}
-}
-
 // WithDeleteForegroundDeletion creates an Option that sets If the resource should be deleted in the foreground.
 func WithDeleteForegroundDeletion(val bool) DeleteOption {
 	return func(cfg *deleteConfig) {
@@ -185,10 +170,8 @@ func DeleteOptionDefaults() DeleteOptions {
 type listConfig struct {
 	// fieldSelector is A selector on the resource's fields.
 	fieldSelector map[string]string
-	// filters is Additional filters to apply.
-	filters []Predicate
-	// labelSelector is A label selector.
-	labelSelector map[string]string
+	// filter is Filter to apply.
+	filter Predicate
 }
 
 // ListOption is a single option for configuring a listConfig
@@ -223,16 +206,10 @@ func (opts ListOptions) fieldSelector() map[string]string {
 	return opts.toConfig().fieldSelector
 }
 
-// filters returns the last set value for filters or the empty value
+// filter returns the last set value for filter or the empty value
 // if not set.
-func (opts ListOptions) filters() []Predicate {
-	return opts.toConfig().filters
-}
-
-// labelSelector returns the last set value for labelSelector or the empty value
-// if not set.
-func (opts ListOptions) labelSelector() map[string]string {
-	return opts.toConfig().labelSelector
+func (opts ListOptions) filter() Predicate {
+	return opts.toConfig().filter
 }
 
 // WithListFieldSelector creates an Option that sets A selector on the resource's fields.
@@ -242,17 +219,10 @@ func WithListFieldSelector(val map[string]string) ListOption {
 	}
 }
 
-// WithListFilters creates an Option that sets Additional filters to apply.
-func WithListFilters(val []Predicate) ListOption {
-	return func(cfg *listConfig) {
-		cfg.filters = val
-	}
-}
-
-// WithListLabelSelector creates an Option that sets A label selector.
-func WithListLabelSelector(val map[string]string) ListOption {
+// WithListFilter creates an Option that sets Filter to apply.
+func WithListFilter(val Predicate) ListOption {
 	return func(cfg *listConfig) {
-		cfg.labelSelector = val
+		cfg.filter = val
 	}
 }
 
diff --git a/pkg/kf/routes/client.go b/pkg/kf/routes/client.go
index 6caba9e..9ed1d73 100644
--- a/pkg/kf/routes/client.go
+++ b/pkg/kf/routes/client.go
@@ -25,7 +25,6 @@ type ClientExtension interface {
 // NewClient creates a new route client.
 func NewClient(kclient kf.KfV1alpha1Interface) Client {
 	return &coreClient{
-		kclient:      kclient,
-		upsertMutate: MutatorList{},
+		kclient: kclient,
 	}
 }
diff --git a/pkg/kf/routes/zz_generated.client.go b/pkg/kf/routes/zz_generated.client.go
index d37dbb4..a19c97b 100644
--- a/pkg/kf/routes/zz_generated.client.go
+++ b/pkg/kf/routes/zz_generated.client.go
@@ -51,19 +51,6 @@ const (
 // Predicate is a boolean function for a v1alpha1.Route.
 type Predicate func(*v1alpha1.Route) bool
 
-// AllPredicate is a predicate that passes if all children pass.
-func AllPredicate(children ...Predicate) Predicate {
-	return func(obj *v1alpha1.Route) bool {
-		for _, filter := range children {
-			if !filter(obj) {
-				return false
-			}
-		}
-
-		return true
-	}
-}
-
 // Mutator is a function that changes v1alpha1.Route.
 type Mutator func(*v1alpha1.Route) error
 
@@ -117,51 +104,6 @@ func (list List) Filter(filter Predicate) (out List) {
 	return
 }
 
-// MutatorList is a list of mutators.
-type MutatorList []Mutator
-
-// Apply passes the given value to each of the mutators in the list failing if
-// one of them returns an error.
-func (list MutatorList) Apply(svc *v1alpha1.Route) error {
-	for _, mutator := range list {
-		if err := mutator(svc); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// LabelSetMutator creates a mutator that sets the given labels on the object.
-func LabelSetMutator(labels map[string]string) Mutator {
-	return func(obj *v1alpha1.Route) error {
-		if obj.Labels == nil {
-			obj.Labels = make(map[string]string)
-		}
-
-		for key, value := range labels {
-			obj.Labels[key] = value
-		}
-
-		return nil
-	}
-}
-
-// LabelEqualsPredicate validates that the given label exists exactly on the object.
-func LabelEqualsPredicate(key, value string) Predicate {
-	return func(obj *v1alpha1.Route) bool {
-		return obj.Labels[key] == value
-	}
-}
-
-// LabelsContainsPredicate validates that the given label exists on the object.
-func LabelsContainsPredicate(key string) Predicate {
-	return func(obj *v1alpha1.Route) bool {
-		_, ok := obj.Labels[key]
-		return ok
-	}
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Client
 ////////////////////////////////////////////////////////////////////////////////
@@ -184,15 +126,15 @@ type Client interface {
 
 type coreClient struct {
 	kclient      cv1alpha1.RoutesGetter
-	upsertMutate MutatorList
+	upsertMutate Mutator
 }
 
 func (core *coreClient) preprocessUpsert(obj *v1alpha1.Route) error {
-	if err := core.upsertMutate.Apply(obj); err != nil {
-		return err
+	if core.upsertMutate == nil {
+		return nil
 	}
 
-	return nil
+	return core.upsertMutate(obj)
 }
 
 // Create inserts the given v1alpha1.Route into the cluster.
@@ -263,10 +205,6 @@ func (cfg deleteConfig) ToDeleteOptions() *metav1.DeleteOptions {
 		resp.PropagationPolicy = &propigationPolicy
 	}
 
-	if cfg.DeleteImmediately {
-		resp.GracePeriodSeconds = new(int64)
-	}
-
 	return &resp
 }
 
@@ -280,7 +218,11 @@ func (core *coreClient) List(namespace string, opts ...ListOption) ([]v1alpha1.R
 		return nil, fmt.Errorf("couldn't list Routes: %v", err)
 	}
 
-	return List(res.Items).Filter(AllPredicate(cfg.filters...)), nil
+	if cfg.filter == nil {
+		return res.Items, nil
+	}
+
+	return List(res.Items).Filter(cfg.filter), nil
 }
 
 func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
@@ -288,10 +230,6 @@ func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
 		resp.FieldSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.fieldSelector))
 	}
 
-	if cfg.labelSelector != nil {
-		resp.LabelSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.labelSelector))
-	}
-
 	return
 }
 
diff --git a/pkg/kf/routes/zz_generated.clientoptions.go b/pkg/kf/routes/zz_generated.clientoptions.go
index fca2bf8..6e993d2 100644
--- a/pkg/kf/routes/zz_generated.clientoptions.go
+++ b/pkg/kf/routes/zz_generated.clientoptions.go
@@ -119,8 +119,6 @@ func GetOptionDefaults() GetOptions {
 }
 
 type deleteConfig struct {
-	// DeleteImmediately is If the resource should be deleted immediately.
-	DeleteImmediately bool
 	// ForegroundDeletion is If the resource should be deleted in the foreground.
 	ForegroundDeletion bool
 }
@@ -151,25 +149,12 @@ func (opts DeleteOptions) Extend(other DeleteOptions) DeleteOptions {
 	return out
 }
 
-// DeleteImmediately returns the last set value for DeleteImmediately or the empty value
-// if not set.
-func (opts DeleteOptions) DeleteImmediately() bool {
-	return opts.toConfig().DeleteImmediately
-}
-
 // ForegroundDeletion returns the last set value for ForegroundDeletion or the empty value
 // if not set.
 func (opts DeleteOptions) ForegroundDeletion() bool {
 	return opts.toConfig().ForegroundDeletion
 }
 
-// WithDeleteDeleteImmediately creates an Option that sets If the resource should be deleted immediately.
-func WithDeleteDeleteImmediately(val bool) DeleteOption {
-	return func(cfg *deleteConfig) {
-		cfg.DeleteImmediately = val
-	}
-}
-
 // WithDeleteForegroundDeletion creates an Option that sets If the resource should be deleted in the foreground.
 func WithDeleteForegroundDeletion(val bool) DeleteOption {
 	return func(cfg *deleteConfig) {
@@ -185,10 +170,8 @@ func DeleteOptionDefaults() DeleteOptions {
 type listConfig struct {
 	// fieldSelector is A selector on the resource's fields.
 	fieldSelector map[string]string
-	// filters is Additional filters to apply.
-	filters []Predicate
-	// labelSelector is A label selector.
-	labelSelector map[string]string
+	// filter is Filter to apply.
+	filter Predicate
 }
 
 // ListOption is a single option for configuring a listConfig
@@ -223,16 +206,10 @@ func (opts ListOptions) fieldSelector() map[string]string {
 	return opts.toConfig().fieldSelector
 }
 
-// filters returns the last set value for filters or the empty value
+// filter returns the last set value for filter or the empty value
 // if not set.
-func (opts ListOptions) filters() []Predicate {
-	return opts.toConfig().filters
-}
-
-// labelSelector returns the last set value for labelSelector or the empty value
-// if not set.
-func (opts ListOptions) labelSelector() map[string]string {
-	return opts.toConfig().labelSelector
+func (opts ListOptions) filter() Predicate {
+	return opts.toConfig().filter
 }
 
 // WithListFieldSelector creates an Option that sets A selector on the resource's fields.
@@ -242,17 +219,10 @@ func WithListFieldSelector(val map[string]string) ListOption {
 	}
 }
 
-// WithListFilters creates an Option that sets Additional filters to apply.
-func WithListFilters(val []Predicate) ListOption {
-	return func(cfg *listConfig) {
-		cfg.filters = val
-	}
-}
-
-// WithListLabelSelector creates an Option that sets A label selector.
-func WithListLabelSelector(val map[string]string) ListOption {
+// WithListFilter creates an Option that sets Filter to apply.
+func WithListFilter(val Predicate) ListOption {
 	return func(cfg *listConfig) {
-		cfg.labelSelector = val
+		cfg.filter = val
 	}
 }
 
diff --git a/pkg/kf/sources/client.go b/pkg/kf/sources/client.go
index a6ec2cb..4832935 100644
--- a/pkg/kf/sources/client.go
+++ b/pkg/kf/sources/client.go
@@ -44,8 +44,7 @@ type sourcesClient struct {
 func NewClient(kclient cv1alpha1.SourcesGetter, buildTailer BuildTailer) Client {
 	return &sourcesClient{
 		coreClient: coreClient{
-			kclient:      kclient,
-			upsertMutate: MutatorList{},
+			kclient: kclient,
 		},
 		buildTailer: buildTailer,
 	}
diff --git a/pkg/kf/sources/zz_generated.client.go b/pkg/kf/sources/zz_generated.client.go
index dedd8bd..b0e02f2 100644
--- a/pkg/kf/sources/zz_generated.client.go
+++ b/pkg/kf/sources/zz_generated.client.go
@@ -51,19 +51,6 @@ const (
 // Predicate is a boolean function for a v1alpha1.Source.
 type Predicate func(*v1alpha1.Source) bool
 
-// AllPredicate is a predicate that passes if all children pass.
-func AllPredicate(children ...Predicate) Predicate {
-	return func(obj *v1alpha1.Source) bool {
-		for _, filter := range children {
-			if !filter(obj) {
-				return false
-			}
-		}
-
-		return true
-	}
-}
-
 // Mutator is a function that changes v1alpha1.Source.
 type Mutator func(*v1alpha1.Source) error
 
@@ -117,51 +104,6 @@ func (list List) Filter(filter Predicate) (out List) {
 	return
 }
 
-// MutatorList is a list of mutators.
-type MutatorList []Mutator
-
-// Apply passes the given value to each of the mutators in the list failing if
-// one of them returns an error.
-func (list MutatorList) Apply(svc *v1alpha1.Source) error {
-	for _, mutator := range list {
-		if err := mutator(svc); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// LabelSetMutator creates a mutator that sets the given labels on the object.
-func LabelSetMutator(labels map[string]string) Mutator {
-	return func(obj *v1alpha1.Source) error {
-		if obj.Labels == nil {
-			obj.Labels = make(map[string]string)
-		}
-
-		for key, value := range labels {
-			obj.Labels[key] = value
-		}
-
-		return nil
-	}
-}
-
-// LabelEqualsPredicate validates that the given label exists exactly on the object.
-func LabelEqualsPredicate(key, value string) Predicate {
-	return func(obj *v1alpha1.Source) bool {
-		return obj.Labels[key] == value
-	}
-}
-
-// LabelsContainsPredicate validates that the given label exists on the object.
-func LabelsContainsPredicate(key string) Predicate {
-	return func(obj *v1alpha1.Source) bool {
-		_, ok := obj.Labels[key]
-		return ok
-	}
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Client
 ////////////////////////////////////////////////////////////////////////////////
@@ -184,15 +126,15 @@ type Client interface {
 
 type coreClient struct {
 	kclient      cv1alpha1.SourcesGetter
-	upsertMutate MutatorList
+	upsertMutate Mutator
 }
 
 func (core *coreClient) preprocessUpsert(obj *v1alpha1.Source) error {
-	if err := core.upsertMutate.Apply(obj); err != nil {
-		return err
+	if core.upsertMutate == nil {
+		return nil
 	}
 
-	return nil
+	return core.upsertMutate(obj)
 }
 
 // Create inserts the given v1alpha1.Source into the cluster.
@@ -263,10 +205,6 @@ func (cfg deleteConfig) ToDeleteOptions() *metav1.DeleteOptions {
 		resp.PropagationPolicy = &propigationPolicy
 	}
 
-	if cfg.DeleteImmediately {
-		resp.GracePeriodSeconds = new(int64)
-	}
-
 	return &resp
 }
 
@@ -280,7 +218,11 @@ func (core *coreClient) List(namespace string, opts ...ListOption) ([]v1alpha1.S
 		return nil, fmt.Errorf("couldn't list Builds: %v", err)
 	}
 
-	return List(res.Items).Filter(AllPredicate(cfg.filters...)), nil
+	if cfg.filter == nil {
+		return res.Items, nil
+	}
+
+	return List(res.Items).Filter(cfg.filter), nil
 }
 
 func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
@@ -288,10 +230,6 @@ func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
 		resp.FieldSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.fieldSelector))
 	}
 
-	if cfg.labelSelector != nil {
-		resp.LabelSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.labelSelector))
-	}
-
 	return
 }
 
diff --git a/pkg/kf/sources/zz_generated.clientoptions.go b/pkg/kf/sources/zz_generated.clientoptions.go
index bfac3f7..1ec8196 100644
--- a/pkg/kf/sources/zz_generated.clientoptions.go
+++ b/pkg/kf/sources/zz_generated.clientoptions.go
@@ -119,8 +119,6 @@ func GetOptionDefaults() GetOptions {
 }
 
 type deleteConfig struct {
-	// DeleteImmediately is If the resource should be deleted immediately.
-	DeleteImmediately bool
 	// ForegroundDeletion is If the resource should be deleted in the foreground.
 	ForegroundDeletion bool
 }
@@ -151,25 +149,12 @@ func (opts DeleteOptions) Extend(other DeleteOptions) DeleteOptions {
 	return out
 }
 
-// DeleteImmediately returns the last set value for DeleteImmediately or the empty value
-// if not set.
-func (opts DeleteOptions) DeleteImmediately() bool {
-	return opts.toConfig().DeleteImmediately
-}
-
 // ForegroundDeletion returns the last set value for ForegroundDeletion or the empty value
 // if not set.
 func (opts DeleteOptions) ForegroundDeletion() bool {
 	return opts.toConfig().ForegroundDeletion
 }
 
-// WithDeleteDeleteImmediately creates an Option that sets If the resource should be deleted immediately.
-func WithDeleteDeleteImmediately(val bool) DeleteOption {
-	return func(cfg *deleteConfig) {
-		cfg.DeleteImmediately = val
-	}
-}
-
 // WithDeleteForegroundDeletion creates an Option that sets If the resource should be deleted in the foreground.
 func WithDeleteForegroundDeletion(val bool) DeleteOption {
 	return func(cfg *deleteConfig) {
@@ -185,10 +170,8 @@ func DeleteOptionDefaults() DeleteOptions {
 type listConfig struct {
 	// fieldSelector is A selector on the resource's fields.
 	fieldSelector map[string]string
-	// filters is Additional filters to apply.
-	filters []Predicate
-	// labelSelector is A label selector.
-	labelSelector map[string]string
+	// filter is Filter to apply.
+	filter Predicate
 }
 
 // ListOption is a single option for configuring a listConfig
@@ -223,16 +206,10 @@ func (opts ListOptions) fieldSelector() map[string]string {
 	return opts.toConfig().fieldSelector
 }
 
-// filters returns the last set value for filters or the empty value
+// filter returns the last set value for filter or the empty value
 // if not set.
-func (opts ListOptions) filters() []Predicate {
-	return opts.toConfig().filters
-}
-
-// labelSelector returns the last set value for labelSelector or the empty value
-// if not set.
-func (opts ListOptions) labelSelector() map[string]string {
-	return opts.toConfig().labelSelector
+func (opts ListOptions) filter() Predicate {
+	return opts.toConfig().filter
 }
 
 // WithListFieldSelector creates an Option that sets A selector on the resource's fields.
@@ -242,17 +219,10 @@ func WithListFieldSelector(val map[string]string) ListOption {
 	}
 }
 
-// WithListFilters creates an Option that sets Additional filters to apply.
-func WithListFilters(val []Predicate) ListOption {
-	return func(cfg *listConfig) {
-		cfg.filters = val
-	}
-}
-
-// WithListLabelSelector creates an Option that sets A label selector.
-func WithListLabelSelector(val map[string]string) ListOption {
+// WithListFilter creates an Option that sets Filter to apply.
+func WithListFilter(val Predicate) ListOption {
 	return func(cfg *listConfig) {
-		cfg.labelSelector = val
+		cfg.filter = val
 	}
 }
 
diff --git a/pkg/kf/spaces/client.go b/pkg/kf/spaces/client.go
index 92c29b5..7f93e3b 100644
--- a/pkg/kf/spaces/client.go
+++ b/pkg/kf/spaces/client.go
@@ -26,8 +26,7 @@ type ClientExtension interface {
 // NewClient creates a new space client.
 func NewClient(kclient cv1alpha1.SpacesGetter) Client {
 	return &coreClient{
-		kclient:      kclient,
-		upsertMutate: MutatorList{},
+		kclient: kclient,
 	}
 }
 
diff --git a/pkg/kf/spaces/zz_generated.client.go b/pkg/kf/spaces/zz_generated.client.go
index dad8408..e5532b4 100644
--- a/pkg/kf/spaces/zz_generated.client.go
+++ b/pkg/kf/spaces/zz_generated.client.go
@@ -51,19 +51,6 @@ const (
 // Predicate is a boolean function for a v1alpha1.Space.
 type Predicate func(*v1alpha1.Space) bool
 
-// AllPredicate is a predicate that passes if all children pass.
-func AllPredicate(children ...Predicate) Predicate {
-	return func(obj *v1alpha1.Space) bool {
-		for _, filter := range children {
-			if !filter(obj) {
-				return false
-			}
-		}
-
-		return true
-	}
-}
-
 // Mutator is a function that changes v1alpha1.Space.
 type Mutator func(*v1alpha1.Space) error
 
@@ -117,51 +104,6 @@ func (list List) Filter(filter Predicate) (out List) {
 	return
 }
 
-// MutatorList is a list of mutators.
-type MutatorList []Mutator
-
-// Apply passes the given value to each of the mutators in the list failing if
-// one of them returns an error.
-func (list MutatorList) Apply(svc *v1alpha1.Space) error {
-	for _, mutator := range list {
-		if err := mutator(svc); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// LabelSetMutator creates a mutator that sets the given labels on the object.
-func LabelSetMutator(labels map[string]string) Mutator {
-	return func(obj *v1alpha1.Space) error {
-		if obj.Labels == nil {
-			obj.Labels = make(map[string]string)
-		}
-
-		for key, value := range labels {
-			obj.Labels[key] = value
-		}
-
-		return nil
-	}
-}
-
-// LabelEqualsPredicate validates that the given label exists exactly on the object.
-func LabelEqualsPredicate(key, value string) Predicate {
-	return func(obj *v1alpha1.Space) bool {
-		return obj.Labels[key] == value
-	}
-}
-
-// LabelsContainsPredicate validates that the given label exists on the object.
-func LabelsContainsPredicate(key string) Predicate {
-	return func(obj *v1alpha1.Space) bool {
-		_, ok := obj.Labels[key]
-		return ok
-	}
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Client
 ////////////////////////////////////////////////////////////////////////////////
@@ -184,15 +126,15 @@ type Client interface {
 
 type coreClient struct {
 	kclient      cv1alpha1.SpacesGetter
-	upsertMutate MutatorList
+	upsertMutate Mutator
 }
 
 func (core *coreClient) preprocessUpsert(obj *v1alpha1.Space) error {
-	if err := core.upsertMutate.Apply(obj); err != nil {
-		return err
+	if core.upsertMutate == nil {
+		return nil
 	}
 
-	return nil
+	return core.upsertMutate(obj)
 }
 
 // Create inserts the given v1alpha1.Space into the cluster.
@@ -263,10 +205,6 @@ func (cfg deleteConfig) ToDeleteOptions() *metav1.DeleteOptions {
 		resp.PropagationPolicy = &propigationPolicy
 	}
 
-	if cfg.DeleteImmediately {
-		resp.GracePeriodSeconds = new(int64)
-	}
-
 	return &resp
 }
 
@@ -280,7 +218,11 @@ func (core *coreClient) List(opts ...ListOption) ([]v1alpha1.Space, error) {
 		return nil, fmt.Errorf("couldn't list Spaces: %v", err)
 	}
 
-	return List(res.Items).Filter(AllPredicate(cfg.filters...)), nil
+	if cfg.filter == nil {
+		return res.Items, nil
+	}
+
+	return List(res.Items).Filter(cfg.filter), nil
 }
 
 func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
@@ -288,10 +230,6 @@ func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
 		resp.FieldSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.fieldSelector))
 	}
 
-	if cfg.labelSelector != nil {
-		resp.LabelSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.labelSelector))
-	}
-
 	return
 }
 
diff --git a/pkg/kf/spaces/zz_generated.clientoptions.go b/pkg/kf/spaces/zz_generated.clientoptions.go
index 6a30ce9..675456d 100644
--- a/pkg/kf/spaces/zz_generated.clientoptions.go
+++ b/pkg/kf/spaces/zz_generated.clientoptions.go
@@ -119,8 +119,6 @@ func GetOptionDefaults() GetOptions {
 }
 
 type deleteConfig struct {
-	// DeleteImmediately is If the resource should be deleted immediately.
-	DeleteImmediately bool
 	// ForegroundDeletion is If the resource should be deleted in the foreground.
 	ForegroundDeletion bool
 }
@@ -151,25 +149,12 @@ func (opts DeleteOptions) Extend(other DeleteOptions) DeleteOptions {
 	return out
 }
 
-// DeleteImmediately returns the last set value for DeleteImmediately or the empty value
-// if not set.
-func (opts DeleteOptions) DeleteImmediately() bool {
-	return opts.toConfig().DeleteImmediately
-}
-
 // ForegroundDeletion returns the last set value for ForegroundDeletion or the empty value
 // if not set.
 func (opts DeleteOptions) ForegroundDeletion() bool {
 	return opts.toConfig().ForegroundDeletion
 }
 
-// WithDeleteDeleteImmediately creates an Option that sets If the resource should be deleted immediately.
-func WithDeleteDeleteImmediately(val bool) DeleteOption {
-	return func(cfg *deleteConfig) {
-		cfg.DeleteImmediately = val
-	}
-}
-
 // WithDeleteForegroundDeletion creates an Option that sets If the resource should be deleted in the foreground.
 func WithDeleteForegroundDeletion(val bool) DeleteOption {
 	return func(cfg *deleteConfig) {
@@ -185,10 +170,8 @@ func DeleteOptionDefaults() DeleteOptions {
 type listConfig struct {
 	// fieldSelector is A selector on the resource's fields.
 	fieldSelector map[string]string
-	// filters is Additional filters to apply.
-	filters []Predicate
-	// labelSelector is A label selector.
-	labelSelector map[string]string
+	// filter is Filter to apply.
+	filter Predicate
 }
 
 // ListOption is a single option for configuring a listConfig
@@ -223,16 +206,10 @@ func (opts ListOptions) fieldSelector() map[string]string {
 	return opts.toConfig().fieldSelector
 }
 
-// filters returns the last set value for filters or the empty value
+// filter returns the last set value for filter or the empty value
 // if not set.
-func (opts ListOptions) filters() []Predicate {
-	return opts.toConfig().filters
-}
-
-// labelSelector returns the last set value for labelSelector or the empty value
-// if not set.
-func (opts ListOptions) labelSelector() map[string]string {
-	return opts.toConfig().labelSelector
+func (opts ListOptions) filter() Predicate {
+	return opts.toConfig().filter
 }
 
 // WithListFieldSelector creates an Option that sets A selector on the resource's fields.
@@ -242,17 +219,10 @@ func WithListFieldSelector(val map[string]string) ListOption {
 	}
 }
 
-// WithListFilters creates an Option that sets Additional filters to apply.
-func WithListFilters(val []Predicate) ListOption {
-	return func(cfg *listConfig) {
-		cfg.filters = val
-	}
-}
-
-// WithListLabelSelector creates an Option that sets A label selector.
-func WithListLabelSelector(val map[string]string) ListOption {
+// WithListFilter creates an Option that sets Filter to apply.
+func WithListFilter(val Predicate) ListOption {
 	return func(cfg *listConfig) {
-		cfg.labelSelector = val
+		cfg.filter = val
 	}
 }
 
diff --git a/pkg/reconciler/route/resources/virtual_service.go b/pkg/reconciler/route/resources/virtual_service.go
index ab321ba..ac19ca0 100644
--- a/pkg/reconciler/route/resources/virtual_service.go
+++ b/pkg/reconciler/route/resources/virtual_service.go
@@ -72,7 +72,7 @@ func MakeVirtualService(claims []*v1alpha1.RouteClaim, routes []*v1alpha1.Route)
 	for _, route := range claims {
 		urlPath := route.Spec.RouteSpecFields.Path
 
-		httpRoute, err := buildHTTPRoute(namespace, urlPath, nil)
+		httpRoute, err := buildHTTPRoute(hostDomain, namespace, urlPath, nil)
 		if err != nil {
 			return nil, err
 		}
@@ -91,7 +91,8 @@ func MakeVirtualService(claims []*v1alpha1.RouteClaim, routes []*v1alpha1.Route)
 		if route.Spec.AppName != "" {
 			appNames = append(appNames, route.Spec.AppName)
 		}
-		httpRoute, err := buildHTTPRoute(namespace, urlPath, appNames)
+
+		httpRoute, err := buildHTTPRoute(hostDomain, namespace, urlPath, appNames)
 		if err != nil {
 			return nil, err
 		}
@@ -128,7 +129,7 @@ func MakeVirtualService(claims []*v1alpha1.RouteClaim, routes []*v1alpha1.Route)
 	}, nil
 }
 
-func buildHTTPRoute(namespace, urlPath string, appNames []string) ([]networking.HTTPRoute, error) {
+func buildHTTPRoute(hostDomain, namespace, urlPath string, appNames []string) ([]networking.HTTPRoute, error) {
 	var pathMatchers []networking.HTTPMatchRequest
 	urlPath = path.Join("/", urlPath, "/")
 	regexpPath, err := v1alpha1.BuildPathRegexp(urlPath)
@@ -153,6 +154,17 @@ func buildHTTPRoute(namespace, urlPath string, appNames []string) ([]networking.
 			Rewrite: &networking.HTTPRewrite{
 				Authority: network.GetServiceHostname(appName, namespace),
 			},
+			Headers: &networking.Headers{
+				Request: &networking.HeaderOperations{
+					Add: map[string]string{
+						// Set forwarding headers so the app gets the real hostname it's serving
+						// at rather than the internal one:
+						// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded1
+						"X-Forwarded-Host": hostDomain,
+						"Forwarded":        fmt.Sprintf("host=%s", hostDomain),
+					},
+				},
+			},
 		})
 	}
 


Test patch:
diff --git a/pkg/kf/apps/kfapp_test.go b/pkg/kf/apps/kfapp_test.go
index dfee92c..a0fa342 100644
--- a/pkg/kf/apps/kfapp_test.go
+++ b/pkg/kf/apps/kfapp_test.go
@@ -246,3 +246,25 @@ func ExampleKfApp_GetClusterURL() {
 
 	// Output: http://app-a.some-namespace.svc.cluster.local
 }
+
+func ExampleKfApp_GetArgs() {
+	myApp := NewKfApp()
+	fmt.Printf("Default: %v\n", myApp.GetArgs())
+
+	myApp.SetArgs([]string{"arg1", "arg2"})
+	fmt.Printf("After set: %v\n", myApp.GetArgs())
+
+	// Output: Default: []
+	// After set: [arg1 arg2]
+}
+
+func ExampleKfApp_GetCommand() {
+	myApp := NewKfApp()
+	fmt.Printf("Default: %v\n", myApp.GetCommand())
+
+	myApp.SetCommand([]string{"/bin/bash", "-x"})
+	fmt.Printf("After set: %v\n", myApp.GetCommand())
+
+	// Output: Default: []
+	// After set: [/bin/bash -x]
+}
diff --git a/pkg/kf/commands/apps/apps_test.go b/pkg/kf/commands/apps/apps_test.go
index a7d7b08..16792ee 100644
--- a/pkg/kf/commands/apps/apps_test.go
+++ b/pkg/kf/commands/apps/apps_test.go
@@ -24,7 +24,7 @@ import (
 	v1alpha1 "github.com/google/kf/pkg/apis/kf/v1alpha1"
 	"github.com/google/kf/pkg/kf/apps/fake"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/testutil"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/pkg/kf/commands/apps/delete_test.go b/pkg/kf/commands/apps/delete_test.go
index 38abad9..953be72 100644
--- a/pkg/kf/commands/apps/delete_test.go
+++ b/pkg/kf/commands/apps/delete_test.go
@@ -23,7 +23,7 @@ import (
 	"github.com/golang/mock/gomock"
 	"github.com/google/kf/pkg/kf/apps/fake"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 )
 
 func TestDeleteCommand(t *testing.T) {
diff --git a/pkg/kf/commands/apps/logs_test.go b/pkg/kf/commands/apps/logs_test.go
index b1df86c..5124619 100644
--- a/pkg/kf/commands/apps/logs_test.go
+++ b/pkg/kf/commands/apps/logs_test.go
@@ -22,8 +22,7 @@ import (
 
 	"github.com/golang/mock/gomock"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
-	"github.com/google/kf/pkg/kf/internal/kf"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/logs"
 	"github.com/google/kf/pkg/kf/logs/fake"
 	"github.com/google/kf/pkg/kf/testutil"
@@ -69,7 +68,7 @@ func TestLogsCommand(t *testing.T) {
 			Setup: func(t *testing.T, fake *fake.FakeTailer) {
 				fake.EXPECT().
 					Tail(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
-					Return(kf.ConfigErr{Reason: "some-error"})
+					Return(utils.ConfigErr{Reason: "some-error"})
 			},
 			Assert: func(t *testing.T, cmd *cobra.Command, err error) {
 				testutil.AssertEqual(t, "SilenceUsage", false, cmd.SilenceUsage)
diff --git a/pkg/kf/commands/apps/push_test.go b/pkg/kf/commands/apps/push_test.go
index 7534e63..56efa61 100644
--- a/pkg/kf/commands/apps/push_test.go
+++ b/pkg/kf/commands/apps/push_test.go
@@ -28,7 +28,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	appsfake "github.com/google/kf/pkg/kf/apps/fake"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	svbFake "github.com/google/kf/pkg/kf/service-bindings/fake"
 	"github.com/google/kf/pkg/kf/testutil"
 	"github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
@@ -93,6 +93,9 @@ func TestPushCommand(t *testing.T) {
 				"-u", "http",
 				"-t", "28",
 				"-s", "cflinuxfs3",
+				"--entrypoint", "start-web.sh",
+				"--args", "a",
+				"--args", "b",
 			},
 			wantImagePrefix: "some-reg.io/src-some-namespace-example-app",
 			srcImageBuilder: func(dir, srcImage string, rebase bool) error {
@@ -108,6 +111,8 @@ func TestPushCommand(t *testing.T) {
 				apps.WithPushEnvironmentVariables(map[string]string{"env1": "val1", "env2": "val2"}),
 				apps.WithPushNoStart(true),
 				apps.WithPushExactScale(intPtr(1)),
+				apps.WithPushArgs([]string{"a", "b"}),
+				apps.WithPushCommand([]string{"start-web.sh"}),
 				apps.WithPushHealthCheck(&corev1.Probe{
 					TimeoutSeconds: 28,
 					Handler: corev1.Handler{
@@ -547,6 +552,8 @@ func TestPushCommand(t *testing.T) {
 					testutil.AssertEqual(t, "health check", expectOpts.HealthCheck(), actualOpts.HealthCheck())
 					testutil.AssertEqual(t, "default route", expectOpts.DefaultRouteDomain(), actualOpts.DefaultRouteDomain())
 					testutil.AssertEqual(t, "random route", expectOpts.RandomRouteDomain(), actualOpts.RandomRouteDomain())
+					testutil.AssertEqual(t, "command", expectOpts.Command(), actualOpts.Command())
+					testutil.AssertEqual(t, "args", expectOpts.Args(), actualOpts.Args())
 
 					if !strings.HasPrefix(actualOpts.SourceImage(), tc.wantImagePrefix) {
 						t.Errorf("Wanted srcImage to start with %s got: %s", tc.wantImagePrefix, actualOpts.SourceImage())
diff --git a/pkg/kf/commands/apps/set_env_test.go b/pkg/kf/commands/apps/set_env_test.go
index 1d750b9..98f3e36 100644
--- a/pkg/kf/commands/apps/set_env_test.go
+++ b/pkg/kf/commands/apps/set_env_test.go
@@ -25,7 +25,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/apps/fake"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/testutil"
 )
 
diff --git a/pkg/kf/commands/apps/unset_env_test.go b/pkg/kf/commands/apps/unset_env_test.go
index 44734e4..0f89f0f 100644
--- a/pkg/kf/commands/apps/unset_env_test.go
+++ b/pkg/kf/commands/apps/unset_env_test.go
@@ -19,7 +19,7 @@ import (
 	"errors"
 	"testing"
 
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 
 	"github.com/golang/mock/gomock"
 	"github.com/google/kf/pkg/internal/envutil"
diff --git a/pkg/kf/commands/buildpacks/buildpacks_test.go b/pkg/kf/commands/buildpacks/buildpacks_test.go
index 9555087..c500e54 100644
--- a/pkg/kf/commands/buildpacks/buildpacks_test.go
+++ b/pkg/kf/commands/buildpacks/buildpacks_test.go
@@ -24,7 +24,7 @@ import (
 	"github.com/google/kf/pkg/kf/buildpacks/fake"
 	cbuildpacks "github.com/google/kf/pkg/kf/commands/buildpacks"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/testutil"
 )
 
diff --git a/pkg/kf/commands/buildpacks/stacks_test.go b/pkg/kf/commands/buildpacks/stacks_test.go
index b4ac11a..2d0f2ff 100644
--- a/pkg/kf/commands/buildpacks/stacks_test.go
+++ b/pkg/kf/commands/buildpacks/stacks_test.go
@@ -23,7 +23,7 @@ import (
 	"github.com/google/kf/pkg/kf/buildpacks/fake"
 	cbuildpacks "github.com/google/kf/pkg/kf/commands/buildpacks"
 	"github.com/google/kf/pkg/kf/commands/config"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/testutil"
 )
 
diff --git a/pkg/kf/commands/routes/create_route_test.go b/pkg/kf/commands/routes/create_route_test.go
index fd56c66..0545c59 100644
--- a/pkg/kf/commands/routes/create_route_test.go
+++ b/pkg/kf/commands/routes/create_route_test.go
@@ -23,7 +23,7 @@ import (
 	"github.com/google/kf/pkg/apis/kf/v1alpha1"
 	"github.com/google/kf/pkg/kf/commands/config"
 	"github.com/google/kf/pkg/kf/commands/routes"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	routesfake "github.com/google/kf/pkg/kf/routeclaims/fake"
 	"github.com/google/kf/pkg/kf/testutil"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/pkg/kf/commands/routes/delete_route_test.go b/pkg/kf/commands/routes/delete_route_test.go
index 74e73a2..01bb97b 100644
--- a/pkg/kf/commands/routes/delete_route_test.go
+++ b/pkg/kf/commands/routes/delete_route_test.go
@@ -25,7 +25,7 @@ import (
 	fakeapp "github.com/google/kf/pkg/kf/apps/fake"
 	"github.com/google/kf/pkg/kf/commands/config"
 	"github.com/google/kf/pkg/kf/commands/routes"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	fakerouteclaims "github.com/google/kf/pkg/kf/routeclaims/fake"
 	"github.com/google/kf/pkg/kf/testutil"
 )
diff --git a/pkg/kf/commands/routes/map_route_test.go b/pkg/kf/commands/routes/map_route_test.go
index fa68fa1..eecc7c2 100644
--- a/pkg/kf/commands/routes/map_route_test.go
+++ b/pkg/kf/commands/routes/map_route_test.go
@@ -25,7 +25,7 @@ import (
 	appsfake "github.com/google/kf/pkg/kf/apps/fake"
 	"github.com/google/kf/pkg/kf/commands/config"
 	"github.com/google/kf/pkg/kf/commands/routes"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/testutil"
 )
 
diff --git a/pkg/kf/commands/routes/unmap_route_test.go b/pkg/kf/commands/routes/unmap_route_test.go
index 50f331b..32c92fc 100644
--- a/pkg/kf/commands/routes/unmap_route_test.go
+++ b/pkg/kf/commands/routes/unmap_route_test.go
@@ -25,7 +25,7 @@ import (
 	"github.com/google/kf/pkg/kf/apps/fake"
 	"github.com/google/kf/pkg/kf/commands/config"
 	"github.com/google/kf/pkg/kf/commands/routes"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/testutil"
 )
 
diff --git a/pkg/kf/commands/service-bindings/bind-service_test.go b/pkg/kf/commands/service-bindings/bind-service_test.go
index db1012f..a2087cc 100644
--- a/pkg/kf/commands/service-bindings/bind-service_test.go
+++ b/pkg/kf/commands/service-bindings/bind-service_test.go
@@ -20,7 +20,7 @@ import (
 
 	"github.com/golang/mock/gomock"
 	servicebindingscmd "github.com/google/kf/pkg/kf/commands/service-bindings"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	servicebindings "github.com/google/kf/pkg/kf/service-bindings"
 	"github.com/google/kf/pkg/kf/service-bindings/fake"
 	"github.com/google/kf/pkg/kf/testutil"
diff --git a/pkg/kf/commands/service-bindings/bindings_test.go b/pkg/kf/commands/service-bindings/bindings_test.go
index be8fdf5..ab9e455 100644
--- a/pkg/kf/commands/service-bindings/bindings_test.go
+++ b/pkg/kf/commands/service-bindings/bindings_test.go
@@ -20,7 +20,7 @@ import (
 
 	"github.com/golang/mock/gomock"
 	servicebindingscmd "github.com/google/kf/pkg/kf/commands/service-bindings"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	servicebindings "github.com/google/kf/pkg/kf/service-bindings"
 	"github.com/google/kf/pkg/kf/service-bindings/fake"
 	"github.com/google/kf/pkg/kf/testutil"
diff --git a/pkg/kf/commands/service-bindings/unbind-service_test.go b/pkg/kf/commands/service-bindings/unbind-service_test.go
index 21627ae..70e5212 100644
--- a/pkg/kf/commands/service-bindings/unbind-service_test.go
+++ b/pkg/kf/commands/service-bindings/unbind-service_test.go
@@ -16,7 +16,7 @@ package servicebindings_test
 
 import (
 	"errors"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"testing"
 
 	"github.com/golang/mock/gomock"
diff --git a/pkg/kf/commands/service-bindings/vcap_services_test.go b/pkg/kf/commands/service-bindings/vcap_services_test.go
index fc95d9e..112fab5 100644
--- a/pkg/kf/commands/service-bindings/vcap_services_test.go
+++ b/pkg/kf/commands/service-bindings/vcap_services_test.go
@@ -23,7 +23,7 @@ import (
 
 	"github.com/google/kf/pkg/kf/commands/config"
 	servicebindings "github.com/google/kf/pkg/kf/commands/service-bindings"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/testutil"
 	corev1 "k8s.io/api/core/v1"
 	k8sfake "k8s.io/client-go/kubernetes/fake"
diff --git a/pkg/kf/commands/services/create-service_test.go b/pkg/kf/commands/services/create-service_test.go
index 856c064..5e01b15 100644
--- a/pkg/kf/commands/services/create-service_test.go
+++ b/pkg/kf/commands/services/create-service_test.go
@@ -24,7 +24,7 @@ import (
 	servicecatalogclientfake "github.com/google/kf/pkg/client/servicecatalog/clientset/versioned/fake"
 	"github.com/google/kf/pkg/kf/commands/config"
 	servicescmd "github.com/google/kf/pkg/kf/commands/services"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/testutil"
 	servicecatalogv1beta1 "github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/pkg/kf/commands/services/delete-service_test.go b/pkg/kf/commands/services/delete-service_test.go
index d570e31..65f2ed1 100644
--- a/pkg/kf/commands/services/delete-service_test.go
+++ b/pkg/kf/commands/services/delete-service_test.go
@@ -20,7 +20,7 @@ import (
 
 	"github.com/golang/mock/gomock"
 	servicescmd "github.com/google/kf/pkg/kf/commands/services"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/services"
 	"github.com/google/kf/pkg/kf/services/fake"
 	"github.com/google/kf/pkg/kf/testutil"
diff --git a/pkg/kf/commands/services/marketplace_test.go b/pkg/kf/commands/services/marketplace_test.go
index 79a9dc9..f26b91c 100644
--- a/pkg/kf/commands/services/marketplace_test.go
+++ b/pkg/kf/commands/services/marketplace_test.go
@@ -20,7 +20,7 @@ import (
 
 	"github.com/golang/mock/gomock"
 	servicescmd "github.com/google/kf/pkg/kf/commands/services"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/services"
 	"github.com/google/kf/pkg/kf/services/fake"
 	"github.com/google/kf/pkg/kf/testutil"
diff --git a/pkg/kf/commands/services/service_test.go b/pkg/kf/commands/services/service_test.go
index 5c59c85..0019ede 100644
--- a/pkg/kf/commands/services/service_test.go
+++ b/pkg/kf/commands/services/service_test.go
@@ -20,7 +20,7 @@ import (
 
 	"github.com/golang/mock/gomock"
 	servicescmd "github.com/google/kf/pkg/kf/commands/services"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/services"
 	"github.com/google/kf/pkg/kf/services/fake"
 	"github.com/google/kf/pkg/kf/testutil"
diff --git a/pkg/kf/commands/services/services_test.go b/pkg/kf/commands/services/services_test.go
index 59f3760..052e0e6 100644
--- a/pkg/kf/commands/services/services_test.go
+++ b/pkg/kf/commands/services/services_test.go
@@ -23,7 +23,7 @@ import (
 	fakeapps "github.com/google/kf/pkg/kf/apps/fake"
 	"github.com/google/kf/pkg/kf/commands/config"
 	servicescmd "github.com/google/kf/pkg/kf/commands/services"
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/services"
 	"github.com/google/kf/pkg/kf/services/fake"
 	"github.com/google/kf/pkg/kf/testutil"
diff --git a/pkg/kf/internal/tools/clientgen/gentest/client.go b/pkg/kf/internal/tools/clientgen/gentest/client.go
index d39db8d..140f75f 100644
--- a/pkg/kf/internal/tools/clientgen/gentest/client.go
+++ b/pkg/kf/internal/tools/clientgen/gentest/client.go
@@ -16,6 +16,7 @@
 package gentest
 
 import v1 "k8s.io/client-go/kubernetes/typed/core/v1"
+import corev1 "k8s.io/api/core/v1"
 
 //go:generate go run ../../option-builder/option-builder.go --pkg gentest ../common-options.yml zz_generated.clientoptions.go
 //go:generate go run ../genclient.go client.yml
@@ -27,9 +28,21 @@ type ClientExtension interface {
 // validator that filter based on a label.
 func NewExampleClient(mockK8s v1.SecretsGetter) Client {
 	return &coreClient{
-		kclient: mockK8s,
-		upsertMutate: MutatorList{
-			LabelSetMutator(map[string]string{"is-a": "OperatorConfig"}),
-		},
+		kclient:      mockK8s,
+		upsertMutate: LabelSetMutator(map[string]string{"is-a": "OperatorConfig"}),
+	}
+}
+
+func LabelSetMutator(labels map[string]string) Mutator {
+	return func(obj *corev1.Secret) error {
+		if obj.Labels == nil {
+			obj.Labels = make(map[string]string)
+		}
+
+		for key, value := range labels {
+			obj.Labels[key] = value
+		}
+
+		return nil
 	}
 }
diff --git a/pkg/kf/internal/tools/clientgen/gentest/client_test.go b/pkg/kf/internal/tools/clientgen/gentest/client_test.go
index d0e4b21..39a22a6 100644
--- a/pkg/kf/internal/tools/clientgen/gentest/client_test.go
+++ b/pkg/kf/internal/tools/clientgen/gentest/client_test.go
@@ -33,46 +33,6 @@ import (
 	cv1 "k8s.io/client-go/kubernetes/typed/core/v1"
 )
 
-func TestAllPredicate(t *testing.T) {
-	pass := func(obj *v1.Secret) bool {
-		return true
-	}
-
-	fail := func(obj *v1.Secret) bool {
-		return false
-	}
-
-	cases := map[string]struct {
-		Children []Predicate
-		Expected bool
-	}{
-		"empty true": {
-			Children: []Predicate{},
-			Expected: true,
-		},
-		"pass": {
-			Children: []Predicate{pass},
-			Expected: true,
-		},
-		"fail": {
-			Children: []Predicate{fail},
-			Expected: false,
-		},
-		"mixed": {
-			Children: []Predicate{pass, fail, pass},
-			Expected: false,
-		},
-	}
-
-	for tn, tc := range cases {
-		t.Run(tn, func(t *testing.T) {
-			pred := AllPredicate(tc.Children...)
-			actual := pred(nil)
-			testutil.AssertEqual(t, "predicate result", tc.Expected, actual)
-		})
-	}
-}
-
 func ExampleList_Filter() {
 	first := v1.Secret{}
 	first.Name = "ok"
@@ -95,26 +55,6 @@ func ExampleList_Filter() {
 	// - ok
 }
 
-func ExampleMutatorList_Apply() {
-	mutators := MutatorList{
-		func(s *v1.Secret) error {
-			s.Name = "Name"
-			return nil
-		},
-		func(s *v1.Secret) error {
-			return errors.New("some-error")
-		},
-	}
-	res := v1.Secret{}
-	err := mutators.Apply(&res)
-
-	fmt.Println("Error:", err)
-	fmt.Println("Mutated name:", res.Name)
-
-	// Output: Error: some-error
-	// Mutated name: Name
-}
-
 func ExampleLabelSetMutator() {
 	out := &v1.Secret{}
 	managedAdder := LabelSetMutator(map[string]string{"managed-by": "kf"})
@@ -125,34 +65,6 @@ func ExampleLabelSetMutator() {
 	// Output: Labels: map[managed-by:kf]
 }
 
-func ExampleLabelEqualsPredicate() {
-	out := &v1.Secret{}
-	out.Labels = map[string]string{"managed-by": "not kf"}
-	pred := LabelEqualsPredicate("managed-by", "kf")
-
-	fmt.Printf("Not Equal: %v\n", pred(out))
-
-	out.Labels["managed-by"] = "kf"
-	fmt.Printf("Equal: %v\n", pred(out))
-
-	// Output: Not Equal: false
-	// Equal: true
-}
-
-func ExampleLabelsContainsPredicate() {
-	out := &v1.Secret{}
-	out.Labels = map[string]string{"my-label": ""}
-
-	mylabelpred := LabelsContainsPredicate("my-label")
-	missinglabelpred := LabelsContainsPredicate("missing")
-
-	fmt.Printf("Contained: %v\n", mylabelpred(out))
-	fmt.Printf("Not Contained: %v\n", missinglabelpred(out))
-
-	// Output: Contained: true
-	// Not Contained: false
-}
-
 func TestClient_invariant(t *testing.T) {
 	// This test validates that the mutators are applied to read and
 	// write operations.
diff --git a/pkg/kf/internal/tools/clientgen/gentest/zz_generated.client.go b/pkg/kf/internal/tools/clientgen/gentest/zz_generated.client.go
index 51b22b2..a542713 100644
--- a/pkg/kf/internal/tools/clientgen/gentest/zz_generated.client.go
+++ b/pkg/kf/internal/tools/clientgen/gentest/zz_generated.client.go
@@ -51,19 +51,6 @@ const (
 // Predicate is a boolean function for a v1.Secret.
 type Predicate func(*v1.Secret) bool
 
-// AllPredicate is a predicate that passes if all children pass.
-func AllPredicate(children ...Predicate) Predicate {
-	return func(obj *v1.Secret) bool {
-		for _, filter := range children {
-			if !filter(obj) {
-				return false
-			}
-		}
-
-		return true
-	}
-}
-
 // Mutator is a function that changes v1.Secret.
 type Mutator func(*v1.Secret) error
 
@@ -117,51 +104,6 @@ func (list List) Filter(filter Predicate) (out List) {
 	return
 }
 
-// MutatorList is a list of mutators.
-type MutatorList []Mutator
-
-// Apply passes the given value to each of the mutators in the list failing if
-// one of them returns an error.
-func (list MutatorList) Apply(svc *v1.Secret) error {
-	for _, mutator := range list {
-		if err := mutator(svc); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// LabelSetMutator creates a mutator that sets the given labels on the object.
-func LabelSetMutator(labels map[string]string) Mutator {
-	return func(obj *v1.Secret) error {
-		if obj.Labels == nil {
-			obj.Labels = make(map[string]string)
-		}
-
-		for key, value := range labels {
-			obj.Labels[key] = value
-		}
-
-		return nil
-	}
-}
-
-// LabelEqualsPredicate validates that the given label exists exactly on the object.
-func LabelEqualsPredicate(key, value string) Predicate {
-	return func(obj *v1.Secret) bool {
-		return obj.Labels[key] == value
-	}
-}
-
-// LabelsContainsPredicate validates that the given label exists on the object.
-func LabelsContainsPredicate(key string) Predicate {
-	return func(obj *v1.Secret) bool {
-		_, ok := obj.Labels[key]
-		return ok
-	}
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Client
 ////////////////////////////////////////////////////////////////////////////////
@@ -184,15 +126,15 @@ type Client interface {
 
 type coreClient struct {
 	kclient      cv1.SecretsGetter
-	upsertMutate MutatorList
+	upsertMutate Mutator
 }
 
 func (core *coreClient) preprocessUpsert(obj *v1.Secret) error {
-	if err := core.upsertMutate.Apply(obj); err != nil {
-		return err
+	if core.upsertMutate == nil {
+		return nil
 	}
 
-	return nil
+	return core.upsertMutate(obj)
 }
 
 // Create inserts the given v1.Secret into the cluster.
@@ -263,10 +205,6 @@ func (cfg deleteConfig) ToDeleteOptions() *metav1.DeleteOptions {
 		resp.PropagationPolicy = &propigationPolicy
 	}
 
-	if cfg.DeleteImmediately {
-		resp.GracePeriodSeconds = new(int64)
-	}
-
 	return &resp
 }
 
@@ -280,7 +218,11 @@ func (core *coreClient) List(namespace string, opts ...ListOption) ([]v1.Secret,
 		return nil, fmt.Errorf("couldn't list OperatorConfigs: %v", err)
 	}
 
-	return List(res.Items).Filter(AllPredicate(cfg.filters...)), nil
+	if cfg.filter == nil {
+		return res.Items, nil
+	}
+
+	return List(res.Items).Filter(cfg.filter), nil
 }
 
 func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
@@ -288,10 +230,6 @@ func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
 		resp.FieldSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.fieldSelector))
 	}
 
-	if cfg.labelSelector != nil {
-		resp.LabelSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.labelSelector))
-	}
-
 	return
 }
 
diff --git a/pkg/kf/internal/tools/clientgen/gentest/zz_generated.clientoptions.go b/pkg/kf/internal/tools/clientgen/gentest/zz_generated.clientoptions.go
index 9bf2e54..998a5e8 100644
--- a/pkg/kf/internal/tools/clientgen/gentest/zz_generated.clientoptions.go
+++ b/pkg/kf/internal/tools/clientgen/gentest/zz_generated.clientoptions.go
@@ -119,8 +119,6 @@ func GetOptionDefaults() GetOptions {
 }
 
 type deleteConfig struct {
-	// DeleteImmediately is If the resource should be deleted immediately.
-	DeleteImmediately bool
 	// ForegroundDeletion is If the resource should be deleted in the foreground.
 	ForegroundDeletion bool
 }
@@ -151,25 +149,12 @@ func (opts DeleteOptions) Extend(other DeleteOptions) DeleteOptions {
 	return out
 }
 
-// DeleteImmediately returns the last set value for DeleteImmediately or the empty value
-// if not set.
-func (opts DeleteOptions) DeleteImmediately() bool {
-	return opts.toConfig().DeleteImmediately
-}
-
 // ForegroundDeletion returns the last set value for ForegroundDeletion or the empty value
 // if not set.
 func (opts DeleteOptions) ForegroundDeletion() bool {
 	return opts.toConfig().ForegroundDeletion
 }
 
-// WithDeleteDeleteImmediately creates an Option that sets If the resource should be deleted immediately.
-func WithDeleteDeleteImmediately(val bool) DeleteOption {
-	return func(cfg *deleteConfig) {
-		cfg.DeleteImmediately = val
-	}
-}
-
 // WithDeleteForegroundDeletion creates an Option that sets If the resource should be deleted in the foreground.
 func WithDeleteForegroundDeletion(val bool) DeleteOption {
 	return func(cfg *deleteConfig) {
@@ -185,10 +170,8 @@ func DeleteOptionDefaults() DeleteOptions {
 type listConfig struct {
 	// fieldSelector is A selector on the resource's fields.
 	fieldSelector map[string]string
-	// filters is Additional filters to apply.
-	filters []Predicate
-	// labelSelector is A label selector.
-	labelSelector map[string]string
+	// filter is Filter to apply.
+	filter Predicate
 }
 
 // ListOption is a single option for configuring a listConfig
@@ -223,16 +206,10 @@ func (opts ListOptions) fieldSelector() map[string]string {
 	return opts.toConfig().fieldSelector
 }
 
-// filters returns the last set value for filters or the empty value
+// filter returns the last set value for filter or the empty value
 // if not set.
-func (opts ListOptions) filters() []Predicate {
-	return opts.toConfig().filters
-}
-
-// labelSelector returns the last set value for labelSelector or the empty value
-// if not set.
-func (opts ListOptions) labelSelector() map[string]string {
-	return opts.toConfig().labelSelector
+func (opts ListOptions) filter() Predicate {
+	return opts.toConfig().filter
 }
 
 // WithListFieldSelector creates an Option that sets A selector on the resource's fields.
@@ -242,17 +219,10 @@ func WithListFieldSelector(val map[string]string) ListOption {
 	}
 }
 
-// WithListFilters creates an Option that sets Additional filters to apply.
-func WithListFilters(val []Predicate) ListOption {
-	return func(cfg *listConfig) {
-		cfg.filters = val
-	}
-}
-
-// WithListLabelSelector creates an Option that sets A label selector.
-func WithListLabelSelector(val map[string]string) ListOption {
+// WithListFilter creates an Option that sets Filter to apply.
+func WithListFilter(val Predicate) ListOption {
 	return func(cfg *listConfig) {
-		cfg.labelSelector = val
+		cfg.filter = val
 	}
 }
 
diff --git a/pkg/kf/commands/utils/prefix_filter.go b/pkg/kf/internal/utils/cli/prefix_filter.go
similarity index 100%
rename from pkg/kf/commands/utils/prefix_filter.go
rename to pkg/kf/internal/utils/cli/prefix_filter.go
diff --git a/pkg/kf/commands/utils/prefix_filter_test.go b/pkg/kf/internal/utils/cli/prefix_filter_test.go
similarity index 96%
rename from pkg/kf/commands/utils/prefix_filter_test.go
rename to pkg/kf/internal/utils/cli/prefix_filter_test.go
index 794fcd2..781c618 100644
--- a/pkg/kf/commands/utils/prefix_filter_test.go
+++ b/pkg/kf/internal/utils/cli/prefix_filter_test.go
@@ -19,7 +19,7 @@ import (
 	"io"
 	"testing"
 
-	"github.com/google/kf/pkg/kf/commands/utils"
+	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
 	"github.com/google/kf/pkg/kf/testutil"
 )
 
diff --git a/pkg/kf/manifest/manifest_test.go b/pkg/kf/manifest/manifest_test.go
index 1b627fa..c0648af 100644
--- a/pkg/kf/manifest/manifest_test.go
+++ b/pkg/kf/manifest/manifest_test.go
@@ -98,6 +98,38 @@ applications:
 				},
 			},
 		},
+		"cf style command": {
+			fileContent: `---
+applications:
+- name: CUSTOM_START
+  command: rake run $VAR
+`,
+			expected: &manifest.Manifest{
+				Applications: []manifest.Application{
+					{
+						Name:    "CUSTOM_START",
+						Command: "rake run $VAR",
+					},
+				},
+			},
+		},
+		"docker style command": {
+			fileContent: `---
+applications:
+- name: CUSTOM_START
+  entrypoint: python
+  args: ["-m", "SimpleHTTPServer"]
+`,
+			expected: &manifest.Manifest{
+				Applications: []manifest.Application{
+					{
+						Name:       "CUSTOM_START",
+						Entrypoint: "python",
+						Args:       []string{"-m", "SimpleHTTPServer"},
+					},
+				},
+			},
+		},
 	}
 
 	for tn, tc := range cases {
@@ -218,3 +250,35 @@ func ExampleApplication_Buildpack() {
 	// One: java
 	// Two: maven,java
 }
+
+func ExampleApplication_CommandArgs() {
+	app := manifest.Application{}
+	fmt.Printf("Blank: %v\n", app.CommandArgs())
+
+	app = manifest.Application{
+		Command: "start.sh && exit 1",
+	}
+	fmt.Printf("Command: %v\n", app.CommandArgs())
+
+	app = manifest.Application{
+		Args: []string{"-m", "SimpleHTTPServer"},
+	}
+	fmt.Printf("Args: %v\n", app.CommandArgs())
+
+	// Output: Blank: []
+	// Command: [start.sh && exit 1]
+	// Args: [-m SimpleHTTPServer]
+}
+
+func ExampleApplication_CommandEntrypoint() {
+	app := manifest.Application{}
+	fmt.Printf("Blank: %v\n", app.CommandEntrypoint())
+
+	app = manifest.Application{
+		Entrypoint: "python",
+	}
+	fmt.Printf("Entrypoint: %v\n", app.CommandEntrypoint())
+
+	// Output: Blank: []
+	// Entrypoint: [python]
+}
diff --git a/pkg/kf/manifest/manifest_validation_test.go b/pkg/kf/manifest/manifest_validation_test.go
new file mode 100644
index 0000000..960bf99
--- /dev/null
+++ b/pkg/kf/manifest/manifest_validation_test.go
@@ -0,0 +1,69 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the License);
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an AS IS BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package manifest
+
+import (
+	"context"
+	"testing"
+
+	"github.com/google/kf/pkg/kf/testutil"
+	"knative.dev/pkg/apis"
+)
+
+func TestApplication_Validation(t *testing.T) {
+	cases := map[string]struct {
+		spec Application
+		want *apis.FieldError
+	}{
+		"valid": {
+			spec: Application{},
+		},
+		"entrypoint and args": {
+			spec: Application{
+				Entrypoint: "python",
+				Args:       []string{"-m", "SimpleHTTPServer"},
+			},
+		},
+		"command and args": {
+			spec: Application{
+				Command: "python",
+				Args:    []string{"-m", "SimpleHTTPServer"},
+			},
+			want: apis.ErrMultipleOneOf("args", "command"),
+		},
+		"entrypoint and command": {
+			spec: Application{
+				Entrypoint: "/lifecycle/launcher",
+				Command:    "python",
+			},
+			want: apis.ErrMultipleOneOf("entrypoint", "command"),
+		},
+		"buildpack and buildpacks": {
+			spec: Application{
+				LegacyBuildpack: "default",
+				Buildpacks:      []string{"java", "node"},
+			},
+			want: apis.ErrMultipleOneOf("buildpack", "buildpacks"),
+		},
+	}
+
+	for tn, tc := range cases {
+		t.Run(tn, func(t *testing.T) {
+			got := tc.spec.Validate(context.Background())
+
+			testutil.AssertEqual(t, "validation errors", tc.want.Error(), got.Error())
+		})
+	}
+}
diff --git a/pkg/reconciler/route/resources/virtual_service_test.go b/pkg/reconciler/route/resources/virtual_service_test.go
index 061903d..e11f0bc 100644
--- a/pkg/reconciler/route/resources/virtual_service_test.go
+++ b/pkg/reconciler/route/resources/virtual_service_test.go
@@ -29,6 +29,30 @@ import (
 	networking "knative.dev/pkg/apis/istio/v1alpha3"
 )
 
+func makeRouteSpecFields(host, domain, path string) v1alpha1.RouteSpecFields {
+	return v1alpha1.RouteSpecFields{
+		Hostname: host,
+		Domain:   domain,
+		Path:     path,
+	}
+}
+
+func makeRoute(host, domain, path string) *v1alpha1.Route {
+	return &v1alpha1.Route{
+		Spec: v1alpha1.RouteSpec{
+			RouteSpecFields: makeRouteSpecFields(host, domain, path),
+		},
+	}
+}
+
+func makeRouteClaim(host, domain, path string) *v1alpha1.RouteClaim {
+	return &v1alpha1.RouteClaim{
+		Spec: v1alpha1.RouteClaimSpec{
+			RouteSpecFields: makeRouteSpecFields(host, domain, path),
+		},
+	}
+}
+
 func TestMakeVirtualService(t *testing.T) {
 	t.Parallel()
 
@@ -50,11 +74,7 @@ func TestMakeVirtualService(t *testing.T) {
 						Labels:    map[string]string{"a": "1", "b": "2"},
 					},
 					Spec: v1alpha1.RouteClaimSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "some-path",
-						},
+						RouteSpecFields: makeRouteSpecFields("some-host", "example.com", "some-path"),
 					},
 				},
 			},
@@ -65,15 +85,7 @@ func TestMakeVirtualService(t *testing.T) {
 					Kind:       "VirtualService",
 				}, v.TypeMeta)
 
-				route := &v1alpha1.Route{
-					Spec: v1alpha1.RouteSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "/some-path",
-						},
-					},
-				}
+				route := makeRoute("some-host", "example.com", "/some-path")
 
 				testutil.AssertEqual(t, "ObjectMeta", metav1.ObjectMeta{
 					Name:      v1alpha1.GenerateName(route.Spec.Hostname, route.Spec.Domain),
@@ -96,11 +108,7 @@ func TestMakeVirtualService(t *testing.T) {
 			Claims: []*v1alpha1.RouteClaim{
 				{
 					Spec: v1alpha1.RouteClaimSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "/some-path",
-						},
+						RouteSpecFields: makeRouteSpecFields("some-host", "example.com", "/some-path"),
 					},
 				},
 			},
@@ -117,26 +125,10 @@ func TestMakeVirtualService(t *testing.T) {
 		},
 		"Route": {
 			Claims: []*v1alpha1.RouteClaim{
-				{
-					Spec: v1alpha1.RouteClaimSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "/some-other-path",
-						},
-					},
-				},
+				makeRouteClaim("some-host", "example.com", "/some-other-path"),
 			},
 			Routes: []*v1alpha1.Route{
-				{
-					Spec: v1alpha1.RouteSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "/some-path",
-						},
-					},
-				},
+				makeRoute("some-host", "example.com", "/some-path"),
 			},
 			Assert: func(t *testing.T, v *networking.VirtualService, err error) {
 				testutil.AssertNil(t, "err", err)
@@ -159,23 +151,15 @@ func TestMakeVirtualService(t *testing.T) {
 						Namespace: "some-namespace",
 					},
 					Spec: v1alpha1.RouteClaimSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "/some-path",
-						},
+						RouteSpecFields: makeRouteSpecFields("some-host", "example.com", "/some-path"),
 					},
 				},
 			},
 			Routes: []*v1alpha1.Route{
 				{
 					Spec: v1alpha1.RouteSpec{
-						AppName: "some-app-name",
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "/some-path",
-						},
+						AppName:         "some-app-name",
+						RouteSpecFields: makeRouteSpecFields("some-host", "example.com", "/some-path"),
 					},
 				},
 			},
@@ -195,15 +179,7 @@ func TestMakeVirtualService(t *testing.T) {
 		},
 		"when there aren't any bound services, setup fault to 503": {
 			Claims: []*v1alpha1.RouteClaim{
-				{
-					Spec: v1alpha1.RouteClaimSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "/some-path",
-						},
-					},
-				},
+				makeRouteClaim("some-host", "example.com", "/some-path"),
 			},
 			Assert: func(t *testing.T, v *networking.VirtualService, err error) {
 				testutil.AssertNil(t, "err", err)
@@ -223,11 +199,7 @@ func TestMakeVirtualService(t *testing.T) {
 						Namespace: "some-namespace",
 					},
 					Spec: v1alpha1.RouteClaimSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "/some-path",
-						},
+						RouteSpecFields: makeRouteSpecFields("some-host", "example.com", "/some-path"),
 					},
 				},
 			},
@@ -237,64 +209,56 @@ func TestMakeVirtualService(t *testing.T) {
 						Namespace: "some-namespace",
 					},
 					Spec: v1alpha1.RouteSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "/some-path",
-						},
-						AppName: "ksvc-1",
+						RouteSpecFields: makeRouteSpecFields("some-host", "example.com", "/some-path"),
+						AppName:         "ksvc-1",
 					},
 				},
 			},
 			Assert: func(t *testing.T, v *networking.VirtualService, err error) {
-				testutil.AssertNil(t, "err", err)
-				testutil.AssertEqual(t, "HTTP len", 1, len(v.Spec.HTTP))
-				testutil.AssertEqual(t, "HTTP route destination", networking.HTTPRouteDestination{
-					Destination: networking.Destination{
-						Host: "istio-ingressgateway.istio-system.svc.cluster.local",
+				expectedHTTP := []networking.HTTPRoute{
+					{
+						Match: []networking.HTTPMatchRequest{
+							{URI: &istio.StringMatch{Regex: "^/some-path(/.*)?"}},
+						},
+						Rewrite: &networking.HTTPRewrite{
+							Authority: network.GetServiceHostname("ksvc-1", "some-namespace"),
+						},
+						Route: []networking.HTTPRouteDestination{
+							{
+								Destination: networking.Destination{Host: "istio-ingressgateway.istio-system.svc.cluster.local"},
+								Weight:      100,
+							},
+						},
+						Headers: &networking.Headers{
+							Request: &networking.HeaderOperations{
+								Add: map[string]string{
+									"X-Forwarded-Host": "some-host.example.com",
+									"Forwarded":        "host=some-host.example.com",
+								},
+							},
+						},
 					},
-					Weight: 100,
-				}, v.Spec.HTTP[0].Route[0])
-				testutil.AssertEqual(t, "HTTP Rewrite", &networking.HTTPRewrite{
-					Authority: network.GetServiceHostname("ksvc-1", "some-namespace"),
-				}, v.Spec.HTTP[0].Rewrite)
-				testutil.AssertEqual(t, "HTTP Match len", 1, len(v.Spec.HTTP[0].Match))
-				testutil.AssertEqual(t, "HTTP Match", "^/some-path(/.*)?", v.Spec.HTTP[0].Match[0].URI.Regex)
+				}
+
+				testutil.AssertNil(t, "err", err)
+				testutil.AssertEqual(t, "HTTP", expectedHTTP, v.Spec.HTTP)
 			},
 		},
 		"Hosts with subdomain": {
 			Claims: []*v1alpha1.RouteClaim{
-				{
-					Spec: v1alpha1.RouteClaimSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "some-host",
-							Domain:   "example.com",
-							Path:     "/some-path",
-						},
-					},
-				},
+				makeRouteClaim("some-host", "example.com", "/some-path"),
 			},
 			Assert: func(t *testing.T, v *networking.VirtualService, err error) {
 				testutil.AssertNil(t, "err", err)
-				testutil.AssertEqual(t, "Hosts len", 1, len(v.Spec.Hosts))
 				testutil.AssertEqual(t, "Hosts", []string{"some-host.example.com"}, v.Spec.Hosts)
 			},
 		},
 		"Hosts without subdomain": {
 			Claims: []*v1alpha1.RouteClaim{
-				{
-					Spec: v1alpha1.RouteClaimSpec{
-						RouteSpecFields: v1alpha1.RouteSpecFields{
-							Hostname: "",
-							Domain:   "example.com",
-							Path:     "/some-path",
-						},
-					},
-				},
+				makeRouteClaim("", "example.com", "/some-path"),
 			},
 			Assert: func(t *testing.T, v *networking.VirtualService, err error) {
 				testutil.AssertNil(t, "err", err)
-				testutil.AssertEqual(t, "Hosts len", 1, len(v.Spec.Hosts))
 				testutil.AssertEqual(t, "Hosts", []string{"example.com"}, v.Spec.Hosts)
 			},
 		},
@@ -307,43 +271,17 @@ func TestMakeVirtualService(t *testing.T) {
 }
 
 func ExampleMakeVirtualService() {
-	vs, err := resources.MakeVirtualService([]*v1alpha1.RouteClaim{
-		{
-			Spec: v1alpha1.RouteClaimSpec{
-				RouteSpecFields: v1alpha1.RouteSpecFields{
-					Hostname: "some-host",
-					Domain:   "example.com/",
-				},
-			},
-		},
-	}, []*v1alpha1.Route{
-		{
-			Spec: v1alpha1.RouteSpec{
-				RouteSpecFields: v1alpha1.RouteSpecFields{
-					Hostname: "some-host",
-					Domain:   "example.com/",
-				},
-			},
-		},
-		{
-			Spec: v1alpha1.RouteSpec{
-				RouteSpecFields: v1alpha1.RouteSpecFields{
-					Hostname: "some-host",
-					Domain:   "example.com",
-					Path:     "/some-path-1",
-				},
-			},
-		},
-		{
-			Spec: v1alpha1.RouteSpec{
-				RouteSpecFields: v1alpha1.RouteSpecFields{
-					Hostname: "some-host",
-					Domain:   "example.com",
-					Path:     "/some-path-2",
-				},
-			},
-		},
-	})
+	claims := []*v1alpha1.RouteClaim{
+		makeRouteClaim("some-host", "example.com/", ""),
+	}
+
+	routes := []*v1alpha1.Route{
+		makeRoute("some-host", "example.com/", ""),
+		makeRoute("some-host", "example.com/", "/some-path-1"),
+		makeRoute("some-host", "example.com/", "/some-path-2"),
+	}
+
+	vs, err := resources.MakeVirtualService(claims, routes)
 	if err != nil {
 		panic(err)
 	}