Repository: google/kf
Pull request #705 (2019-09-23 19:46:23)

Use generated client for instances
<!-- Include the issue number below -->
## Proposed Changes

* Split out the ServiceInstance client from the generic service catalog stuff towards our goal of solving #638

## 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

```



Patch:
diff --git a/pkg/kf/apps/client.go b/pkg/kf/apps/client.go
index 4eefc07..866934e 100644
--- a/pkg/kf/apps/client.go
+++ b/pkg/kf/apps/client.go
@@ -15,7 +15,6 @@
 package apps
 
 import (
-	"fmt"
 	"io"
 
 	v1alpha1 "github.com/google/kf/pkg/apis/kf/v1alpha1"
@@ -91,33 +90,3 @@ func (ac *appsClient) Restage(namespace, name string) (app *v1alpha1.App, err er
 
 	return ac.coreClient.Update(namespace, app)
 }
-
-// ConditionServiceBindingsReady returns true if service bindings are ready and
-// errors if the bindings failed.
-func ConditionServiceBindingsReady(app *v1alpha1.App, apiErr error) (isFinal bool, err error) {
-	if apiErr != nil {
-		return true, apiErr
-	}
-
-	// don't propagate old statuses
-	if app.Generation != app.Status.ObservedGeneration {
-		return false, nil
-	}
-
-	if cond := app.Status.GetCondition(v1alpha1.AppConditionServiceBindingsReady); cond != nil {
-		switch {
-		case cond.IsTrue():
-			return true, nil
-
-		case cond.IsUnknown():
-			return false, nil
-
-		default:
-			// return true and a failrue assuming IsFalse and other statuses can't be
-			// recovered from because they violate the K8s spec
-			return true, fmt.Errorf("checking %s failed, status: %s message: %s reason: %s", cond.Type, cond.Status, cond.Message, cond.Reason)
-		}
-	}
-
-	return false, nil
-}
diff --git a/pkg/kf/apps/client.yml b/pkg/kf/apps/client.yml
index fa26f30..0e30dcb 100644
--- a/pkg/kf/apps/client.yml
+++ b/pkg/kf/apps/client.yml
@@ -6,6 +6,13 @@ kubernetes:
   kind: "App"
   version: "v1alpha1"
   namespaced: true
+  observedGenerationFieldPath: Status.ObservedGeneration
+  conditionsFieldPath: Status.Conditions
+  conditions:
+  - name: Ready
+    ref: v1alpha1.AppConditionReady
+  - name: ServiceBindingsReady
+    ref: v1alpha1.AppConditionServiceBindingsReady
 type: "v1alpha1.App"
 clientType: "cv1alpha1.AppsGetter"
 cf:
diff --git a/pkg/kf/apps/fake/fake_client.go b/pkg/kf/apps/fake/fake_client.go
index 37b153e..9f4e23f 100644
--- a/pkg/kf/apps/fake/fake_client.go
+++ b/pkg/kf/apps/fake/fake_client.go
@@ -267,6 +267,51 @@ func (mr *FakeClientMockRecorder) WaitFor(arg0, arg1, arg2, arg3, arg4 interface
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitFor", reflect.TypeOf((*FakeClient)(nil).WaitFor), arg0, arg1, arg2, arg3, arg4)
 }
 
+// WaitForConditionReadyTrue mocks base method
+func (m *FakeClient) WaitForConditionReadyTrue(arg0 context.Context, arg1, arg2 string, arg3 time.Duration) (*v1alpha1.App, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WaitForConditionReadyTrue", arg0, arg1, arg2, arg3)
+	ret0, _ := ret[0].(*v1alpha1.App)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// WaitForConditionReadyTrue indicates an expected call of WaitForConditionReadyTrue
+func (mr *FakeClientMockRecorder) WaitForConditionReadyTrue(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForConditionReadyTrue", reflect.TypeOf((*FakeClient)(nil).WaitForConditionReadyTrue), arg0, arg1, arg2, arg3)
+}
+
+// WaitForConditionServiceBindingsReadyTrue mocks base method
+func (m *FakeClient) WaitForConditionServiceBindingsReadyTrue(arg0 context.Context, arg1, arg2 string, arg3 time.Duration) (*v1alpha1.App, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WaitForConditionServiceBindingsReadyTrue", arg0, arg1, arg2, arg3)
+	ret0, _ := ret[0].(*v1alpha1.App)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// WaitForConditionServiceBindingsReadyTrue indicates an expected call of WaitForConditionServiceBindingsReadyTrue
+func (mr *FakeClientMockRecorder) WaitForConditionServiceBindingsReadyTrue(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForConditionServiceBindingsReadyTrue", reflect.TypeOf((*FakeClient)(nil).WaitForConditionServiceBindingsReadyTrue), arg0, arg1, arg2, arg3)
+}
+
+// WaitForDeletion mocks base method
+func (m *FakeClient) WaitForDeletion(arg0 context.Context, arg1, arg2 string, arg3 time.Duration) (*v1alpha1.App, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WaitForDeletion", arg0, arg1, arg2, arg3)
+	ret0, _ := ret[0].(*v1alpha1.App)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// WaitForDeletion indicates an expected call of WaitForDeletion
+func (mr *FakeClientMockRecorder) WaitForDeletion(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForDeletion", reflect.TypeOf((*FakeClient)(nil).WaitForDeletion), arg0, arg1, arg2, arg3)
+}
+
 // WaitForE mocks base method
 func (m *FakeClient) WaitForE(arg0 context.Context, arg1, arg2 string, arg3 time.Duration, arg4 apps.ConditionFuncE) (*v1alpha1.App, error) {
 	m.ctrl.T.Helper()
diff --git a/pkg/kf/apps/zz_generated.client.go b/pkg/kf/apps/zz_generated.client.go
index ca69d49..8c7e59d 100644
--- a/pkg/kf/apps/zz_generated.client.go
+++ b/pkg/kf/apps/zz_generated.client.go
@@ -27,6 +27,7 @@ import (
 
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"knative.dev/pkg/apis"
 	"knative.dev/pkg/kmp"
 )
 
@@ -48,6 +49,11 @@ const (
 	APIVersion = "v1alpha1"
 )
 
+var (
+	ConditionReady                = apis.ConditionType(v1alpha1.AppConditionReady)
+	ConditionServiceBindingsReady = apis.ConditionType(v1alpha1.AppConditionServiceBindingsReady)
+)
+
 // Predicate is a boolean function for a v1alpha1.App.
 type Predicate func(*v1alpha1.App) bool
 
@@ -104,6 +110,29 @@ func (list List) Filter(filter Predicate) (out List) {
 	return
 }
 
+// ObservedGenerationMatchesGeneration is a predicate that returns true if the
+// object's ObservedGeneration matches the genration of the object.
+func ObservedGenerationMatchesGeneration(obj *v1alpha1.App) bool {
+	return obj.Generation == obj.Status.ObservedGeneration
+}
+
+// ExtractConditions converts the native condition types into an apis.Condition
+// array with the Type, Status, Reason, and Message fields intact.
+func ExtractConditions(obj *v1alpha1.App) (extracted []apis.Condition) {
+	for _, cond := range obj.Status.Conditions {
+		// Only copy the following four fields to be compatible with
+		// recommended Kuberntes fields.
+		extracted = append(extracted, apis.Condition{
+			Type:    apis.ConditionType(cond.Type),
+			Status:  cond.Status,
+			Reason:  cond.Reason,
+			Message: cond.Message,
+		})
+	}
+
+	return
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Client
 ////////////////////////////////////////////////////////////////////////////////
@@ -120,6 +149,11 @@ type Client interface {
 	WaitFor(ctx context.Context, namespace string, name string, interval time.Duration, condition Predicate) (*v1alpha1.App, error)
 	WaitForE(ctx context.Context, namespace string, name string, interval time.Duration, condition ConditionFuncE) (*v1alpha1.App, error)
 
+	// Utility functions
+	WaitForDeletion(ctx context.Context, namespace string, name string, interval time.Duration) (*v1alpha1.App, error)
+	WaitForConditionReadyTrue(ctx context.Context, namespace string, name string, interval time.Duration) (*v1alpha1.App, error)
+	WaitForConditionServiceBindingsReadyTrue(ctx context.Context, namespace string, name string, interval time.Duration) (*v1alpha1.App, error)
+
 	// ClientExtension can be used by the developer to extend the client.
 	ClientExtension
 }
@@ -318,3 +352,60 @@ func wrapPredicate(condition Predicate) ConditionFuncE {
 		return condition(obj), nil
 	}
 }
+
+// WaitForDeletion is a utility function that combines WaitForE with ConditionDeleted.
+func (core *coreClient) WaitForDeletion(ctx context.Context, namespace string, name string, interval time.Duration) (instance *v1alpha1.App, err error) {
+	return core.WaitForE(ctx, namespace, name, interval, ConditionDeleted)
+}
+
+func checkConditionTrue(obj *v1alpha1.App, err error, condition apis.ConditionType) (bool, error) {
+	if err != nil {
+		return true, err
+	}
+
+	// don't propagate old statuses
+	if !ObservedGenerationMatchesGeneration(obj) {
+		return false, nil
+	}
+
+	for _, cond := range ExtractConditions(obj) {
+		if cond.Type == condition {
+			switch {
+			case cond.IsTrue():
+				return true, nil
+
+			case cond.IsUnknown():
+				return false, nil
+
+			default:
+				// return true and a failure assuming IsFalse and other statuses can't be
+				// recovered from because they violate the K8s spec
+				return true, fmt.Errorf("checking %s failed, status: %s message: %s reason: %s", cond.Type, cond.Status, cond.Message, cond.Reason)
+			}
+		}
+	}
+
+	return false, nil
+}
+
+// ConditionReadyTrue is a ConditionFuncE that waits for Condition{Ready v1alpha1.AppConditionReady } to
+// become true and fails with an error if the condition becomes false.
+func ConditionReadyTrue(obj *v1alpha1.App, err error) (bool, error) {
+	return checkConditionTrue(obj, err, ConditionReady)
+}
+
+// WaitForConditionReadyTrue is a utility function that combines WaitForE with ConditionReadyTrue.
+func (core *coreClient) WaitForConditionReadyTrue(ctx context.Context, namespace string, name string, interval time.Duration) (instance *v1alpha1.App, err error) {
+	return core.WaitForE(ctx, namespace, name, interval, ConditionReadyTrue)
+}
+
+// ConditionServiceBindingsReadyTrue is a ConditionFuncE that waits for Condition{ServiceBindingsReady v1alpha1.AppConditionServiceBindingsReady } to
+// become true and fails with an error if the condition becomes false.
+func ConditionServiceBindingsReadyTrue(obj *v1alpha1.App, err error) (bool, error) {
+	return checkConditionTrue(obj, err, ConditionServiceBindingsReady)
+}
+
+// WaitForConditionServiceBindingsReadyTrue is a utility function that combines WaitForE with ConditionServiceBindingsReadyTrue.
+func (core *coreClient) WaitForConditionServiceBindingsReadyTrue(ctx context.Context, namespace string, name string, interval time.Duration) (instance *v1alpha1.App, err error) {
+	return core.WaitForE(ctx, namespace, name, interval, ConditionServiceBindingsReadyTrue)
+}
diff --git a/pkg/kf/commands/config/config.go b/pkg/kf/commands/config/config.go
index 887a251..dec8485 100644
--- a/pkg/kf/commands/config/config.go
+++ b/pkg/kf/commands/config/config.go
@@ -27,8 +27,8 @@ import (
 	"github.com/google/kf/pkg/apis/kf/v1alpha1"
 	kf "github.com/google/kf/pkg/client/clientset/versioned/typed/kf/v1alpha1"
 	servicecatalogclient "github.com/google/kf/pkg/client/servicecatalog/clientset/versioned"
+	"github.com/google/kf/pkg/kf/marketplace"
 	"github.com/google/kf/pkg/kf/secrets"
-	"github.com/google/kf/pkg/kf/services"
 	"github.com/imdario/mergo"
 	build "github.com/knative/build/pkg/client/clientset/versioned/typed/build/v1alpha1"
 	serving "github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1"
@@ -249,7 +249,7 @@ func GetDynamicClient(p *KfParams) dynamic.Interface {
 }
 
 // GetSvcatApp returns a SvcatClient.
-func GetSvcatApp(p *KfParams) services.SClientFactory {
+func GetSvcatApp(p *KfParams) marketplace.SClientFactory {
 	return func(namespace string) servicecatalog.SvcatClient {
 		config := getRestConfig(p)
 
diff --git a/pkg/kf/commands/services/create-service.go b/pkg/kf/commands/services/create-service.go
index 035092a..2a05a5b 100644
--- a/pkg/kf/commands/services/create-service.go
+++ b/pkg/kf/commands/services/create-service.go
@@ -131,42 +131,33 @@ func NewCreateServiceCommand(p *config.KfParams, client servicecatalogclient.Int
 }
 
 func createServiceInstance(client servicecatalogclient.Interface, p *config.KfParams, rawParams *runtime.RawExtension, serviceName, planName, instanceName, broker string, spaceScoped bool) (*servicecatalogv1beta1.ServiceInstance, error) {
+	planRef := servicecatalogv1beta1.PlanReference{
+		ClusterServicePlanExternalName:  planName,
+		ClusterServiceClassExternalName: serviceName,
+	}
+
 	if spaceScoped {
-		return client.ServicecatalogV1beta1().
-			ServiceInstances(p.Namespace).
-			Create(&servicecatalogv1beta1.ServiceInstance{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      instanceName,
-					Namespace: p.Namespace,
-				},
-				Spec: servicecatalogv1beta1.ServiceInstanceSpec{
-					PlanReference: servicecatalogv1beta1.PlanReference{
-						ServicePlanExternalName:  planName,
-						ServiceClassExternalName: serviceName,
-					},
-					Parameters: rawParams,
-				},
-			})
-	} else {
-		return client.ServicecatalogV1beta1().
-			ServiceInstances(p.Namespace).
-			Create(&servicecatalogv1beta1.ServiceInstance{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      instanceName,
-					Namespace: p.Namespace,
-				},
-				Spec: servicecatalogv1beta1.ServiceInstanceSpec{
-					PlanReference: servicecatalogv1beta1.PlanReference{
-						ClusterServicePlanExternalName:  planName,
-						ClusterServiceClassExternalName: serviceName,
-					},
-					Parameters: rawParams,
-				},
-			})
+		planRef = servicecatalogv1beta1.PlanReference{
+			ServicePlanExternalName:  planName,
+			ServiceClassExternalName: serviceName,
+		}
 	}
+
+	return client.ServicecatalogV1beta1().
+		ServiceInstances(p.Namespace).
+		Create(&servicecatalogv1beta1.ServiceInstance{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      instanceName,
+				Namespace: p.Namespace,
+			},
+			Spec: servicecatalogv1beta1.ServiceInstanceSpec{
+				PlanReference: planRef,
+				Parameters:    rawParams,
+			},
+		})
 }
-func findMatchingClusterPlans(client servicecatalogclient.Interface, planName, serviceName, broker string) ([]servicecatalogv1beta1.ClusterServicePlan, error) {
 
+func findMatchingClusterPlans(client servicecatalogclient.Interface, planName, serviceName, broker string) ([]servicecatalogv1beta1.ClusterServicePlan, error) {
 	var matchingPlans []servicecatalogv1beta1.ClusterServicePlan
 
 	plans, err := client.ServicecatalogV1beta1().
diff --git a/pkg/kf/commands/services/delete-service.go b/pkg/kf/commands/services/delete-service.go
index 150ff96..024ade0 100644
--- a/pkg/kf/commands/services/delete-service.go
+++ b/pkg/kf/commands/services/delete-service.go
@@ -24,7 +24,7 @@ import (
 )
 
 // NewDeleteServiceCommand allows users to delete service instances.
-func NewDeleteServiceCommand(p *config.KfParams, client services.ClientInterface) *cobra.Command {
+func NewDeleteServiceCommand(p *config.KfParams, client services.Client) *cobra.Command {
 	deleteCmd := &cobra.Command{
 		Use:     "delete-service SERVICE_INSTANCE",
 		Aliases: []string{"ds"},
@@ -40,7 +40,7 @@ func NewDeleteServiceCommand(p *config.KfParams, client services.ClientInterface
 				return err
 			}
 			fmt.Fprintf(cmd.OutOrStdout(), "Deleting service instance %q %s", instanceName, utils.AsyncLogSuffix)
-			return client.DeleteService(instanceName, services.WithDeleteServiceNamespace(p.Namespace))
+			return client.Delete(p.Namespace, instanceName)
 		},
 	}
 
diff --git a/pkg/kf/commands/services/marketplace.go b/pkg/kf/commands/services/marketplace.go
index cb20a24..688d69f 100644
--- a/pkg/kf/commands/services/marketplace.go
+++ b/pkg/kf/commands/services/marketplace.go
@@ -21,13 +21,13 @@ import (
 	"github.com/google/kf/pkg/kf/commands/config"
 	"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/google/kf/pkg/kf/marketplace"
 	servicecatalog "github.com/poy/service-catalog/pkg/svcat/service-catalog"
 	"github.com/spf13/cobra"
 )
 
 // NewMarketplaceCommand allows users to get a service instance.
-func NewMarketplaceCommand(p *config.KfParams, client services.ClientInterface) *cobra.Command {
+func NewMarketplaceCommand(p *config.KfParams, client marketplace.ClientInterface) *cobra.Command {
 	var serviceName string
 
 	marketplaceCommand := &cobra.Command{
@@ -49,7 +49,7 @@ func NewMarketplaceCommand(p *config.KfParams, client services.ClientInterface)
 				return err
 			}
 
-			marketplace, err := client.Marketplace(services.WithMarketplaceNamespace(p.Namespace))
+			marketplace, err := client.Marketplace(p.Namespace)
 			if err != nil {
 				return err
 			}
diff --git a/pkg/kf/commands/services/service.go b/pkg/kf/commands/services/service.go
index 2ca4670..5f63676 100644
--- a/pkg/kf/commands/services/service.go
+++ b/pkg/kf/commands/services/service.go
@@ -23,7 +23,7 @@ import (
 )
 
 // NewGetServiceCommand allows users to get a service instance.
-func NewGetServiceCommand(p *config.KfParams, client services.ClientInterface) *cobra.Command {
+func NewGetServiceCommand(p *config.KfParams, client services.Client) *cobra.Command {
 	serviceCommand := &cobra.Command{
 		Use:     "service SERVICE_INSTANCE",
 		Short:   "Show service instance info",
@@ -38,7 +38,7 @@ func NewGetServiceCommand(p *config.KfParams, client services.ClientInterface) *
 
 			cmd.SilenceUsage = true
 
-			instance, err := client.GetService(instanceName, services.WithGetServiceNamespace(p.Namespace))
+			instance, err := client.Get(p.Namespace, instanceName)
 			if err != nil {
 				return err
 			}
diff --git a/pkg/kf/commands/services/services.go b/pkg/kf/commands/services/services.go
index ba98e59..d866877 100644
--- a/pkg/kf/commands/services/services.go
+++ b/pkg/kf/commands/services/services.go
@@ -24,6 +24,7 @@ import (
 	"github.com/google/kf/pkg/kf/commands/config"
 	"github.com/google/kf/pkg/kf/describe"
 	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
+	"github.com/google/kf/pkg/kf/marketplace"
 	"github.com/google/kf/pkg/kf/services"
 	"github.com/spf13/cobra"
 )
@@ -31,8 +32,9 @@ import (
 // NewListServicesCommand allows users to list service instances.
 func NewListServicesCommand(
 	p *config.KfParams,
-	client services.ClientInterface,
+	client services.Client,
 	appsClient apps.Client,
+	marketplaceClient marketplace.ClientInterface,
 ) *cobra.Command {
 	servicesCommand := &cobra.Command{
 		Use:     "services",
@@ -48,7 +50,7 @@ func NewListServicesCommand(
 				return err
 			}
 
-			instances, err := client.ListServices(services.WithListServicesNamespace(p.Namespace))
+			instances, err := client.List(p.Namespace)
 			if err != nil {
 				return err
 			}
@@ -61,10 +63,10 @@ func NewListServicesCommand(
 
 			describe.TabbedWriter(cmd.OutOrStdout(), func(w io.Writer) {
 				fmt.Fprintln(w, "Name\tService\tPlan\tBound Apps\tLast Operation\tBroker")
-				for _, instance := range instances.Items {
+				for _, instance := range instances {
 					lastCond := services.LastStatusCondition(instance)
 					var brokerInfo string
-					brokerInfo, err = client.BrokerName(instance)
+					brokerInfo, err = marketplaceClient.BrokerName(instance)
 					if err != nil {
 						brokerInfo = fmt.Sprintf("error finding broker: %s", err)
 					}
diff --git a/pkg/kf/commands/wire_gen.go b/pkg/kf/commands/wire_gen.go
index fbfc312..23806b5 100644
--- a/pkg/kf/commands/wire_gen.go
+++ b/pkg/kf/commands/wire_gen.go
@@ -8,6 +8,8 @@ package commands
 import (
 	"github.com/google/go-containerregistry/pkg/v1/remote"
 	"github.com/google/kf/pkg/client/clientset/versioned/typed/kf/v1alpha1"
+	"github.com/google/kf/pkg/client/servicecatalog/clientset/versioned"
+	"github.com/google/kf/pkg/client/servicecatalog/clientset/versioned/typed/servicecatalog/v1beta1"
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/buildpacks"
 	builds2 "github.com/google/kf/pkg/kf/builds"
@@ -24,6 +26,7 @@ import (
 	spaces2 "github.com/google/kf/pkg/kf/commands/spaces"
 	"github.com/google/kf/pkg/kf/istio"
 	"github.com/google/kf/pkg/kf/logs"
+	"github.com/google/kf/pkg/kf/marketplace"
 	"github.com/google/kf/pkg/kf/routeclaims"
 	"github.com/google/kf/pkg/kf/routes"
 	"github.com/google/kf/pkg/kf/service-bindings"
@@ -207,35 +210,40 @@ func InjectCreateService(p *config.KfParams) *cobra.Command {
 }
 
 func InjectDeleteService(p *config.KfParams) *cobra.Command {
-	sClientFactory := config.GetSvcatApp(p)
-	clientInterface := services2.NewClient(sClientFactory)
-	command := services.NewDeleteServiceCommand(p, clientInterface)
+	versionedInterface := config.GetServiceCatalogClient(p)
+	serviceInstancesGetter := provideServiceInstancesGetter(versionedInterface)
+	client := services2.NewClient(serviceInstancesGetter)
+	command := services.NewDeleteServiceCommand(p, client)
 	return command
 }
 
 func InjectGetService(p *config.KfParams) *cobra.Command {
-	sClientFactory := config.GetSvcatApp(p)
-	clientInterface := services2.NewClient(sClientFactory)
-	command := services.NewGetServiceCommand(p, clientInterface)
+	versionedInterface := config.GetServiceCatalogClient(p)
+	serviceInstancesGetter := provideServiceInstancesGetter(versionedInterface)
+	client := services2.NewClient(serviceInstancesGetter)
+	command := services.NewGetServiceCommand(p, client)
 	return command
 }
 
 func InjectListServices(p *config.KfParams) *cobra.Command {
-	sClientFactory := config.GetSvcatApp(p)
-	clientInterface := services2.NewClient(sClientFactory)
+	versionedInterface := config.GetServiceCatalogClient(p)
+	serviceInstancesGetter := provideServiceInstancesGetter(versionedInterface)
+	client := services2.NewClient(serviceInstancesGetter)
 	kfV1alpha1Interface := config.GetKfClient(p)
 	appsGetter := provideAppsGetter(kfV1alpha1Interface)
 	sourcesGetter := provideKfSources(kfV1alpha1Interface)
 	buildTailer := provideSourcesBuildTailer()
-	client := sources.NewClient(sourcesGetter, buildTailer)
-	appsClient := apps.NewClient(appsGetter, client)
-	command := services.NewListServicesCommand(p, clientInterface, appsClient)
+	sourcesClient := sources.NewClient(sourcesGetter, buildTailer)
+	appsClient := apps.NewClient(appsGetter, sourcesClient)
+	sClientFactory := config.GetSvcatApp(p)
+	clientInterface := marketplace.NewClient(sClientFactory)
+	command := services.NewListServicesCommand(p, client, appsClient, clientInterface)
 	return command
 }
 
 func InjectMarketplace(p *config.KfParams) *cobra.Command {
 	sClientFactory := config.GetSvcatApp(p)
-	clientInterface := services2.NewClient(sClientFactory)
+	clientInterface := marketplace.NewClient(sClientFactory)
 	command := services.NewMarketplaceCommand(p, clientInterface)
 	return command
 }
@@ -487,6 +495,14 @@ func provideCoreV1(p *config.KfParams) v1.CoreV1Interface {
 	return config.GetKubernetes(p).CoreV1()
 }
 
+func provideServiceInstancesGetter(sc versioned.Interface) v1beta1.ServiceInstancesGetter {
+	return sc.ServicecatalogV1beta1()
+}
+
+var ServicesSet = wire.NewSet(
+	provideServiceInstancesGetter, config.GetServiceCatalogClient, config.GetSvcatApp, marketplace.NewClient, services2.NewClient,
+)
+
 /////////////////
 // Buildpacks //
 ///////////////
diff --git a/pkg/kf/commands/wire_injector.go b/pkg/kf/commands/wire_injector.go
index 0a09941..6f85788 100644
--- a/pkg/kf/commands/wire_injector.go
+++ b/pkg/kf/commands/wire_injector.go
@@ -19,6 +19,8 @@ package commands
 import (
 	"github.com/google/go-containerregistry/pkg/v1/remote"
 	kfv1alpha1 "github.com/google/kf/pkg/client/clientset/versioned/typed/kf/v1alpha1"
+	servicecatalogclient "github.com/google/kf/pkg/client/servicecatalog/clientset/versioned"
+	scv1beta1 "github.com/google/kf/pkg/client/servicecatalog/clientset/versioned/typed/servicecatalog/v1beta1"
 	"github.com/google/kf/pkg/kf/apps"
 	"github.com/google/kf/pkg/kf/buildpacks"
 	"github.com/google/kf/pkg/kf/builds"
@@ -35,6 +37,7 @@ import (
 	cspaces "github.com/google/kf/pkg/kf/commands/spaces"
 	"github.com/google/kf/pkg/kf/istio"
 	kflogs "github.com/google/kf/pkg/kf/logs"
+	"github.com/google/kf/pkg/kf/marketplace"
 	"github.com/google/kf/pkg/kf/routeclaims"
 	"github.com/google/kf/pkg/kf/routes"
 	servicebindings "github.com/google/kf/pkg/kf/service-bindings"
@@ -173,37 +176,47 @@ func InjectUnsetEnv(p *config.KfParams) *cobra.Command {
 ////////////////
 // Services //
 /////////////
+
+func provideServiceInstancesGetter(sc servicecatalogclient.Interface) scv1beta1.ServiceInstancesGetter {
+	return sc.ServicecatalogV1beta1()
+}
+
+var ServicesSet = wire.NewSet(
+	provideServiceInstancesGetter,
+	config.GetServiceCatalogClient,
+	config.GetSvcatApp,
+	marketplace.NewClient,
+	services.NewClient,
+)
+
 func InjectCreateService(p *config.KfParams) *cobra.Command {
 	wire.Build(
-		config.GetServiceCatalogClient,
 		servicescmd.NewCreateServiceCommand,
+		ServicesSet,
 	)
 	return nil
 }
 
 func InjectDeleteService(p *config.KfParams) *cobra.Command {
 	wire.Build(
-		services.NewClient,
 		servicescmd.NewDeleteServiceCommand,
-		config.GetSvcatApp,
+		ServicesSet,
 	)
 	return nil
 }
 
 func InjectGetService(p *config.KfParams) *cobra.Command {
 	wire.Build(
-		services.NewClient,
 		servicescmd.NewGetServiceCommand,
-		config.GetSvcatApp,
+		ServicesSet,
 	)
 	return nil
 }
 
 func InjectListServices(p *config.KfParams) *cobra.Command {
 	wire.Build(
-		services.NewClient,
 		servicescmd.NewListServicesCommand,
-		config.GetSvcatApp,
+		ServicesSet,
 		AppsSet,
 	)
 	return nil
@@ -211,9 +224,8 @@ func InjectListServices(p *config.KfParams) *cobra.Command {
 
 func InjectMarketplace(p *config.KfParams) *cobra.Command {
 	wire.Build(
-		services.NewClient,
 		servicescmd.NewMarketplaceCommand,
-		config.GetSvcatApp,
+		ServicesSet,
 	)
 	return nil
 }
diff --git a/pkg/kf/internal/tools/clientgen/client.go b/pkg/kf/internal/tools/clientgen/client.go
index adcc632..84dd5eb 100644
--- a/pkg/kf/internal/tools/clientgen/client.go
+++ b/pkg/kf/internal/tools/clientgen/client.go
@@ -18,9 +18,36 @@ package clientgen
 
 import (
 	"bytes"
+	"fmt"
 	"text/template"
 )
 
+type Condition struct {
+	Name string `json:"name"`
+	Ref  string `json:"ref"`
+	Val  string `json:"value"`
+}
+
+func (c *Condition) Definition() string {
+	if c.Ref != "" {
+		return c.Ref
+	}
+
+	return fmt.Sprintf("%q", c.Val)
+}
+
+func (c *Condition) ConditionName() string {
+	return fmt.Sprintf("Condition%s", c.Name)
+}
+
+func (c *Condition) PredicateName() string {
+	return fmt.Sprintf("%sTrue", c.ConditionName())
+}
+
+func (c *Condition) WaitForName() string {
+	return fmt.Sprintf("WaitFor%s", c.PredicateName())
+}
+
 type ClientParams struct {
 	// Package is the package the client will be generated in.
 	Package string `json:"package"`
@@ -39,6 +66,15 @@ type ClientParams struct {
 		// Plural contains the pluralizataion of kind. If blank, default of Kind+"s"
 		// is assumed.
 		Plural string `json:"plural"`
+		// ObservedGenerationField contains the name of the field used for
+		// ObservedGenerations (if the resource suuports it).
+		ObservedGenerationFieldPath string `json:"observedGenerationFieldPath"`
+		// ConditionField contains the name of the field used for
+		// conditions.
+		ConditionsFieldPath string `json:"conditionsFieldPath"`
+		// Conditions holds the names of interesting conditions that will be turned
+		// into predicates.
+		Conditions []Condition `json:"conditions"`
 	} `json:"kubernetes"`
 
 	// CF contains information about this resource from a CF side.
@@ -54,6 +90,17 @@ type ClientParams struct {
 	ClientType string `json:"clientType"`
 }
 
+// SupportsObservedGeneration returns true if the type supports
+// ObservedGeneration fields.
+func (f *ClientParams) SupportsObservedGeneration() bool {
+	return f.Kubernetes.ObservedGenerationFieldPath != ""
+}
+
+// SupportsConditions returns true if the type supports condition fields.
+func (f *ClientParams) SupportsConditions() bool {
+	return f.Kubernetes.ConditionsFieldPath != ""
+}
+
 func (f *ClientParams) Render() ([]byte, error) {
 	buf := &bytes.Buffer{}
 
diff --git a/pkg/kf/internal/tools/clientgen/tmplclient.go b/pkg/kf/internal/tools/clientgen/tmplclient.go
index f7e1b59..f3cb44e 100644
--- a/pkg/kf/internal/tools/clientgen/tmplclient.go
+++ b/pkg/kf/internal/tools/clientgen/tmplclient.go
@@ -29,6 +29,7 @@ var clientTemplate = template.Must(template.New("").Funcs(generator.TemplateFunc
 {{ $nssig := "" }}
 {{ $ns := "" }}
 {{ $nsparam := "" }}
+{{ $type := .Type }}
 
 {{ if .Kubernetes.Namespaced }}
   {{ $nssig = "namespace string," }}
@@ -48,6 +49,12 @@ type Client interface {
 	WaitFor(ctx context.Context, {{ $nssig }} name string, interval time.Duration, condition Predicate) (*{{.Type}}, error)
 	WaitForE(ctx context.Context, {{ $nssig }} name string, interval time.Duration, condition ConditionFuncE) (*{{.Type}}, error)
 
+	// Utility functions
+	WaitForDeletion(ctx context.Context, {{ $nssig }} name string, interval time.Duration) (*{{.Type}}, error)
+	{{ if .SupportsConditions }}{{ range .Kubernetes.Conditions }}{{.WaitForName}}(ctx context.Context, {{ $nssig }} name string, interval time.Duration) (*{{$type}}, error)
+	{{ end }}
+	{{ end }}
+
 	// ClientExtension can be used by the developer to extend the client.
 	ClientExtension
 }
@@ -246,4 +253,56 @@ func wrapPredicate(condition Predicate) ConditionFuncE {
 		return condition(obj), nil
 	}
 }
+
+// WaitForDeletion is a utility function that combines WaitForE with ConditionDeleted.
+func (core *coreClient) WaitForDeletion(ctx context.Context, {{ $nssig }} name string, interval time.Duration) (instance *{{.Type}}, err error) {
+	return core.WaitForE(ctx, {{ $nsparam }} name, interval, ConditionDeleted)
+}
+
+{{ if .SupportsConditions }}
+func checkConditionTrue(obj *{{.Type}}, err error, condition apis.ConditionType) (bool, error) {
+	if err != nil {
+		return true, err
+	}
+
+	{{ if .SupportsObservedGeneration }}// don't propagate old statuses
+	if !ObservedGenerationMatchesGeneration(obj){
+		return false, nil
+	}
+	{{ end }}
+	for _, cond := range ExtractConditions(obj) {
+		if cond.Type == condition {
+			switch {
+			case cond.IsTrue():
+				return true, nil
+
+			case cond.IsUnknown():
+				return false, nil
+
+			default:
+				// return true and a failure assuming IsFalse and other statuses can't be
+				// recovered from because they violate the K8s spec
+				return true, fmt.Errorf("checking %s failed, status: %s message: %s reason: %s", cond.Type, cond.Status, cond.Message, cond.Reason)
+			}
+		}
+	}
+
+	return false, nil
+}
+
+{{ range .Kubernetes.Conditions }}
+// {{.PredicateName}} is a ConditionFuncE that waits for Condition{{.}} to
+// become true and fails with an error if the condition becomes false.
+func {{.PredicateName}}(obj *{{$type}}, err error) (bool, error) {
+	return checkConditionTrue(obj, err, {{.ConditionName}})
+}
+
+// {{.WaitForName}} is a utility function that combines WaitForE with {{.PredicateName}}.
+func (core *coreClient) {{.WaitForName}}(ctx context.Context, {{ $nssig }} name string, interval time.Duration) (instance *{{$type}}, err error) {
+	return core.WaitForE(ctx, {{ $nsparam }} name, interval, {{.PredicateName}})
+}
+{{ end }}
+
+{{ end }}
+
 `))
diff --git a/pkg/kf/internal/tools/clientgen/tmplfunctional.go b/pkg/kf/internal/tools/clientgen/tmplfunctional.go
index 361c76d..5d429c3 100644
--- a/pkg/kf/internal/tools/clientgen/tmplfunctional.go
+++ b/pkg/kf/internal/tools/clientgen/tmplfunctional.go
@@ -34,6 +34,13 @@ const (
 	APIVersion = "{{.Kubernetes.Version}}"
 )
 
+{{ if .SupportsConditions }}
+var (
+	{{ range .Kubernetes.Conditions }}
+	{{.ConditionName}} = apis.ConditionType({{.Definition}}){{ end }}
+)
+{{ end }}
+
 // Predicate is a boolean function for a {{.Type}}.
 type Predicate func(*{{.Type}}) bool
 
@@ -90,4 +97,31 @@ func (list List) Filter(filter Predicate) (out List) {
 	return
 }
 
+
+{{ if .SupportsObservedGeneration }}
+// ObservedGenerationMatchesGeneration is a predicate that returns true if the
+// object's ObservedGeneration matches the genration of the object.
+func ObservedGenerationMatchesGeneration(obj *{{.Type}}) bool {
+	return obj.Generation == obj.{{.Kubernetes.ObservedGenerationFieldPath}}
+}
+{{ end }}
+
+{{ if .SupportsConditions }}
+// ExtractConditions converts the native condition types into an apis.Condition
+// array with the Type, Status, Reason, and Message fields intact.
+func ExtractConditions(obj *{{.Type}}) (extracted []apis.Condition) {
+	for _, cond := range obj.{{.Kubernetes.ConditionsFieldPath}} {
+		// Only copy the following four fields to be compatible with
+		// recommended Kuberntes fields.
+		extracted = append(extracted, apis.Condition{
+			Type:    apis.ConditionType(cond.Type),
+			Status:  cond.Status,
+			Reason:  cond.Reason,
+			Message: cond.Message,
+		})
+	}
+
+	return
+}
+{{ end }}
 `))
diff --git a/pkg/kf/internal/tools/clientgen/tmplheader.go b/pkg/kf/internal/tools/clientgen/tmplheader.go
index a61e280..d3bb7ff 100644
--- a/pkg/kf/internal/tools/clientgen/tmplheader.go
+++ b/pkg/kf/internal/tools/clientgen/tmplheader.go
@@ -37,6 +37,7 @@ import (
 	"time"
 
 	"knative.dev/pkg/kmp"
+	{{ if .SupportsConditions }}"knative.dev/pkg/apis"{{ end }}
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
diff --git a/pkg/kf/services/services.go b/pkg/kf/marketplace/client.go
similarity index 55%
rename from pkg/kf/services/services.go
rename to pkg/kf/marketplace/client.go
index 9fee58d..a1c6305 100644
--- a/pkg/kf/services/services.go
+++ b/pkg/kf/marketplace/client.go
@@ -12,15 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package services
+package marketplace
 
 import (
 	"github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
 	servicecatalog "github.com/poy/service-catalog/pkg/svcat/service-catalog"
 )
 
-//go:generate go run ../internal/tools/option-builder/option-builder.go options.yml options.go
-
 // KfMarketplace contains information to describe the
 // services and plans available in the catalog.
 type KfMarketplace struct {
@@ -31,21 +29,11 @@ type KfMarketplace struct {
 // ClientInterface is a client capable of interacting with service catalog services
 // and mapping the CF to Kubernetes concepts.
 type ClientInterface interface {
-
-	// DeleteService removes an instance of a service on the cluster.
-	DeleteService(instanceName string, opts ...DeleteServiceOption) error
-
-	// GetService gets an instance of a service on the cluster.
-	GetService(instanceName string, opts ...GetServiceOption) (*v1beta1.ServiceInstance, error)
-
-	// ListServices lists instances of services on the cluster.
-	ListServices(opts ...ListServicesOption) (*v1beta1.ServiceInstanceList, error)
-
 	// Marketplace lists available services and plans in the marketplace.
-	Marketplace(opts ...MarketplaceOption) (*KfMarketplace, error)
+	Marketplace(namespace string) (*KfMarketplace, error)
 
 	// BrokerName fetches the service broker name for a service.
-	BrokerName(service v1beta1.ServiceInstance, opts ...BrokerNameOption) (string, error)
+	BrokerName(service v1beta1.ServiceInstance) (string, error)
 }
 
 // SClientFactory creates a Service Catalog client.
@@ -64,40 +52,12 @@ type Client struct {
 	createSvcatClient SClientFactory
 }
 
-// DeleteService removes an instance of a service on the cluster.
-func (c *Client) DeleteService(instanceName string, opts ...DeleteServiceOption) error {
-	cfg := DeleteServiceOptionDefaults().Extend(opts).toConfig()
-
-	svcat := c.createSvcatClient(cfg.Namespace)
-	return svcat.Deprovision(cfg.Namespace, instanceName)
-}
-
-// GetService gets an instance of a service on the cluster.
-func (c *Client) GetService(instanceName string, opts ...GetServiceOption) (*v1beta1.ServiceInstance, error) {
-	cfg := GetServiceOptionDefaults().Extend(opts).toConfig()
-
-	svcat := c.createSvcatClient(cfg.Namespace)
-	return svcat.RetrieveInstance(cfg.Namespace, instanceName)
-}
-
-// ListServices lists instances of services on the cluster.
-func (c *Client) ListServices(opts ...ListServicesOption) (*v1beta1.ServiceInstanceList, error) {
-	cfg := ListServicesOptionDefaults().Extend(opts).toConfig()
-
-	svcat := c.createSvcatClient(cfg.Namespace)
-
-	// RetrieveInstances(ns, classFilter, planFilter string)
-	return svcat.RetrieveInstances(cfg.Namespace, "", "")
-}
-
 // Marketplace lists available services and plans in the marketplace.
-func (c *Client) Marketplace(opts ...MarketplaceOption) (*KfMarketplace, error) {
-	cfg := MarketplaceOptionDefaults().Extend(opts).toConfig()
-
-	svcat := c.createSvcatClient(cfg.Namespace)
+func (c *Client) Marketplace(namespace string) (*KfMarketplace, error) {
+	svcat := c.createSvcatClient(namespace)
 
 	scope := servicecatalog.ScopeOptions{
-		Namespace: cfg.Namespace,
+		Namespace: namespace,
 		Scope:     servicecatalog.AllScope,
 	}
 
@@ -119,9 +79,8 @@ func (c *Client) Marketplace(opts ...MarketplaceOption) (*KfMarketplace, error)
 }
 
 // BrokerName fetches the service broker name for a service.
-func (c *Client) BrokerName(service v1beta1.ServiceInstance, opts ...BrokerNameOption) (string, error) {
-	cfg := BrokerNameOptionDefaults().Extend(opts).toConfig()
-	svcat := c.createSvcatClient(cfg.Namespace)
+func (c *Client) BrokerName(service v1beta1.ServiceInstance) (string, error) {
+	svcat := c.createSvcatClient(service.GetNamespace())
 
 	scope := servicecatalog.ScopeOptions{
 		Scope: servicecatalog.ClusterScope,
diff --git a/pkg/kf/marketplace/doc.go b/pkg/kf/marketplace/doc.go
new file mode 100644
index 0000000..3e5133c
--- /dev/null
+++ b/pkg/kf/marketplace/doc.go
@@ -0,0 +1,17 @@
+// 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 marketplace provides access to the resources provided by service
+// brokers for consumption by developers.
+package marketplace
diff --git a/pkg/kf/marketplace/fake/fake_client_interface.go b/pkg/kf/marketplace/fake/fake_client_interface.go
new file mode 100644
index 0000000..105dd11
--- /dev/null
+++ b/pkg/kf/marketplace/fake/fake_client_interface.go
@@ -0,0 +1,80 @@
+// 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.
+//
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/google/kf/pkg/kf/marketplace/fake (interfaces: ClientInterface)
+
+// Package fake is a generated GoMock package.
+package fake
+
+import (
+	gomock "github.com/golang/mock/gomock"
+	marketplace "github.com/google/kf/pkg/kf/marketplace"
+	v1beta1 "github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
+	reflect "reflect"
+)
+
+// FakeClientInterface is a mock of ClientInterface interface
+type FakeClientInterface struct {
+	ctrl     *gomock.Controller
+	recorder *FakeClientInterfaceMockRecorder
+}
+
+// FakeClientInterfaceMockRecorder is the mock recorder for FakeClientInterface
+type FakeClientInterfaceMockRecorder struct {
+	mock *FakeClientInterface
+}
+
+// NewFakeClientInterface creates a new mock instance
+func NewFakeClientInterface(ctrl *gomock.Controller) *FakeClientInterface {
+	mock := &FakeClientInterface{ctrl: ctrl}
+	mock.recorder = &FakeClientInterfaceMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *FakeClientInterface) EXPECT() *FakeClientInterfaceMockRecorder {
+	return m.recorder
+}
+
+// BrokerName mocks base method
+func (m *FakeClientInterface) BrokerName(arg0 v1beta1.ServiceInstance) (string, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "BrokerName", arg0)
+	ret0, _ := ret[0].(string)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// BrokerName indicates an expected call of BrokerName
+func (mr *FakeClientInterfaceMockRecorder) BrokerName(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BrokerName", reflect.TypeOf((*FakeClientInterface)(nil).BrokerName), arg0)
+}
+
+// Marketplace mocks base method
+func (m *FakeClientInterface) Marketplace(arg0 string) (*marketplace.KfMarketplace, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Marketplace", arg0)
+	ret0, _ := ret[0].(*marketplace.KfMarketplace)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Marketplace indicates an expected call of Marketplace
+func (mr *FakeClientInterfaceMockRecorder) Marketplace(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marketplace", reflect.TypeOf((*FakeClientInterface)(nil).Marketplace), arg0)
+}
diff --git a/pkg/kf/marketplace/fake/interfaces.go b/pkg/kf/marketplace/fake/interfaces.go
new file mode 100644
index 0000000..5200530
--- /dev/null
+++ b/pkg/kf/marketplace/fake/interfaces.go
@@ -0,0 +1,24 @@
+// 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 fake
+
+import "github.com/google/kf/pkg/kf/marketplace"
+
+//go:generate mockgen --package=fake --destination=fake_client_interface.go --copyright_file ../../internal/tools/option-builder/LICENSE_HEADER --mock_names=ClientInterface=FakeClientInterface github.com/google/kf/pkg/kf/marketplace/fake ClientInterface
+
+// ClientInterface is implementd by marketplace.Client.
+type ClientInterface interface {
+	marketplace.ClientInterface
+}
diff --git a/pkg/kf/routeclaims/fake/fake_client.go b/pkg/kf/routeclaims/fake/fake_client.go
index 762fce8..cbe5485 100644
--- a/pkg/kf/routeclaims/fake/fake_client.go
+++ b/pkg/kf/routeclaims/fake/fake_client.go
@@ -195,6 +195,21 @@ func (mr *FakeClientMockRecorder) WaitFor(arg0, arg1, arg2, arg3, arg4 interface
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitFor", reflect.TypeOf((*FakeClient)(nil).WaitFor), arg0, arg1, arg2, arg3, arg4)
 }
 
+// WaitForDeletion mocks base method
+func (m *FakeClient) WaitForDeletion(arg0 context.Context, arg1, arg2 string, arg3 time.Duration) (*v1alpha1.RouteClaim, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WaitForDeletion", arg0, arg1, arg2, arg3)
+	ret0, _ := ret[0].(*v1alpha1.RouteClaim)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// WaitForDeletion indicates an expected call of WaitForDeletion
+func (mr *FakeClientMockRecorder) WaitForDeletion(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForDeletion", reflect.TypeOf((*FakeClient)(nil).WaitForDeletion), arg0, arg1, arg2, arg3)
+}
+
 // WaitForE mocks base method
 func (m *FakeClient) WaitForE(arg0 context.Context, arg1, arg2 string, arg3 time.Duration, arg4 routeclaims.ConditionFuncE) (*v1alpha1.RouteClaim, error) {
 	m.ctrl.T.Helper()
diff --git a/pkg/kf/routeclaims/zz_generated.client.go b/pkg/kf/routeclaims/zz_generated.client.go
index b03bfe7..37690af 100644
--- a/pkg/kf/routeclaims/zz_generated.client.go
+++ b/pkg/kf/routeclaims/zz_generated.client.go
@@ -25,9 +25,10 @@ import (
 	"strings"
 	"time"
 
+	"knative.dev/pkg/kmp"
+
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"knative.dev/pkg/kmp"
 )
 
 // User defined imports
@@ -120,6 +121,9 @@ type Client interface {
 	WaitFor(ctx context.Context, namespace string, name string, interval time.Duration, condition Predicate) (*v1alpha1.RouteClaim, error)
 	WaitForE(ctx context.Context, namespace string, name string, interval time.Duration, condition ConditionFuncE) (*v1alpha1.RouteClaim, error)
 
+	// Utility functions
+	WaitForDeletion(ctx context.Context, namespace string, name string, interval time.Duration) (*v1alpha1.RouteClaim, error)
+
 	// ClientExtension can be used by the developer to extend the client.
 	ClientExtension
 }
@@ -318,3 +322,8 @@ func wrapPredicate(condition Predicate) ConditionFuncE {
 		return condition(obj), nil
 	}
 }
+
+// WaitForDeletion is a utility function that combines WaitForE with ConditionDeleted.
+func (core *coreClient) WaitForDeletion(ctx context.Context, namespace string, name string, interval time.Duration) (instance *v1alpha1.RouteClaim, err error) {
+	return core.WaitForE(ctx, namespace, name, interval, ConditionDeleted)
+}
diff --git a/pkg/kf/routes/fake/fake_client.go b/pkg/kf/routes/fake/fake_client.go
index 97c55da..fede700 100644
--- a/pkg/kf/routes/fake/fake_client.go
+++ b/pkg/kf/routes/fake/fake_client.go
@@ -195,6 +195,21 @@ func (mr *FakeClientMockRecorder) WaitFor(arg0, arg1, arg2, arg3, arg4 interface
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitFor", reflect.TypeOf((*FakeClient)(nil).WaitFor), arg0, arg1, arg2, arg3, arg4)
 }
 
+// WaitForDeletion mocks base method
+func (m *FakeClient) WaitForDeletion(arg0 context.Context, arg1, arg2 string, arg3 time.Duration) (*v1alpha1.Route, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WaitForDeletion", arg0, arg1, arg2, arg3)
+	ret0, _ := ret[0].(*v1alpha1.Route)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// WaitForDeletion indicates an expected call of WaitForDeletion
+func (mr *FakeClientMockRecorder) WaitForDeletion(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForDeletion", reflect.TypeOf((*FakeClient)(nil).WaitForDeletion), arg0, arg1, arg2, arg3)
+}
+
 // WaitForE mocks base method
 func (m *FakeClient) WaitForE(arg0 context.Context, arg1, arg2 string, arg3 time.Duration, arg4 routes.ConditionFuncE) (*v1alpha1.Route, error) {
 	m.ctrl.T.Helper()
diff --git a/pkg/kf/routes/zz_generated.client.go b/pkg/kf/routes/zz_generated.client.go
index a19c97b..5fcf291 100644
--- a/pkg/kf/routes/zz_generated.client.go
+++ b/pkg/kf/routes/zz_generated.client.go
@@ -25,9 +25,10 @@ import (
 	"strings"
 	"time"
 
+	"knative.dev/pkg/kmp"
+
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"knative.dev/pkg/kmp"
 )
 
 // User defined imports
@@ -120,6 +121,9 @@ type Client interface {
 	WaitFor(ctx context.Context, namespace string, name string, interval time.Duration, condition Predicate) (*v1alpha1.Route, error)
 	WaitForE(ctx context.Context, namespace string, name string, interval time.Duration, condition ConditionFuncE) (*v1alpha1.Route, error)
 
+	// Utility functions
+	WaitForDeletion(ctx context.Context, namespace string, name string, interval time.Duration) (*v1alpha1.Route, error)
+
 	// ClientExtension can be used by the developer to extend the client.
 	ClientExtension
 }
@@ -318,3 +322,8 @@ func wrapPredicate(condition Predicate) ConditionFuncE {
 		return condition(obj), nil
 	}
 }
+
+// WaitForDeletion is a utility function that combines WaitForE with ConditionDeleted.
+func (core *coreClient) WaitForDeletion(ctx context.Context, namespace string, name string, interval time.Duration) (instance *v1alpha1.Route, err error) {
+	return core.WaitForE(ctx, namespace, name, interval, ConditionDeleted)
+}
diff --git a/pkg/kf/service-bindings/client.go b/pkg/kf/service-bindings/client.go
index 9440488..a27365d 100644
--- a/pkg/kf/service-bindings/client.go
+++ b/pkg/kf/service-bindings/client.go
@@ -140,7 +140,7 @@ func (c *Client) List(opts ...ListOption) ([]servicecatalogv1beta1.ServiceBindin
 
 // WaitForBindings waits for bindings to all be ready or fail on the given app.
 func (c *Client) WaitForBindings(ctx context.Context, namespace, appName string) error {
-	_, err := c.appsClient.WaitForE(ctx, namespace, appName, 2*time.Second, apps.ConditionServiceBindingsReady)
+	_, err := c.appsClient.WaitForConditionServiceBindingsReadyTrue(ctx, namespace, appName, 2*time.Second)
 	return err
 }
 
diff --git a/pkg/kf/services/client.go b/pkg/kf/services/client.go
new file mode 100644
index 0000000..0a2ace6
--- /dev/null
+++ b/pkg/kf/services/client.go
@@ -0,0 +1,30 @@
+// 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 services
+
+import (
+	cv1beta1 "github.com/google/kf/pkg/client/servicecatalog/clientset/versioned/typed/servicecatalog/v1beta1"
+)
+
+// ClientExtension holds additional functions that should be exposed by client.
+type ClientExtension interface {
+}
+
+// NewClient creates a new service client.
+func NewClient(kclient cv1beta1.ServiceInstancesGetter) Client {
+	return &coreClient{
+		kclient: kclient,
+	}
+}
diff --git a/pkg/kf/services/client.yml b/pkg/kf/services/client.yml
new file mode 100644
index 0000000..8a16c83
--- /dev/null
+++ b/pkg/kf/services/client.yml
@@ -0,0 +1,14 @@
+# This file contains options for genfunctional.go
+---
+package: services
+imports:
+  github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1: v1beta1
+  github.com/google/kf/pkg/client/servicecatalog/clientset/versioned/typed/servicecatalog/v1beta1: cv1beta1
+kubernetes:
+  kind: "ServiceInstance"
+  version: "v1beta1"
+  namespaced: true
+type: "v1beta1.ServiceInstance"
+clientType: "cv1beta1.ServiceInstancesGetter"
+cf:
+  name: "Service"
diff --git a/pkg/kf/services/doc.go b/pkg/kf/services/doc.go
new file mode 100644
index 0000000..8301736
--- /dev/null
+++ b/pkg/kf/services/doc.go
@@ -0,0 +1,20 @@
+// 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 services contains the client for working with service catalog
+// service instances.
+package services
+
+//go:generate go run ../internal/tools/option-builder/option-builder.go --pkg services ../internal/tools/clientgen/common-options.yml zz_generated.clientoptions.go
+//go:generate go run ../internal/tools/clientgen/genclient.go client.yml
diff --git a/pkg/kf/services/fake/fake_client.go b/pkg/kf/services/fake/fake_client.go
new file mode 100644
index 0000000..571bf5e
--- /dev/null
+++ b/pkg/kf/services/fake/fake_client.go
@@ -0,0 +1,211 @@
+// 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.
+//
+
+// Code generated by MockGen. DO NOT EDIT.
+// Source: github.com/google/kf/pkg/kf/services/fake (interfaces: Client)
+
+// Package fake is a generated GoMock package.
+package fake
+
+import (
+	context "context"
+	gomock "github.com/golang/mock/gomock"
+	services "github.com/google/kf/pkg/kf/services"
+	v1beta1 "github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
+	reflect "reflect"
+	time "time"
+)
+
+// FakeClient is a mock of Client interface
+type FakeClient struct {
+	ctrl     *gomock.Controller
+	recorder *FakeClientMockRecorder
+}
+
+// FakeClientMockRecorder is the mock recorder for FakeClient
+type FakeClientMockRecorder struct {
+	mock *FakeClient
+}
+
+// NewFakeClient creates a new mock instance
+func NewFakeClient(ctrl *gomock.Controller) *FakeClient {
+	mock := &FakeClient{ctrl: ctrl}
+	mock.recorder = &FakeClientMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *FakeClient) EXPECT() *FakeClientMockRecorder {
+	return m.recorder
+}
+
+// Create mocks base method
+func (m *FakeClient) Create(arg0 string, arg1 *v1beta1.ServiceInstance, arg2 ...services.CreateOption) (*v1beta1.ServiceInstance, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{arg0, arg1}
+	for _, a := range arg2 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "Create", varargs...)
+	ret0, _ := ret[0].(*v1beta1.ServiceInstance)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Create indicates an expected call of Create
+func (mr *FakeClientMockRecorder) Create(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{arg0, arg1}, arg2...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*FakeClient)(nil).Create), varargs...)
+}
+
+// Delete mocks base method
+func (m *FakeClient) Delete(arg0, arg1 string, arg2 ...services.DeleteOption) error {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{arg0, arg1}
+	for _, a := range arg2 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "Delete", varargs...)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Delete indicates an expected call of Delete
+func (mr *FakeClientMockRecorder) Delete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{arg0, arg1}, arg2...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*FakeClient)(nil).Delete), varargs...)
+}
+
+// Get mocks base method
+func (m *FakeClient) Get(arg0, arg1 string, arg2 ...services.GetOption) (*v1beta1.ServiceInstance, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{arg0, arg1}
+	for _, a := range arg2 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "Get", varargs...)
+	ret0, _ := ret[0].(*v1beta1.ServiceInstance)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Get indicates an expected call of Get
+func (mr *FakeClientMockRecorder) Get(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{arg0, arg1}, arg2...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*FakeClient)(nil).Get), varargs...)
+}
+
+// List mocks base method
+func (m *FakeClient) List(arg0 string, arg1 ...services.ListOption) ([]v1beta1.ServiceInstance, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{arg0}
+	for _, a := range arg1 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "List", varargs...)
+	ret0, _ := ret[0].([]v1beta1.ServiceInstance)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// List indicates an expected call of List
+func (mr *FakeClientMockRecorder) List(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{arg0}, arg1...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*FakeClient)(nil).List), varargs...)
+}
+
+// Transform mocks base method
+func (m *FakeClient) Transform(arg0, arg1 string, arg2 services.Mutator) (*v1beta1.ServiceInstance, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Transform", arg0, arg1, arg2)
+	ret0, _ := ret[0].(*v1beta1.ServiceInstance)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Transform indicates an expected call of Transform
+func (mr *FakeClientMockRecorder) Transform(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Transform", reflect.TypeOf((*FakeClient)(nil).Transform), arg0, arg1, arg2)
+}
+
+// Update mocks base method
+func (m *FakeClient) Update(arg0 string, arg1 *v1beta1.ServiceInstance, arg2 ...services.UpdateOption) (*v1beta1.ServiceInstance, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{arg0, arg1}
+	for _, a := range arg2 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "Update", varargs...)
+	ret0, _ := ret[0].(*v1beta1.ServiceInstance)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Update indicates an expected call of Update
+func (mr *FakeClientMockRecorder) Update(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{arg0, arg1}, arg2...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*FakeClient)(nil).Update), varargs...)
+}
+
+// Upsert mocks base method
+func (m *FakeClient) Upsert(arg0 string, arg1 *v1beta1.ServiceInstance, arg2 services.Merger) (*v1beta1.ServiceInstance, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Upsert", arg0, arg1, arg2)
+	ret0, _ := ret[0].(*v1beta1.ServiceInstance)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Upsert indicates an expected call of Upsert
+func (mr *FakeClientMockRecorder) Upsert(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upsert", reflect.TypeOf((*FakeClient)(nil).Upsert), arg0, arg1, arg2)
+}
+
+// WaitFor mocks base method
+func (m *FakeClient) WaitFor(arg0 context.Context, arg1, arg2 string, arg3 time.Duration, arg4 services.Predicate) (*v1beta1.ServiceInstance, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WaitFor", arg0, arg1, arg2, arg3, arg4)
+	ret0, _ := ret[0].(*v1beta1.ServiceInstance)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// WaitFor indicates an expected call of WaitFor
+func (mr *FakeClientMockRecorder) WaitFor(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitFor", reflect.TypeOf((*FakeClient)(nil).WaitFor), arg0, arg1, arg2, arg3, arg4)
+}
+
+// WaitForE mocks base method
+func (m *FakeClient) WaitForE(arg0 context.Context, arg1, arg2 string, arg3 time.Duration, arg4 services.ConditionFuncE) (*v1beta1.ServiceInstance, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WaitForE", arg0, arg1, arg2, arg3, arg4)
+	ret0, _ := ret[0].(*v1beta1.ServiceInstance)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// WaitForE indicates an expected call of WaitForE
+func (mr *FakeClientMockRecorder) WaitForE(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForE", reflect.TypeOf((*FakeClient)(nil).WaitForE), arg0, arg1, arg2, arg3, arg4)
+}
diff --git a/pkg/kf/services/fake/fake_client_interface.go b/pkg/kf/services/fake/fake_client_interface.go
deleted file mode 100644
index 23b1eac..0000000
--- a/pkg/kf/services/fake/fake_client_interface.go
+++ /dev/null
@@ -1,147 +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.
-//
-
-// Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/google/kf/pkg/kf/services/fake (interfaces: ClientInterface)
-
-// Package fake is a generated GoMock package.
-package fake
-
-import (
-	gomock "github.com/golang/mock/gomock"
-	services "github.com/google/kf/pkg/kf/services"
-	v1beta1 "github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
-	reflect "reflect"
-)
-
-// FakeClientInterface is a mock of ClientInterface interface
-type FakeClientInterface struct {
-	ctrl     *gomock.Controller
-	recorder *FakeClientInterfaceMockRecorder
-}
-
-// FakeClientInterfaceMockRecorder is the mock recorder for FakeClientInterface
-type FakeClientInterfaceMockRecorder struct {
-	mock *FakeClientInterface
-}
-
-// NewFakeClientInterface creates a new mock instance
-func NewFakeClientInterface(ctrl *gomock.Controller) *FakeClientInterface {
-	mock := &FakeClientInterface{ctrl: ctrl}
-	mock.recorder = &FakeClientInterfaceMockRecorder{mock}
-	return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use
-func (m *FakeClientInterface) EXPECT() *FakeClientInterfaceMockRecorder {
-	return m.recorder
-}
-
-// BrokerName mocks base method
-func (m *FakeClientInterface) BrokerName(arg0 v1beta1.ServiceInstance, arg1 ...services.BrokerNameOption) (string, error) {
-	m.ctrl.T.Helper()
-	varargs := []interface{}{arg0}
-	for _, a := range arg1 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "BrokerName", varargs...)
-	ret0, _ := ret[0].(string)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// BrokerName indicates an expected call of BrokerName
-func (mr *FakeClientInterfaceMockRecorder) BrokerName(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]interface{}{arg0}, arg1...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BrokerName", reflect.TypeOf((*FakeClientInterface)(nil).BrokerName), varargs...)
-}
-
-// DeleteService mocks base method
-func (m *FakeClientInterface) DeleteService(arg0 string, arg1 ...services.DeleteServiceOption) error {
-	m.ctrl.T.Helper()
-	varargs := []interface{}{arg0}
-	for _, a := range arg1 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "DeleteService", varargs...)
-	ret0, _ := ret[0].(error)
-	return ret0
-}
-
-// DeleteService indicates an expected call of DeleteService
-func (mr *FakeClientInterfaceMockRecorder) DeleteService(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]interface{}{arg0}, arg1...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteService", reflect.TypeOf((*FakeClientInterface)(nil).DeleteService), varargs...)
-}
-
-// GetService mocks base method
-func (m *FakeClientInterface) GetService(arg0 string, arg1 ...services.GetServiceOption) (*v1beta1.ServiceInstance, error) {
-	m.ctrl.T.Helper()
-	varargs := []interface{}{arg0}
-	for _, a := range arg1 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "GetService", varargs...)
-	ret0, _ := ret[0].(*v1beta1.ServiceInstance)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// GetService indicates an expected call of GetService
-func (mr *FakeClientInterfaceMockRecorder) GetService(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	varargs := append([]interface{}{arg0}, arg1...)
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetService", reflect.TypeOf((*FakeClientInterface)(nil).GetService), varargs...)
-}
-
-// ListServices mocks base method
-func (m *FakeClientInterface) ListServices(arg0 ...services.ListServicesOption) (*v1beta1.ServiceInstanceList, error) {
-	m.ctrl.T.Helper()
-	varargs := []interface{}{}
-	for _, a := range arg0 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "ListServices", varargs...)
-	ret0, _ := ret[0].(*v1beta1.ServiceInstanceList)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// ListServices indicates an expected call of ListServices
-func (mr *FakeClientInterfaceMockRecorder) ListServices(arg0 ...interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListServices", reflect.TypeOf((*FakeClientInterface)(nil).ListServices), arg0...)
-}
-
-// Marketplace mocks base method
-func (m *FakeClientInterface) Marketplace(arg0 ...services.MarketplaceOption) (*services.KfMarketplace, error) {
-	m.ctrl.T.Helper()
-	varargs := []interface{}{}
-	for _, a := range arg0 {
-		varargs = append(varargs, a)
-	}
-	ret := m.ctrl.Call(m, "Marketplace", varargs...)
-	ret0, _ := ret[0].(*services.KfMarketplace)
-	ret1, _ := ret[1].(error)
-	return ret0, ret1
-}
-
-// Marketplace indicates an expected call of Marketplace
-func (mr *FakeClientInterfaceMockRecorder) Marketplace(arg0 ...interface{}) *gomock.Call {
-	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Marketplace", reflect.TypeOf((*FakeClientInterface)(nil).Marketplace), arg0...)
-}
diff --git a/pkg/kf/services/fake/interfaces.go b/pkg/kf/services/fake/interfaces.go
index b6f04b0..7cee55c 100644
--- a/pkg/kf/services/fake/interfaces.go
+++ b/pkg/kf/services/fake/interfaces.go
@@ -16,9 +16,9 @@ package fake
 
 import "github.com/google/kf/pkg/kf/services"
 
-//go:generate mockgen --package=fake --destination=fake_client_interface.go --copyright_file ../../internal/tools/option-builder/LICENSE_HEADER --mock_names=ClientInterface=FakeClientInterface github.com/google/kf/pkg/kf/services/fake ClientInterface
+//go:generate mockgen --package=fake --destination=fake_client.go --copyright_file ../../internal/tools/option-builder/LICENSE_HEADER --mock_names=Client=FakeClient github.com/google/kf/pkg/kf/services/fake Client
 
-// ClientInterface is implementd by services.Client.
-type ClientInterface interface {
-	services.ClientInterface
+// Client is implementd by services.Client.
+type Client interface {
+	services.Client
 }
diff --git a/pkg/kf/services/options.go b/pkg/kf/services/options.go
deleted file mode 100644
index 7382ff6..0000000
--- a/pkg/kf/services/options.go
+++ /dev/null
@@ -1,338 +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.
-
-// This file was generated with option-builder.go, DO NOT EDIT IT.
-
-package services
-
-type createServiceConfig struct {
-	// Namespace is the Kubernetes namespace to use.
-	Namespace string
-	// Params is service-specific configuration parameters.
-	Params map[string]interface{}
-}
-
-// CreateServiceOption is a single option for configuring a createServiceConfig
-type CreateServiceOption func(*createServiceConfig)
-
-// CreateServiceOptions is a configuration set defining a createServiceConfig
-type CreateServiceOptions []CreateServiceOption
-
-// toConfig applies all the options to a new createServiceConfig and returns it.
-func (opts CreateServiceOptions) toConfig() createServiceConfig {
-	cfg := createServiceConfig{}
-
-	for _, v := range opts {
-		v(&cfg)
-	}
-
-	return cfg
-}
-
-// Extend creates a new CreateServiceOptions with the contents of other overriding
-// the values set in this CreateServiceOptions.
-func (opts CreateServiceOptions) Extend(other CreateServiceOptions) CreateServiceOptions {
-	var out CreateServiceOptions
-	out = append(out, opts...)
-	out = append(out, other...)
-	return out
-}
-
-// Namespace returns the last set value for Namespace or the empty value
-// if not set.
-func (opts CreateServiceOptions) Namespace() string {
-	return opts.toConfig().Namespace
-}
-
-// Params returns the last set value for Params or the empty value
-// if not set.
-func (opts CreateServiceOptions) Params() map[string]interface{} {
-	return opts.toConfig().Params
-}
-
-// WithCreateServiceNamespace creates an Option that sets the Kubernetes namespace to use.
-func WithCreateServiceNamespace(val string) CreateServiceOption {
-	return func(cfg *createServiceConfig) {
-		cfg.Namespace = val
-	}
-}
-
-// WithCreateServiceParams creates an Option that sets service-specific configuration parameters.
-func WithCreateServiceParams(val map[string]interface{}) CreateServiceOption {
-	return func(cfg *createServiceConfig) {
-		cfg.Params = val
-	}
-}
-
-// CreateServiceOptionDefaults gets the default values for CreateService.
-func CreateServiceOptionDefaults() CreateServiceOptions {
-	return CreateServiceOptions{
-		WithCreateServiceNamespace("default"),
-	}
-}
-
-type deleteServiceConfig struct {
-	// Namespace is the Kubernetes namespace to use.
-	Namespace string
-}
-
-// DeleteServiceOption is a single option for configuring a deleteServiceConfig
-type DeleteServiceOption func(*deleteServiceConfig)
-
-// DeleteServiceOptions is a configuration set defining a deleteServiceConfig
-type DeleteServiceOptions []DeleteServiceOption
-
-// toConfig applies all the options to a new deleteServiceConfig and returns it.
-func (opts DeleteServiceOptions) toConfig() deleteServiceConfig {
-	cfg := deleteServiceConfig{}
-
-	for _, v := range opts {
-		v(&cfg)
-	}
-
-	return cfg
-}
-
-// Extend creates a new DeleteServiceOptions with the contents of other overriding
-// the values set in this DeleteServiceOptions.
-func (opts DeleteServiceOptions) Extend(other DeleteServiceOptions) DeleteServiceOptions {
-	var out DeleteServiceOptions
-	out = append(out, opts...)
-	out = append(out, other...)
-	return out
-}
-
-// Namespace returns the last set value for Namespace or the empty value
-// if not set.
-func (opts DeleteServiceOptions) Namespace() string {
-	return opts.toConfig().Namespace
-}
-
-// WithDeleteServiceNamespace creates an Option that sets the Kubernetes namespace to use.
-func WithDeleteServiceNamespace(val string) DeleteServiceOption {
-	return func(cfg *deleteServiceConfig) {
-		cfg.Namespace = val
-	}
-}
-
-// DeleteServiceOptionDefaults gets the default values for DeleteService.
-func DeleteServiceOptionDefaults() DeleteServiceOptions {
-	return DeleteServiceOptions{
-		WithDeleteServiceNamespace("default"),
-	}
-}
-
-type getServiceConfig struct {
-	// Namespace is the Kubernetes namespace to use.
-	Namespace string
-}
-
-// GetServiceOption is a single option for configuring a getServiceConfig
-type GetServiceOption func(*getServiceConfig)
-
-// GetServiceOptions is a configuration set defining a getServiceConfig
-type GetServiceOptions []GetServiceOption
-
-// toConfig applies all the options to a new getServiceConfig and returns it.
-func (opts GetServiceOptions) toConfig() getServiceConfig {
-	cfg := getServiceConfig{}
-
-	for _, v := range opts {
-		v(&cfg)
-	}
-
-	return cfg
-}
-
-// Extend creates a new GetServiceOptions with the contents of other overriding
-// the values set in this GetServiceOptions.
-func (opts GetServiceOptions) Extend(other GetServiceOptions) GetServiceOptions {
-	var out GetServiceOptions
-	out = append(out, opts...)
-	out = append(out, other...)
-	return out
-}
-
-// Namespace returns the last set value for Namespace or the empty value
-// if not set.
-func (opts GetServiceOptions) Namespace() string {
-	return opts.toConfig().Namespace
-}
-
-// WithGetServiceNamespace creates an Option that sets the Kubernetes namespace to use.
-func WithGetServiceNamespace(val string) GetServiceOption {
-	return func(cfg *getServiceConfig) {
-		cfg.Namespace = val
-	}
-}
-
-// GetServiceOptionDefaults gets the default values for GetService.
-func GetServiceOptionDefaults() GetServiceOptions {
-	return GetServiceOptions{
-		WithGetServiceNamespace("default"),
-	}
-}
-
-type listServicesConfig struct {
-	// Namespace is the Kubernetes namespace to use.
-	Namespace string
-}
-
-// ListServicesOption is a single option for configuring a listServicesConfig
-type ListServicesOption func(*listServicesConfig)
-
-// ListServicesOptions is a configuration set defining a listServicesConfig
-type ListServicesOptions []ListServicesOption
-
-// toConfig applies all the options to a new listServicesConfig and returns it.
-func (opts ListServicesOptions) toConfig() listServicesConfig {
-	cfg := listServicesConfig{}
-
-	for _, v := range opts {
-		v(&cfg)
-	}
-
-	return cfg
-}
-
-// Extend creates a new ListServicesOptions with the contents of other overriding
-// the values set in this ListServicesOptions.
-func (opts ListServicesOptions) Extend(other ListServicesOptions) ListServicesOptions {
-	var out ListServicesOptions
-	out = append(out, opts...)
-	out = append(out, other...)
-	return out
-}
-
-// Namespace returns the last set value for Namespace or the empty value
-// if not set.
-func (opts ListServicesOptions) Namespace() string {
-	return opts.toConfig().Namespace
-}
-
-// WithListServicesNamespace creates an Option that sets the Kubernetes namespace to use.
-func WithListServicesNamespace(val string) ListServicesOption {
-	return func(cfg *listServicesConfig) {
-		cfg.Namespace = val
-	}
-}
-
-// ListServicesOptionDefaults gets the default values for ListServices.
-func ListServicesOptionDefaults() ListServicesOptions {
-	return ListServicesOptions{
-		WithListServicesNamespace("default"),
-	}
-}
-
-type marketplaceConfig struct {
-	// Namespace is the Kubernetes namespace to use.
-	Namespace string
-}
-
-// MarketplaceOption is a single option for configuring a marketplaceConfig
-type MarketplaceOption func(*marketplaceConfig)
-
-// MarketplaceOptions is a configuration set defining a marketplaceConfig
-type MarketplaceOptions []MarketplaceOption
-
-// toConfig applies all the options to a new marketplaceConfig and returns it.
-func (opts MarketplaceOptions) toConfig() marketplaceConfig {
-	cfg := marketplaceConfig{}
-
-	for _, v := range opts {
-		v(&cfg)
-	}
-
-	return cfg
-}
-
-// Extend creates a new MarketplaceOptions with the contents of other overriding
-// the values set in this MarketplaceOptions.
-func (opts MarketplaceOptions) Extend(other MarketplaceOptions) MarketplaceOptions {
-	var out MarketplaceOptions
-	out = append(out, opts...)
-	out = append(out, other...)
-	return out
-}
-
-// Namespace returns the last set value for Namespace or the empty value
-// if not set.
-func (opts MarketplaceOptions) Namespace() string {
-	return opts.toConfig().Namespace
-}
-
-// WithMarketplaceNamespace creates an Option that sets the Kubernetes namespace to use.
-func WithMarketplaceNamespace(val string) MarketplaceOption {
-	return func(cfg *marketplaceConfig) {
-		cfg.Namespace = val
-	}
-}
-
-// MarketplaceOptionDefaults gets the default values for Marketplace.
-func MarketplaceOptionDefaults() MarketplaceOptions {
-	return MarketplaceOptions{
-		WithMarketplaceNamespace("default"),
-	}
-}
-
-type brokerNameConfig struct {
-	// Namespace is the Kubernetes namespace to use.
-	Namespace string
-}
-
-// BrokerNameOption is a single option for configuring a brokerNameConfig
-type BrokerNameOption func(*brokerNameConfig)
-
-// BrokerNameOptions is a configuration set defining a brokerNameConfig
-type BrokerNameOptions []BrokerNameOption
-
-// toConfig applies all the options to a new brokerNameConfig and returns it.
-func (opts BrokerNameOptions) toConfig() brokerNameConfig {
-	cfg := brokerNameConfig{}
-
-	for _, v := range opts {
-		v(&cfg)
-	}
-
-	return cfg
-}
-
-// Extend creates a new BrokerNameOptions with the contents of other overriding
-// the values set in this BrokerNameOptions.
-func (opts BrokerNameOptions) Extend(other BrokerNameOptions) BrokerNameOptions {
-	var out BrokerNameOptions
-	out = append(out, opts...)
-	out = append(out, other...)
-	return out
-}
-
-// Namespace returns the last set value for Namespace or the empty value
-// if not set.
-func (opts BrokerNameOptions) Namespace() string {
-	return opts.toConfig().Namespace
-}
-
-// WithBrokerNameNamespace creates an Option that sets the Kubernetes namespace to use.
-func WithBrokerNameNamespace(val string) BrokerNameOption {
-	return func(cfg *brokerNameConfig) {
-		cfg.Namespace = val
-	}
-}
-
-// BrokerNameOptionDefaults gets the default values for BrokerName.
-func BrokerNameOptionDefaults() BrokerNameOptions {
-	return BrokerNameOptions{
-		WithBrokerNameNamespace("default"),
-	}
-}
diff --git a/pkg/kf/services/options.yml b/pkg/kf/services/options.yml
deleted file mode 100644
index 2283228..0000000
--- a/pkg/kf/services/options.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-package: services
-common:
-- name: Namespace
-  type: string
-  description: the Kubernetes namespace to use.
-  default: '"default"'
-configs:
-- name: CreateService
-  options:
-  - name: Params
-    type: 'map[string]interface{}'
-    description: service-specific configuration parameters.
-- name: DeleteService
-- name: GetService
-- name: ListServices
-- name: Marketplace
-- name: BrokerName
diff --git a/pkg/kf/services/zz_generated.client.go b/pkg/kf/services/zz_generated.client.go
new file mode 100644
index 0000000..aa1d699
--- /dev/null
+++ b/pkg/kf/services/zz_generated.client.go
@@ -0,0 +1,320 @@
+// 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.
+
+// This file was generated with functions.go, DO NOT EDIT IT.
+
+package services
+
+// Generator defined imports
+import (
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"strings"
+	"time"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"knative.dev/pkg/kmp"
+)
+
+// User defined imports
+import (
+	cv1beta1 "github.com/google/kf/pkg/client/servicecatalog/clientset/versioned/typed/servicecatalog/v1beta1"
+	v1beta1 "github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
+)
+
+////////////////////////////////////////////////////////////////////////////////
+// Functional Utilities
+////////////////////////////////////////////////////////////////////////////////
+
+const (
+	// Kind contains the kind for the backing Kubernetes API.
+	Kind = "ServiceInstance"
+
+	// APIVersion contains the version for the backing Kubernetes API.
+	APIVersion = "v1beta1"
+)
+
+// Predicate is a boolean function for a v1beta1.ServiceInstance.
+type Predicate func(*v1beta1.ServiceInstance) bool
+
+// Mutator is a function that changes v1beta1.ServiceInstance.
+type Mutator func(*v1beta1.ServiceInstance) error
+
+// DiffWrapper wraps a mutator and prints out the diff between the original object
+// and the one it returns if there's no error.
+func DiffWrapper(w io.Writer, mutator Mutator) Mutator {
+	return func(mutable *v1beta1.ServiceInstance) error {
+		before := mutable.DeepCopy()
+
+		if err := mutator(mutable); err != nil {
+			return err
+		}
+
+		FormatDiff(w, "old", "new", before, mutable)
+
+		return nil
+	}
+}
+
+// FormatDiff creates a diff between two v1beta1.ServiceInstances and writes it to the given
+// writer.
+func FormatDiff(w io.Writer, leftName, rightName string, left, right *v1beta1.ServiceInstance) {
+	diff, err := kmp.SafeDiff(left, right)
+	switch {
+	case err != nil:
+		fmt.Fprintf(w, "couldn't format diff: %s\n", err.Error())
+
+	case diff == "":
+		fmt.Fprintln(w, "No changes")
+
+	default:
+		fmt.Fprintf(w, "Service Diff (-%s +%s):\n", leftName, rightName)
+		// go-cmp randomly chooses to prefix lines with non-breaking spaces or
+		// regular spaces to prevent people from using it as a real diff/patch
+		// tool. We normalize them so our outputs will be consistent.
+		fmt.Fprintln(w, strings.ReplaceAll(diff, " ", " "))
+	}
+}
+
+// List represents a collection of v1beta1.ServiceInstance.
+type List []v1beta1.ServiceInstance
+
+// Filter returns a new list items for which the predicates fails removed.
+func (list List) Filter(filter Predicate) (out List) {
+	for _, v := range list {
+		if filter(&v) {
+			out = append(out, v)
+		}
+	}
+
+	return
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Client
+////////////////////////////////////////////////////////////////////////////////
+
+// Client is the interface for interacting with v1beta1.ServiceInstance types as Service CF style objects.
+type Client interface {
+	Create(namespace string, obj *v1beta1.ServiceInstance, opts ...CreateOption) (*v1beta1.ServiceInstance, error)
+	Update(namespace string, obj *v1beta1.ServiceInstance, opts ...UpdateOption) (*v1beta1.ServiceInstance, error)
+	Transform(namespace string, name string, transformer Mutator) (*v1beta1.ServiceInstance, error)
+	Get(namespace string, name string, opts ...GetOption) (*v1beta1.ServiceInstance, error)
+	Delete(namespace string, name string, opts ...DeleteOption) error
+	List(namespace string, opts ...ListOption) ([]v1beta1.ServiceInstance, error)
+	Upsert(namespace string, newObj *v1beta1.ServiceInstance, merge Merger) (*v1beta1.ServiceInstance, error)
+	WaitFor(ctx context.Context, namespace string, name string, interval time.Duration, condition Predicate) (*v1beta1.ServiceInstance, error)
+	WaitForE(ctx context.Context, namespace string, name string, interval time.Duration, condition ConditionFuncE) (*v1beta1.ServiceInstance, error)
+
+	// ClientExtension can be used by the developer to extend the client.
+	ClientExtension
+}
+
+type coreClient struct {
+	kclient      cv1beta1.ServiceInstancesGetter
+	upsertMutate Mutator
+}
+
+func (core *coreClient) preprocessUpsert(obj *v1beta1.ServiceInstance) error {
+	if core.upsertMutate == nil {
+		return nil
+	}
+
+	return core.upsertMutate(obj)
+}
+
+// Create inserts the given v1beta1.ServiceInstance into the cluster.
+// The value to be inserted will be preprocessed and validated before being sent.
+func (core *coreClient) Create(namespace string, obj *v1beta1.ServiceInstance, opts ...CreateOption) (*v1beta1.ServiceInstance, error) {
+	if err := core.preprocessUpsert(obj); err != nil {
+		return nil, err
+	}
+
+	return core.kclient.ServiceInstances(namespace).Create(obj)
+}
+
+// Update replaces the existing object in the cluster with the new one.
+// The value to be inserted will be preprocessed and validated before being sent.
+func (core *coreClient) Update(namespace string, obj *v1beta1.ServiceInstance, opts ...UpdateOption) (*v1beta1.ServiceInstance, error) {
+	if err := core.preprocessUpsert(obj); err != nil {
+		return nil, err
+	}
+
+	return core.kclient.ServiceInstances(namespace).Update(obj)
+}
+
+// Transform performs a read/modify/write on the object with the given name
+// and returns the updated object. Transform manages the options for the Get and
+// Update calls.
+func (core *coreClient) Transform(namespace string, name string, mutator Mutator) (*v1beta1.ServiceInstance, error) {
+	obj, err := core.Get(namespace, name)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := mutator(obj); err != nil {
+		return nil, err
+	}
+
+	return core.Update(namespace, obj)
+}
+
+// Get retrieves an existing object in the cluster with the given name.
+// The function will return an error if an object is retrieved from the cluster
+// but doesn't pass the membership test of this client.
+func (core *coreClient) Get(namespace string, name string, opts ...GetOption) (*v1beta1.ServiceInstance, error) {
+	res, err := core.kclient.ServiceInstances(namespace).Get(name, metav1.GetOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("couldn't get the Service with the name %q: %v", name, err)
+	}
+
+	return res, nil
+}
+
+// Delete removes an existing object in the cluster.
+// The deleted object is NOT tested for membership before deletion.
+func (core *coreClient) Delete(namespace string, name string, opts ...DeleteOption) error {
+	cfg := DeleteOptionDefaults().Extend(opts).toConfig()
+
+	if err := core.kclient.ServiceInstances(namespace).Delete(name, cfg.ToDeleteOptions()); err != nil {
+		return fmt.Errorf("couldn't delete the Service with the name %q: %v", name, err)
+	}
+
+	return nil
+}
+
+func (cfg deleteConfig) ToDeleteOptions() *metav1.DeleteOptions {
+	resp := metav1.DeleteOptions{}
+
+	if cfg.ForegroundDeletion {
+		propigationPolicy := metav1.DeletePropagationForeground
+		resp.PropagationPolicy = &propigationPolicy
+	}
+
+	return &resp
+}
+
+// List gets objects in the cluster and filters the results based on the
+// internal membership test.
+func (core *coreClient) List(namespace string, opts ...ListOption) ([]v1beta1.ServiceInstance, error) {
+	cfg := ListOptionDefaults().Extend(opts).toConfig()
+
+	res, err := core.kclient.ServiceInstances(namespace).List(cfg.ToListOptions())
+	if err != nil {
+		return nil, fmt.Errorf("couldn't list Services: %v", err)
+	}
+
+	if cfg.filter == nil {
+		return res.Items, nil
+	}
+
+	return List(res.Items).Filter(cfg.filter), nil
+}
+
+func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
+	if cfg.fieldSelector != nil {
+		resp.FieldSelector = metav1.FormatLabelSelector(metav1.SetAsLabelSelector(cfg.fieldSelector))
+	}
+
+	return
+}
+
+// Merger is a type to merge an existing value with a new one.
+type Merger func(newObj, oldObj *v1beta1.ServiceInstance) *v1beta1.ServiceInstance
+
+// Upsert inserts the object into the cluster if it doesn't already exist, or else
+// calls the merge function to merge the existing and new then performs an Update.
+func (core *coreClient) Upsert(namespace string, newObj *v1beta1.ServiceInstance, merge Merger) (*v1beta1.ServiceInstance, error) {
+	// NOTE: the field selector may be ignored by some Kubernetes resources
+	// so we double check down below.
+	existing, err := core.List(namespace, WithListFieldSelector(map[string]string{"metadata.name": newObj.Name}))
+	if err != nil {
+		return nil, err
+	}
+
+	for _, oldObj := range existing {
+		if oldObj.Name == newObj.Name {
+			return core.Update(namespace, merge(newObj, &oldObj))
+		}
+	}
+
+	return core.Create(namespace, newObj)
+}
+
+// WaitFor is a convenience wrapper for WaitForE that fails if the error
+// passed is non-nil. It allows the use of Predicates instead of ConditionFuncE.
+func (core *coreClient) WaitFor(ctx context.Context, namespace string, name string, interval time.Duration, condition Predicate) (*v1beta1.ServiceInstance, error) {
+	return core.WaitForE(ctx, namespace, name, interval, wrapPredicate(condition))
+}
+
+// ConditionFuncE is a callback used by WaitForE. Done should be set to true
+// once the condition succeeds and shouldn't be called anymore. The error
+// will be passed back to the user.
+//
+// This function MAY retrieve a nil instance and an apiErr. It's up to the
+// function to decide how to handle the apiErr.
+type ConditionFuncE func(instance *v1beta1.ServiceInstance, apiErr error) (done bool, err error)
+
+// WaitForE polls for the given object every interval until the condition
+// function becomes done or the timeout expires. The first poll occurs
+// immediately after the function is invoked.
+//
+// The function polls infinitely if no timeout is supplied.
+func (core *coreClient) WaitForE(ctx context.Context, namespace string, name string, interval time.Duration, condition ConditionFuncE) (instance *v1beta1.ServiceInstance, err error) {
+	var done bool
+	tick := time.Tick(interval)
+
+	for {
+		instance, err = core.kclient.ServiceInstances(namespace).Get(name, metav1.GetOptions{})
+		if done, err = condition(instance, err); done {
+			return
+		}
+
+		select {
+		case <-tick:
+			// repeat instance check
+		case <-ctx.Done():
+			return nil, errors.New("waiting for Service timed out")
+		}
+	}
+}
+
+// ConditionDeleted is a ConditionFuncE that succeeds if the error returned by
+// the cluster was a not found error.
+func ConditionDeleted(_ *v1beta1.ServiceInstance, apiErr error) (bool, error) {
+	if apiErr != nil {
+		if apierrors.IsNotFound(apiErr) {
+			apiErr = nil
+		}
+
+		return true, apiErr
+	}
+
+	return false, nil
+}
+
+// wrapPredicate converts a predicate to a ConditionFuncE that fails if the
+// error is not nil
+func wrapPredicate(condition Predicate) ConditionFuncE {
+	return func(obj *v1beta1.ServiceInstance, err error) (bool, error) {
+		if err != nil {
+			return true, err
+		}
+
+		return condition(obj), nil
+	}
+}
diff --git a/pkg/kf/services/zz_generated.clientoptions.go b/pkg/kf/services/zz_generated.clientoptions.go
new file mode 100644
index 0000000..731979b
--- /dev/null
+++ b/pkg/kf/services/zz_generated.clientoptions.go
@@ -0,0 +1,232 @@
+// 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.
+
+// This file was generated with option-builder.go, DO NOT EDIT IT.
+
+package services
+
+type createConfig struct {
+}
+
+// CreateOption is a single option for configuring a createConfig
+type CreateOption func(*createConfig)
+
+// CreateOptions is a configuration set defining a createConfig
+type CreateOptions []CreateOption
+
+// toConfig applies all the options to a new createConfig and returns it.
+func (opts CreateOptions) toConfig() createConfig {
+	cfg := createConfig{}
+
+	for _, v := range opts {
+		v(&cfg)
+	}
+
+	return cfg
+}
+
+// Extend creates a new CreateOptions with the contents of other overriding
+// the values set in this CreateOptions.
+func (opts CreateOptions) Extend(other CreateOptions) CreateOptions {
+	var out CreateOptions
+	out = append(out, opts...)
+	out = append(out, other...)
+	return out
+}
+
+// CreateOptionDefaults gets the default values for Create.
+func CreateOptionDefaults() CreateOptions {
+	return CreateOptions{}
+}
+
+type updateConfig struct {
+}
+
+// UpdateOption is a single option for configuring a updateConfig
+type UpdateOption func(*updateConfig)
+
+// UpdateOptions is a configuration set defining a updateConfig
+type UpdateOptions []UpdateOption
+
+// toConfig applies all the options to a new updateConfig and returns it.
+func (opts UpdateOptions) toConfig() updateConfig {
+	cfg := updateConfig{}
+
+	for _, v := range opts {
+		v(&cfg)
+	}
+
+	return cfg
+}
+
+// Extend creates a new UpdateOptions with the contents of other overriding
+// the values set in this UpdateOptions.
+func (opts UpdateOptions) Extend(other UpdateOptions) UpdateOptions {
+	var out UpdateOptions
+	out = append(out, opts...)
+	out = append(out, other...)
+	return out
+}
+
+// UpdateOptionDefaults gets the default values for Update.
+func UpdateOptionDefaults() UpdateOptions {
+	return UpdateOptions{}
+}
+
+type getConfig struct {
+}
+
+// GetOption is a single option for configuring a getConfig
+type GetOption func(*getConfig)
+
+// GetOptions is a configuration set defining a getConfig
+type GetOptions []GetOption
+
+// toConfig applies all the options to a new getConfig and returns it.
+func (opts GetOptions) toConfig() getConfig {
+	cfg := getConfig{}
+
+	for _, v := range opts {
+		v(&cfg)
+	}
+
+	return cfg
+}
+
+// Extend creates a new GetOptions with the contents of other overriding
+// the values set in this GetOptions.
+func (opts GetOptions) Extend(other GetOptions) GetOptions {
+	var out GetOptions
+	out = append(out, opts...)
+	out = append(out, other...)
+	return out
+}
+
+// GetOptionDefaults gets the default values for Get.
+func GetOptionDefaults() GetOptions {
+	return GetOptions{}
+}
+
+type deleteConfig struct {
+	// ForegroundDeletion is If the resource should be deleted in the foreground.
+	ForegroundDeletion bool
+}
+
+// DeleteOption is a single option for configuring a deleteConfig
+type DeleteOption func(*deleteConfig)
+
+// DeleteOptions is a configuration set defining a deleteConfig
+type DeleteOptions []DeleteOption
+
+// toConfig applies all the options to a new deleteConfig and returns it.
+func (opts DeleteOptions) toConfig() deleteConfig {
+	cfg := deleteConfig{}
+
+	for _, v := range opts {
+		v(&cfg)
+	}
+
+	return cfg
+}
+
+// Extend creates a new DeleteOptions with the contents of other overriding
+// the values set in this DeleteOptions.
+func (opts DeleteOptions) Extend(other DeleteOptions) DeleteOptions {
+	var out DeleteOptions
+	out = append(out, opts...)
+	out = append(out, other...)
+	return out
+}
+
+// ForegroundDeletion returns the last set value for ForegroundDeletion or the empty value
+// if not set.
+func (opts DeleteOptions) ForegroundDeletion() bool {
+	return opts.toConfig().ForegroundDeletion
+}
+
+// WithDeleteForegroundDeletion creates an Option that sets If the resource should be deleted in the foreground.
+func WithDeleteForegroundDeletion(val bool) DeleteOption {
+	return func(cfg *deleteConfig) {
+		cfg.ForegroundDeletion = val
+	}
+}
+
+// DeleteOptionDefaults gets the default values for Delete.
+func DeleteOptionDefaults() DeleteOptions {
+	return DeleteOptions{}
+}
+
+type listConfig struct {
+	// fieldSelector is A selector on the resource's fields.
+	fieldSelector map[string]string
+	// filter is Filter to apply.
+	filter Predicate
+}
+
+// ListOption is a single option for configuring a listConfig
+type ListOption func(*listConfig)
+
+// ListOptions is a configuration set defining a listConfig
+type ListOptions []ListOption
+
+// toConfig applies all the options to a new listConfig and returns it.
+func (opts ListOptions) toConfig() listConfig {
+	cfg := listConfig{}
+
+	for _, v := range opts {
+		v(&cfg)
+	}
+
+	return cfg
+}
+
+// Extend creates a new ListOptions with the contents of other overriding
+// the values set in this ListOptions.
+func (opts ListOptions) Extend(other ListOptions) ListOptions {
+	var out ListOptions
+	out = append(out, opts...)
+	out = append(out, other...)
+	return out
+}
+
+// fieldSelector returns the last set value for fieldSelector or the empty value
+// if not set.
+func (opts ListOptions) fieldSelector() map[string]string {
+	return opts.toConfig().fieldSelector
+}
+
+// filter returns the last set value for filter or the empty value
+// if not set.
+func (opts ListOptions) filter() Predicate {
+	return opts.toConfig().filter
+}
+
+// WithListFieldSelector creates an Option that sets A selector on the resource's fields.
+func WithListFieldSelector(val map[string]string) ListOption {
+	return func(cfg *listConfig) {
+		cfg.fieldSelector = val
+	}
+}
+
+// WithListFilter creates an Option that sets Filter to apply.
+func WithListFilter(val Predicate) ListOption {
+	return func(cfg *listConfig) {
+		cfg.filter = val
+	}
+}
+
+// ListOptionDefaults gets the default values for List.
+func ListOptionDefaults() ListOptions {
+	return ListOptions{}
+}
diff --git a/pkg/kf/sources/fake/fake_client.go b/pkg/kf/sources/fake/fake_client.go
index 75f6ad6..3715976 100644
--- a/pkg/kf/sources/fake/fake_client.go
+++ b/pkg/kf/sources/fake/fake_client.go
@@ -225,6 +225,21 @@ func (mr *FakeClientMockRecorder) WaitFor(arg0, arg1, arg2, arg3, arg4 interface
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitFor", reflect.TypeOf((*FakeClient)(nil).WaitFor), arg0, arg1, arg2, arg3, arg4)
 }
 
+// WaitForDeletion mocks base method
+func (m *FakeClient) WaitForDeletion(arg0 context.Context, arg1, arg2 string, arg3 time.Duration) (*v1alpha1.Source, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WaitForDeletion", arg0, arg1, arg2, arg3)
+	ret0, _ := ret[0].(*v1alpha1.Source)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// WaitForDeletion indicates an expected call of WaitForDeletion
+func (mr *FakeClientMockRecorder) WaitForDeletion(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForDeletion", reflect.TypeOf((*FakeClient)(nil).WaitForDeletion), arg0, arg1, arg2, arg3)
+}
+
 // WaitForE mocks base method
 func (m *FakeClient) WaitForE(arg0 context.Context, arg1, arg2 string, arg3 time.Duration, arg4 sources.ConditionFuncE) (*v1alpha1.Source, error) {
 	m.ctrl.T.Helper()
diff --git a/pkg/kf/sources/zz_generated.client.go b/pkg/kf/sources/zz_generated.client.go
index b0e02f2..ad4939c 100644
--- a/pkg/kf/sources/zz_generated.client.go
+++ b/pkg/kf/sources/zz_generated.client.go
@@ -25,9 +25,10 @@ import (
 	"strings"
 	"time"
 
+	"knative.dev/pkg/kmp"
+
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"knative.dev/pkg/kmp"
 )
 
 // User defined imports
@@ -120,6 +121,9 @@ type Client interface {
 	WaitFor(ctx context.Context, namespace string, name string, interval time.Duration, condition Predicate) (*v1alpha1.Source, error)
 	WaitForE(ctx context.Context, namespace string, name string, interval time.Duration, condition ConditionFuncE) (*v1alpha1.Source, error)
 
+	// Utility functions
+	WaitForDeletion(ctx context.Context, namespace string, name string, interval time.Duration) (*v1alpha1.Source, error)
+
 	// ClientExtension can be used by the developer to extend the client.
 	ClientExtension
 }
@@ -318,3 +322,8 @@ func wrapPredicate(condition Predicate) ConditionFuncE {
 		return condition(obj), nil
 	}
 }
+
+// WaitForDeletion is a utility function that combines WaitForE with ConditionDeleted.
+func (core *coreClient) WaitForDeletion(ctx context.Context, namespace string, name string, interval time.Duration) (instance *v1alpha1.Source, err error) {
+	return core.WaitForE(ctx, namespace, name, interval, ConditionDeleted)
+}
diff --git a/pkg/kf/spaces/fake/fake_client.go b/pkg/kf/spaces/fake/fake_client.go
index fe33c7d..c4af8d6 100644
--- a/pkg/kf/spaces/fake/fake_client.go
+++ b/pkg/kf/spaces/fake/fake_client.go
@@ -194,6 +194,21 @@ func (mr *FakeClientMockRecorder) WaitFor(arg0, arg1, arg2, arg3 interface{}) *g
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitFor", reflect.TypeOf((*FakeClient)(nil).WaitFor), arg0, arg1, arg2, arg3)
 }
 
+// WaitForDeletion mocks base method
+func (m *FakeClient) WaitForDeletion(arg0 context.Context, arg1 string, arg2 time.Duration) (*v1alpha1.Space, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "WaitForDeletion", arg0, arg1, arg2)
+	ret0, _ := ret[0].(*v1alpha1.Space)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// WaitForDeletion indicates an expected call of WaitForDeletion
+func (mr *FakeClientMockRecorder) WaitForDeletion(arg0, arg1, arg2 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitForDeletion", reflect.TypeOf((*FakeClient)(nil).WaitForDeletion), arg0, arg1, arg2)
+}
+
 // WaitForE mocks base method
 func (m *FakeClient) WaitForE(arg0 context.Context, arg1 string, arg2 time.Duration, arg3 spaces.ConditionFuncE) (*v1alpha1.Space, error) {
 	m.ctrl.T.Helper()
diff --git a/pkg/kf/spaces/zz_generated.client.go b/pkg/kf/spaces/zz_generated.client.go
index e5532b4..3cd73a0 100644
--- a/pkg/kf/spaces/zz_generated.client.go
+++ b/pkg/kf/spaces/zz_generated.client.go
@@ -25,9 +25,10 @@ import (
 	"strings"
 	"time"
 
+	"knative.dev/pkg/kmp"
+
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"knative.dev/pkg/kmp"
 )
 
 // User defined imports
@@ -120,6 +121,9 @@ type Client interface {
 	WaitFor(ctx context.Context, name string, interval time.Duration, condition Predicate) (*v1alpha1.Space, error)
 	WaitForE(ctx context.Context, name string, interval time.Duration, condition ConditionFuncE) (*v1alpha1.Space, error)
 
+	// Utility functions
+	WaitForDeletion(ctx context.Context, name string, interval time.Duration) (*v1alpha1.Space, error)
+
 	// ClientExtension can be used by the developer to extend the client.
 	ClientExtension
 }
@@ -318,3 +322,8 @@ func wrapPredicate(condition Predicate) ConditionFuncE {
 		return condition(obj), nil
 	}
 }
+
+// WaitForDeletion is a utility function that combines WaitForE with ConditionDeleted.
+func (core *coreClient) WaitForDeletion(ctx context.Context, name string, interval time.Duration) (instance *v1alpha1.Space, err error) {
+	return core.WaitForE(ctx, name, interval, ConditionDeleted)
+}
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/client_test.go b/pkg/kf/apps/client_test.go
deleted file mode 100644
index be0e859..0000000
--- a/pkg/kf/apps/client_test.go
+++ /dev/null
@@ -1,107 +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 apps
-
-import (
-	"errors"
-	"testing"
-
-	v1alpha1 "github.com/google/kf/pkg/apis/kf/v1alpha1"
-	"github.com/google/kf/pkg/kf/testutil"
-	duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
-)
-
-func TestConditionServiceBindingsReady(t *testing.T) {
-	cases := map[string]struct {
-		app    v1alpha1.App
-		apiErr error
-
-		expectDone bool
-		expectErr  error
-	}{
-		"api error": {
-			apiErr:     errors.New("api-error"),
-			expectErr:  errors.New("api-error"),
-			expectDone: true,
-		},
-		"mismatch generation": {
-			app: v1alpha1.App{
-				Status: v1alpha1.AppStatus{
-					Status: duckv1beta1.Status{
-						ObservedGeneration: 3,
-					},
-				},
-			},
-			expectDone: false,
-			expectErr:  nil,
-		},
-		"missing status": {
-			app: v1alpha1.App{
-				Status: v1alpha1.AppStatus{
-					Status: duckv1beta1.Status{
-						Conditions: duckv1beta1.Conditions{},
-					},
-				},
-			},
-			expectDone: false,
-		},
-		"unknown status causes retry": {
-			app: v1alpha1.App{
-				Status: v1alpha1.AppStatus{
-					Status: duckv1beta1.Status{
-						Conditions: duckv1beta1.Conditions{
-							{Type: v1alpha1.AppConditionServiceBindingsReady, Status: "Unknown"},
-						},
-					},
-				},
-			},
-			expectDone: false,
-		},
-		"good status completes without error": {
-			app: v1alpha1.App{
-				Status: v1alpha1.AppStatus{
-					Status: duckv1beta1.Status{
-						Conditions: duckv1beta1.Conditions{
-							{Type: v1alpha1.AppConditionServiceBindingsReady, Status: "True"},
-						},
-					},
-				},
-			},
-			expectDone: true,
-		},
-		"bad status completes with error": {
-			app: v1alpha1.App{
-				Status: v1alpha1.AppStatus{
-					Status: duckv1beta1.Status{
-						Conditions: duckv1beta1.Conditions{
-							{Type: v1alpha1.AppConditionServiceBindingsReady, Status: "False", Message: "ShortMessage", Reason: "longer description"},
-						},
-					},
-				},
-			},
-			expectDone: true,
-			expectErr:  errors.New("checking ServiceBindingsReady failed, status: False message: ShortMessage reason: longer description"),
-		},
-	}
-
-	for tn, tc := range cases {
-		t.Run(tn, func(t *testing.T) {
-			actualDone, actualErr := ConditionServiceBindingsReady(&tc.app, tc.apiErr)
-
-			testutil.AssertErrorsEqual(t, tc.expectErr, actualErr)
-			testutil.AssertEqual(t, "done", tc.expectDone, actualDone)
-		})
-	}
-}
diff --git a/pkg/kf/commands/services/delete-service_test.go b/pkg/kf/commands/services/delete-service_test.go
index 65f2ed1..f7df398 100644
--- a/pkg/kf/commands/services/delete-service_test.go
+++ b/pkg/kf/commands/services/delete-service_test.go
@@ -18,12 +18,9 @@ import (
 	"errors"
 	"testing"
 
-	"github.com/golang/mock/gomock"
 	servicescmd "github.com/google/kf/pkg/kf/commands/services"
 	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"
 )
 
 func TestNewDeleteServiceCommand(t *testing.T) {
@@ -35,10 +32,8 @@ func TestNewDeleteServiceCommand(t *testing.T) {
 		"command params get passed correctly": {
 			Args:      []string{"mydb"},
 			Namespace: "custom-ns",
-			Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-				f.EXPECT().DeleteService("mydb", gomock.Any()).Do(func(name string, opts ...services.DeleteServiceOption) {
-					testutil.AssertEqual(t, "namespace", "custom-ns", services.DeleteServiceOptions(opts).Namespace())
-				}).Return(nil)
+			Setup: func(t *testing.T, f *fake.FakeClient) {
+				f.EXPECT().Delete("custom-ns", "mydb").Return(nil)
 			},
 		},
 		"empty namespace": {
@@ -48,8 +43,8 @@ func TestNewDeleteServiceCommand(t *testing.T) {
 		"bad server call": {
 			Args:      []string{"mydb"},
 			Namespace: "custom-ns",
-			Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-				f.EXPECT().DeleteService("mydb", gomock.Any()).Return(errors.New("server-call-error"))
+			Setup: func(t *testing.T, f *fake.FakeClient) {
+				f.EXPECT().Delete("custom-ns", "mydb").Return(errors.New("server-call-error"))
 			},
 			ExpectedErr: errors.New("server-call-error"),
 		},
diff --git a/pkg/kf/commands/services/helpers_test.go b/pkg/kf/commands/services/helpers_test.go
index 5bb150b..7982eef 100644
--- a/pkg/kf/commands/services/helpers_test.go
+++ b/pkg/kf/commands/services/helpers_test.go
@@ -29,7 +29,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-type commandFactory func(p *config.KfParams, client services.ClientInterface) *cobra.Command
+type commandFactory func(p *config.KfParams, client services.Client) *cobra.Command
 
 func dummyServerInstance(instanceName string) *v1beta1.ServiceInstance {
 	instance := v1beta1.ServiceInstance{}
@@ -47,7 +47,7 @@ func dummyServerInstance(instanceName string) *v1beta1.ServiceInstance {
 
 type serviceTest struct {
 	Args      []string
-	Setup     func(t *testing.T, f *fake.FakeClientInterface)
+	Setup     func(t *testing.T, f *fake.FakeClient)
 	Namespace string
 
 	ExpectedErr     error
@@ -55,10 +55,11 @@ type serviceTest struct {
 }
 
 func runTest(t *testing.T, tc serviceTest, newCommand commandFactory) {
+	t.Helper()
 	ctrl := gomock.NewController(t)
 	defer ctrl.Finish()
 
-	client := fake.NewFakeClientInterface(ctrl)
+	client := fake.NewFakeClient(ctrl)
 	if tc.Setup != nil {
 		tc.Setup(t, client)
 	}
diff --git a/pkg/kf/commands/services/marketplace_test.go b/pkg/kf/commands/services/marketplace_test.go
index f26b91c..2f3afa6 100644
--- a/pkg/kf/commands/services/marketplace_test.go
+++ b/pkg/kf/commands/services/marketplace_test.go
@@ -18,18 +18,28 @@ import (
 	"errors"
 	"testing"
 
+	"bytes"
+
 	"github.com/golang/mock/gomock"
+	"github.com/google/kf/pkg/kf/commands/config"
 	servicescmd "github.com/google/kf/pkg/kf/commands/services"
 	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/marketplace"
+	"github.com/google/kf/pkg/kf/marketplace/fake"
 	"github.com/google/kf/pkg/kf/testutil"
 	"github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
 	servicecatalog "github.com/poy/service-catalog/pkg/svcat/service-catalog"
 )
 
 func TestNewMarketplaceCommand(t *testing.T) {
-	cases := map[string]serviceTest{
+	cases := map[string]struct {
+		Args      []string
+		Setup     func(t *testing.T, f *fake.FakeClientInterface)
+		Namespace string
+
+		ExpectedErr     error
+		ExpectedStrings []string
+	}{
 		"too many params": {
 			Args:        []string{"mydb"},
 			ExpectedErr: errors.New("accepts 0 arg(s), received 1"),
@@ -38,9 +48,7 @@ func TestNewMarketplaceCommand(t *testing.T) {
 			Args:      []string{},
 			Namespace: "custom-ns",
 			Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-				f.EXPECT().Marketplace(gomock.Any()).Do(func(opts ...services.MarketplaceOption) {
-					testutil.AssertEqual(t, "namespace", "custom-ns", services.MarketplaceOptions(opts).Namespace())
-				}).Return(&services.KfMarketplace{}, nil)
+				f.EXPECT().Marketplace("custom-ns").Return(&marketplace.KfMarketplace{}, nil)
 			},
 		},
 		"empty namespace": {
@@ -57,7 +65,7 @@ func TestNewMarketplaceCommand(t *testing.T) {
 				fakeService.Spec.Description = "fake-description"
 				fakeService.Spec.ClusterServiceBrokerName = "fake-broker"
 
-				f.EXPECT().Marketplace(gomock.Any()).Return(&services.KfMarketplace{
+				f.EXPECT().Marketplace(gomock.Any()).Return(&marketplace.KfMarketplace{
 					Services: []servicecatalog.Class{fakeService},
 					Plans:    []servicecatalog.Plan{},
 				}, nil)
@@ -79,7 +87,7 @@ func TestNewMarketplaceCommand(t *testing.T) {
 				fakePlan.Spec.ClusterServiceClassRef.Name = fakeService.Name
 				fakePlan.Spec.CommonServicePlanSpec.ExternalName = fakePlan.Name
 
-				f.EXPECT().Marketplace(gomock.Any()).Return(&services.KfMarketplace{
+				f.EXPECT().Marketplace(gomock.Any()).Return(&marketplace.KfMarketplace{
 					Services: []servicecatalog.Class{fakeService},
 					Plans:    []servicecatalog.Plan{fakePlan},
 				}, nil)
@@ -90,7 +98,7 @@ func TestNewMarketplaceCommand(t *testing.T) {
 			Args:      []string{},
 			Namespace: "custom-ns",
 			Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-				f.EXPECT().Marketplace(gomock.Any()).Return(&services.KfMarketplace{}, nil)
+				f.EXPECT().Marketplace(gomock.Any()).Return(&marketplace.KfMarketplace{}, nil)
 			},
 			ExpectedStrings: []string{},
 		},
@@ -106,7 +114,28 @@ func TestNewMarketplaceCommand(t *testing.T) {
 
 	for tn, tc := range cases {
 		t.Run(tn, func(t *testing.T) {
-			runTest(t, tc, servicescmd.NewMarketplaceCommand)
+			ctrl := gomock.NewController(t)
+			defer ctrl.Finish()
+
+			client := fake.NewFakeClientInterface(ctrl)
+			if tc.Setup != nil {
+				tc.Setup(t, client)
+			}
+
+			buf := new(bytes.Buffer)
+			p := &config.KfParams{
+				Namespace: tc.Namespace,
+			}
+
+			cmd := servicescmd.NewMarketplaceCommand(p, client)
+			cmd.SetOutput(buf)
+			cmd.SetArgs(tc.Args)
+			_, actualErr := cmd.ExecuteC()
+			if tc.ExpectedErr != nil || actualErr != nil {
+				testutil.AssertErrorsEqual(t, tc.ExpectedErr, actualErr)
+			}
+
+			testutil.AssertContainsAll(t, buf.String(), tc.ExpectedStrings)
 		})
 	}
 }
diff --git a/pkg/kf/commands/services/service_test.go b/pkg/kf/commands/services/service_test.go
index 0019ede..8e87cfb 100644
--- a/pkg/kf/commands/services/service_test.go
+++ b/pkg/kf/commands/services/service_test.go
@@ -21,9 +21,7 @@ import (
 	"github.com/golang/mock/gomock"
 	servicescmd "github.com/google/kf/pkg/kf/commands/services"
 	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"
 )
 
 func TestNewGetServiceCommand(t *testing.T) {
@@ -35,10 +33,8 @@ func TestNewGetServiceCommand(t *testing.T) {
 		"command params get passed correctly": {
 			Args:      []string{"mydb"},
 			Namespace: "custom-ns",
-			Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-				f.EXPECT().GetService("mydb", gomock.Any()).Do(func(name string, opts ...services.GetServiceOption) {
-					testutil.AssertEqual(t, "namespace", "custom-ns", services.GetServiceOptions(opts).Namespace())
-				}).Return(dummyServerInstance("mydb-instance1"), nil)
+			Setup: func(t *testing.T, f *fake.FakeClient) {
+				f.EXPECT().Get("custom-ns", "mydb").Return(dummyServerInstance("mydb-instance1"), nil)
 			},
 		},
 		"empty namespace": {
@@ -48,24 +44,24 @@ func TestNewGetServiceCommand(t *testing.T) {
 		"command output outputs instance info": {
 			Args:      []string{"mydb"},
 			Namespace: "custom-ns",
-			Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-				f.EXPECT().GetService("mydb", gomock.Any()).Return(dummyServerInstance("mydb-instance1"), nil)
+			Setup: func(t *testing.T, f *fake.FakeClient) {
+				f.EXPECT().Get(gomock.Any(), "mydb").Return(dummyServerInstance("mydb-instance1"), nil)
 			},
 			ExpectedStrings: []string{"mydb-instance1"},
 		},
 		"service not found": {
 			Args:      []string{"some-missing-service"},
 			Namespace: "custom-ns",
-			Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-				f.EXPECT().GetService("some-missing-service", gomock.Any()).Return(nil, nil)
+			Setup: func(t *testing.T, f *fake.FakeClient) {
+				f.EXPECT().Get(gomock.Any(), "some-missing-service").Return(nil, nil)
 			},
 			ExpectedStrings: []string{"<empty>"},
 		},
 		"bad server call": {
 			Args:      []string{"mydb"},
 			Namespace: "custom-ns",
-			Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-				f.EXPECT().GetService("mydb", gomock.Any()).Return(nil, errors.New("server-call-error"))
+			Setup: func(t *testing.T, f *fake.FakeClient) {
+				f.EXPECT().Get(gomock.Any(), "mydb").Return(nil, errors.New("server-call-error"))
 			},
 			ExpectedErr: errors.New("server-call-error"),
 		},
diff --git a/pkg/kf/commands/services/services_test.go b/pkg/kf/commands/services/services_test.go
index 052e0e6..74f1257 100644
--- a/pkg/kf/commands/services/services_test.go
+++ b/pkg/kf/commands/services/services_test.go
@@ -24,9 +24,9 @@ import (
 	"github.com/google/kf/pkg/kf/commands/config"
 	servicescmd "github.com/google/kf/pkg/kf/commands/services"
 	utils "github.com/google/kf/pkg/kf/internal/utils/cli"
+	fakemarketplace "github.com/google/kf/pkg/kf/marketplace/fake"
 	"github.com/google/kf/pkg/kf/services"
 	"github.com/google/kf/pkg/kf/services/fake"
-	"github.com/google/kf/pkg/kf/testutil"
 	"github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
 	"github.com/spf13/cobra"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -35,7 +35,8 @@ import (
 func TestNewServicesCommand(t *testing.T) {
 	cases := map[string]struct {
 		serviceTest
-		AppSetup func(t *testing.T, f *fakeapps.FakeClient)
+		AppSetup         func(t *testing.T, f *fakeapps.FakeClient)
+		MarketplaceSetup func(t *testing.T, f *fakemarketplace.FakeClientInterface)
 	}{
 		"too many params": {
 			serviceTest: serviceTest{
@@ -46,14 +47,8 @@ func TestNewServicesCommand(t *testing.T) {
 		"custom namespace": {
 			serviceTest: serviceTest{
 				Namespace: "test-ns",
-				Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-					f.EXPECT().ListServices(gomock.Any()).
-						DoAndReturn(func(opts ...services.ListServicesOption) (*v1beta1.ServiceInstanceList, error) {
-							options := services.ListServicesOptions(opts)
-							testutil.AssertEqual(t, "namespace", "test-ns", options.Namespace())
-
-							return &v1beta1.ServiceInstanceList{}, nil
-						})
+				Setup: func(t *testing.T, f *fake.FakeClient) {
+					f.EXPECT().List("test-ns").Return([]v1beta1.ServiceInstance{}, nil)
 				},
 			},
 		},
@@ -65,9 +60,8 @@ func TestNewServicesCommand(t *testing.T) {
 		"empty result": {
 			serviceTest: serviceTest{
 				Namespace: "test-ns",
-				Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-					emptyList := &v1beta1.ServiceInstanceList{Items: []v1beta1.ServiceInstance{}}
-					f.EXPECT().ListServices(gomock.Any()).Return(emptyList, nil)
+				Setup: func(t *testing.T, f *fake.FakeClient) {
+					f.EXPECT().List(gomock.Any()).Return([]v1beta1.ServiceInstance{}, nil)
 				},
 				ExpectedErr: nil, // explicitly expecting no failure with zero length list
 			},
@@ -78,28 +72,29 @@ func TestNewServicesCommand(t *testing.T) {
 			},
 			serviceTest: serviceTest{
 				Namespace: "test-ns",
-				Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-					serviceList := &v1beta1.ServiceInstanceList{Items: []v1beta1.ServiceInstance{}}
-					f.EXPECT().ListServices(gomock.Any()).Return(serviceList, nil)
+				Setup: func(t *testing.T, f *fake.FakeClient) {
+					f.EXPECT().List(gomock.Any()).Return([]v1beta1.ServiceInstance{}, nil)
 				},
 				ExpectedErr: errors.New("some-error"),
 			},
 		},
 		"fetching broker name fails": {
+			MarketplaceSetup: func(t *testing.T, f *fakemarketplace.FakeClientInterface) {
+				f.EXPECT().BrokerName(gomock.Any()).Return("", errors.New("fetch-broker-name-error")).Times(2)
+			},
 			serviceTest: serviceTest{
 				Namespace: "test-ns",
-				Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-					serviceList := &v1beta1.ServiceInstanceList{Items: []v1beta1.ServiceInstance{
+				Setup: func(t *testing.T, f *fake.FakeClient) {
+					serviceList := []v1beta1.ServiceInstance{
 						*dummyServerInstance("service-1"),
 						*dummyServerInstance("service-2"),
-					}}
-					f.EXPECT().ListServices(gomock.Any()).Return(serviceList, nil)
-					f.EXPECT().BrokerName(gomock.Any()).Return("", errors.New("some-error")).Times(2)
+					}
+					f.EXPECT().List(gomock.Any()).Return(serviceList, nil)
 				},
-				ExpectedErr: errors.New("some-error"),
+				ExpectedErr: errors.New("fetch-broker-name-error"),
 				ExpectedStrings: []string{
 					"service-1", "service-2", // service instances still displayed with error msg
-					"some-error",
+					"fetch-broker-name-error",
 				},
 			},
 		},
@@ -112,18 +107,17 @@ func TestNewServicesCommand(t *testing.T) {
 			},
 			serviceTest: serviceTest{
 				Namespace: "test-ns",
-				Setup: func(t *testing.T, f *fake.FakeClientInterface) {
+				Setup: func(t *testing.T, f *fake.FakeClient) {
 					// We'll take the conditions off this so it has to show an
 					// unknown state
 					service1 := *dummyServerInstance("service-1")
 					service1.Status.Conditions = nil
 
-					serviceList := &v1beta1.ServiceInstanceList{Items: []v1beta1.ServiceInstance{
+					serviceList := []v1beta1.ServiceInstance{
 						service1,
 						*dummyServerInstance("service-2"),
-					}}
-					f.EXPECT().ListServices(gomock.Any()).Return(serviceList, nil)
-					f.EXPECT().BrokerName(gomock.Any()).Return("some-broker", nil).Times(2)
+					}
+					f.EXPECT().List(gomock.Any()).Return(serviceList, nil)
 				},
 				ExpectedStrings: []string{
 					"service-1", "service-2", // Binding Names
@@ -137,8 +131,8 @@ func TestNewServicesCommand(t *testing.T) {
 			serviceTest: serviceTest{
 				Namespace:   "test-ns",
 				ExpectedErr: errors.New("server-call-error"),
-				Setup: func(t *testing.T, f *fake.FakeClientInterface) {
-					f.EXPECT().ListServices(gomock.Any()).Return(nil, errors.New("server-call-error"))
+				Setup: func(t *testing.T, f *fake.FakeClient) {
+					f.EXPECT().List(gomock.Any()).Return(nil, errors.New("server-call-error"))
 				},
 			},
 		},
@@ -154,8 +148,16 @@ func TestNewServicesCommand(t *testing.T) {
 				appClient.EXPECT().List(gomock.Any())
 			}
 
-			runTest(t, tc.serviceTest, func(p *config.KfParams, client services.ClientInterface) *cobra.Command {
-				return servicescmd.NewListServicesCommand(p, client, appClient)
+			marketplaceClient := fakemarketplace.NewFakeClientInterface(gomock.NewController(t))
+			if tc.MarketplaceSetup != nil {
+				tc.MarketplaceSetup(t, marketplaceClient)
+			} else {
+				// Give default empty broker response
+				marketplaceClient.EXPECT().BrokerName(gomock.Any()).Return("some-broker", nil).AnyTimes()
+			}
+
+			runTest(t, tc.serviceTest, func(p *config.KfParams, client services.Client) *cobra.Command {
+				return servicescmd.NewListServicesCommand(p, client, appClient, marketplaceClient)
 			})
 		})
 	}
diff --git a/pkg/kf/internal/tools/clientgen/gentest/client.go b/pkg/kf/internal/tools/clientgen/gentest/client.go
index 140f75f..ba5cb94 100644
--- a/pkg/kf/internal/tools/clientgen/gentest/client.go
+++ b/pkg/kf/internal/tools/clientgen/gentest/client.go
@@ -26,7 +26,7 @@ type ClientExtension interface {
 
 // NewExampleClient creates an example client with a mutator and membership
 // validator that filter based on a label.
-func NewExampleClient(mockK8s v1.SecretsGetter) Client {
+func NewExampleClient(mockK8s v1.PodsGetter) Client {
 	return &coreClient{
 		kclient:      mockK8s,
 		upsertMutate: LabelSetMutator(map[string]string{"is-a": "OperatorConfig"}),
@@ -34,7 +34,7 @@ func NewExampleClient(mockK8s v1.SecretsGetter) Client {
 }
 
 func LabelSetMutator(labels map[string]string) Mutator {
-	return func(obj *corev1.Secret) error {
+	return func(obj *corev1.Pod) error {
 		if obj.Labels == nil {
 			obj.Labels = make(map[string]string)
 		}
diff --git a/pkg/kf/internal/tools/clientgen/gentest/client.yml b/pkg/kf/internal/tools/clientgen/gentest/client.yml
index 8d296a5..2e54e8a 100644
--- a/pkg/kf/internal/tools/clientgen/gentest/client.yml
+++ b/pkg/kf/internal/tools/clientgen/gentest/client.yml
@@ -3,10 +3,18 @@
 package: gentest
 imports: {"k8s.io/api/core/v1":"v1", "k8s.io/client-go/kubernetes/typed/core/v1": "cv1"}
 kubernetes:
-  kind: "Secret"
+  kind: "Pod"
   version: "v1"
   namespaced: true
-type: "v1.Secret"
-clientType: "cv1.SecretsGetter"
+  # pod doesn't have an observedgeneration, but we want to exercise the code
+  observedGenerationFieldPath: Generation
+  conditionsFieldPath: Status.Conditions
+  conditions:
+  - name: Ready
+    value: Ready
+  - name: Initialized
+    ref: v1.PodInitialized
+type: "v1.Pod"
+clientType: "cv1.PodsGetter"
 cf:
   name: "OperatorConfig"
diff --git a/pkg/kf/internal/tools/clientgen/gentest/client_test.go b/pkg/kf/internal/tools/clientgen/gentest/client_test.go
index 39a22a6..090bbe2 100644
--- a/pkg/kf/internal/tools/clientgen/gentest/client_test.go
+++ b/pkg/kf/internal/tools/clientgen/gentest/client_test.go
@@ -34,15 +34,15 @@ import (
 )
 
 func ExampleList_Filter() {
-	first := v1.Secret{}
+	first := v1.Pod{}
 	first.Name = "ok"
 
-	second := v1.Secret{}
+	second := v1.Pod{}
 	second.Name = "name-too-long-to-pass"
 
 	list := List{first, second}
 
-	filtered := list.Filter(func(s *v1.Secret) bool {
+	filtered := list.Filter(func(s *v1.Pod) bool {
 		return len(s.Name) < 8
 	})
 
@@ -56,7 +56,7 @@ func ExampleList_Filter() {
 }
 
 func ExampleLabelSetMutator() {
-	out := &v1.Secret{}
+	out := &v1.Pod{}
 	managedAdder := LabelSetMutator(map[string]string{"managed-by": "kf"})
 
 	managedAdder(out)
@@ -70,33 +70,33 @@ func TestClient_invariant(t *testing.T) {
 	// write operations.
 	mockK8s := testclient.NewSimpleClientset().CoreV1()
 
-	secretsClient := NewExampleClient(mockK8s)
+	client := NewExampleClient(mockK8s)
 
 	t.Run("create", func(t *testing.T) {
-		good := &v1.Secret{}
+		good := &v1.Pod{}
 		good.Name = "created-through-client"
 
-		if _, err := secretsClient.Create("default", good); err != nil {
+		if _, err := client.Create("default", good); err != nil {
 			t.Fatal(err)
 		}
 	})
 
 	t.Run("list", func(t *testing.T) {
-		out, err := secretsClient.List("default")
+		out, err := client.List("default")
 		testutil.AssertNil(t, "list err", err)
 
-		testutil.AssertEqual(t, "secret count", 1, len(out))
+		testutil.AssertEqual(t, "object count", 1, len(out))
 	})
 
 	t.Run("transform", func(t *testing.T) {
-		transformResult, err := secretsClient.Transform("default", "created-through-client", func(s *v1.Secret) error {
+		transformResult, err := client.Transform("default", "created-through-client", func(s *v1.Pod) error {
 			s.Labels["mutated"] = "true"
 
 			return nil
 		})
 		testutil.AssertNil(t, "transform err", err)
 
-		modified, err := secretsClient.Get("default", "created-through-client")
+		modified, err := client.Get("default", "created-through-client")
 		testutil.AssertNil(t, "get err", err)
 
 		testutil.AssertEqual(t, "mutated label", "true", modified.Labels["mutated"])
@@ -112,22 +112,22 @@ func TestClient_Delete(t *testing.T) {
 		Name      string
 		Options   []DeleteOption
 		ExpectErr error
-		Setup     func(mockK8s cv1.SecretsGetter)
+		Setup     func(mockK8s cv1.PodsGetter)
 	}{
-		"secret does not exist": {
+		"object does not exist": {
 			Namespace: "default",
-			Name:      "some-secret",
+			Name:      "some-object",
 			Options:   []DeleteOption{},
-			ExpectErr: errors.New(`couldn't delete the OperatorConfig with the name "some-secret": secrets "some-secret" not found`),
+			ExpectErr: errors.New(`couldn't delete the OperatorConfig with the name "some-object": pods "some-object" not found`),
 		},
-		"secret exists": {
+		"object exists": {
 			Namespace: "my-namespace",
-			Name:      "some-secret",
+			Name:      "some-object",
 			Options:   []DeleteOption{},
-			Setup: func(mockK8s cv1.SecretsGetter) {
-				secret := &v1.Secret{}
-				secret.Name = "some-secret"
-				mockK8s.Secrets("my-namespace").Create(secret)
+			Setup: func(mockK8s cv1.PodsGetter) {
+				obj := &v1.Pod{}
+				obj.Name = "some-object"
+				mockK8s.Pods("my-namespace").Create(obj)
 			},
 		},
 	}
@@ -140,25 +140,25 @@ func TestClient_Delete(t *testing.T) {
 				tc.Setup(mockK8s)
 			}
 
-			secretsClient := coreClient{
+			client := coreClient{
 				kclient: mockK8s,
 			}
 
-			actualErr := secretsClient.Delete(tc.Namespace, tc.Name, tc.Options...)
+			actualErr := client.Delete(tc.Namespace, tc.Name, tc.Options...)
 			if tc.ExpectErr != nil || actualErr != nil {
 				testutil.AssertErrorsEqual(t, tc.ExpectErr, actualErr)
 
 				return
 			}
 
-			secrets, err := mockK8s.Secrets(tc.Namespace).List(metav1.ListOptions{})
+			objects, err := mockK8s.Pods(tc.Namespace).List(metav1.ListOptions{})
 			if err != nil {
 				t.Fatal(err)
 			}
 
-			for _, s := range secrets.Items {
+			for _, s := range objects.Items {
 				if s.Name == tc.Name {
-					t.Fatal("The secret wasn't deleted")
+					t.Fatal("The object wasn't deleted")
 				}
 			}
 		})
@@ -166,55 +166,55 @@ func TestClient_Delete(t *testing.T) {
 }
 
 func TestClient_Upsert(t *testing.T) {
-	fakeSecret := func(name string, value string) *v1.Secret {
-		s := &v1.Secret{}
+	fakePod := func(name string, hostname string) *v1.Pod {
+		s := &v1.Pod{}
 		s.Name = name
-		s.StringData = map[string]string{"value": value}
-
+		s.Spec.Hostname = hostname
 		return s
 	}
+
 	cases := map[string]struct {
 		Namespace   string
-		PreExisting []*v1.Secret
-		ToUpsert    *v1.Secret
+		PreExisting []*v1.Pod
+		ToUpsert    *v1.Pod
 		Merger      Merger
 		ExpectErr   error
-		Validate    func(t *testing.T, mockK8s cv1.SecretsGetter)
+		Validate    func(t *testing.T, mockK8s cv1.PodsGetter)
 	}{
 		"inserts if not found": {
 			Namespace: "default",
-			ToUpsert:  fakeSecret("foo", "new"),
+			ToUpsert:  fakePod("foo", "new"),
 			Merger:    nil, // should not be called
-			Validate: func(t *testing.T, mockK8s cv1.SecretsGetter) {
-				secret, err := mockK8s.Secrets("default").Get("foo", metav1.GetOptions{})
-				testutil.AssertNil(t, "secrets err", err)
-				testutil.AssertEqual(t, "value", "new", secret.StringData["value"])
+			Validate: func(t *testing.T, mockK8s cv1.PodsGetter) {
+				obj, err := mockK8s.Pods("default").Get("foo", metav1.GetOptions{})
+				testutil.AssertNil(t, "get err", err)
+				testutil.AssertEqual(t, "value", "new", obj.Spec.Hostname)
 			},
 		},
 		"update if found": {
-			PreExisting: []*v1.Secret{fakeSecret("foo", "old")},
+			PreExisting: []*v1.Pod{fakePod("foo", "old")},
 			Namespace:   "testing",
-			ToUpsert:    fakeSecret("foo", "new"),
-			Merger:      func(n, o *v1.Secret) *v1.Secret { return n },
-			Validate: func(t *testing.T, mockK8s cv1.SecretsGetter) {
-				secret, err := mockK8s.Secrets("testing").Get("foo", metav1.GetOptions{})
-				testutil.AssertNil(t, "secrets err", err)
-				testutil.AssertEqual(t, "value", "new", secret.StringData["value"])
+			ToUpsert:    fakePod("foo", "new"),
+			Merger:      func(n, o *v1.Pod) *v1.Pod { return n },
+			Validate: func(t *testing.T, mockK8s cv1.PodsGetter) {
+				obj, err := mockK8s.Pods("testing").Get("foo", metav1.GetOptions{})
+				testutil.AssertNil(t, "get err", err)
+				testutil.AssertEqual(t, "value", "new", obj.Spec.Hostname)
 			},
 		},
 		"calls merge with right order": {
 			Namespace:   "default",
-			PreExisting: []*v1.Secret{fakeSecret("foo", "old")},
-			ToUpsert:    fakeSecret("foo", "new"),
-			Merger: func(n, o *v1.Secret) *v1.Secret {
-				n.StringData["value"] = n.StringData["value"] + "-" + o.StringData["value"]
+			PreExisting: []*v1.Pod{fakePod("foo", "old")},
+			ToUpsert:    fakePod("foo", "new"),
+			Merger: func(n, o *v1.Pod) *v1.Pod {
+				n.Spec.Hostname = n.Spec.Hostname + "-" + o.Spec.Hostname
 				return n
 			},
 			ExpectErr: nil,
-			Validate: func(t *testing.T, mockK8s cv1.SecretsGetter) {
-				secret, err := mockK8s.Secrets("default").Get("foo", metav1.GetOptions{})
-				testutil.AssertNil(t, "secrets err", err)
-				testutil.AssertEqual(t, "value", "new-old", secret.StringData["value"])
+			Validate: func(t *testing.T, mockK8s cv1.PodsGetter) {
+				obj, err := mockK8s.Pods("default").Get("foo", metav1.GetOptions{})
+				testutil.AssertNil(t, "get err", err)
+				testutil.AssertEqual(t, "value", "new-old", obj.Spec.Hostname)
 			},
 		},
 	}
@@ -223,14 +223,14 @@ func TestClient_Upsert(t *testing.T) {
 		t.Run(tn, func(t *testing.T) {
 			mockK8s := testclient.NewSimpleClientset().CoreV1()
 
-			secretsClient := NewExampleClient(mockK8s)
+			client := NewExampleClient(mockK8s)
 
 			for _, v := range tc.PreExisting {
-				_, err := secretsClient.Create(tc.Namespace, v)
-				testutil.AssertNil(t, "creating preexisting secrets", err)
+				_, err := client.Create(tc.Namespace, v)
+				testutil.AssertNil(t, "creating preexisting objects", err)
 			}
 
-			_, actualErr := secretsClient.Upsert(tc.Namespace, tc.ToUpsert, tc.Merger)
+			_, actualErr := client.Upsert(tc.Namespace, tc.ToUpsert, tc.Merger)
 			if tc.ExpectErr != nil || actualErr != nil {
 				testutil.AssertErrorsEqual(t, tc.ExpectErr, actualErr)
 
@@ -245,29 +245,29 @@ func TestClient_Upsert(t *testing.T) {
 }
 
 func ExampleDiffWrapper_noDiff() {
-	secret := &v1.Secret{}
+	obj := &v1.Pod{}
 
-	wrapper := DiffWrapper(os.Stdout, func(s *v1.Secret) error {
-		// don't mutate the secret
+	wrapper := DiffWrapper(os.Stdout, func(s *v1.Pod) error {
+		// don't mutate the object
 		return nil
 	})
 
-	wrapper(secret)
+	wrapper(obj)
 
 	// Output: No changes
 }
 
 func ExampleDiffWrapper_changes() {
-	secret := &v1.Secret{}
-	secret.Type = "opaque"
+	obj := &v1.Pod{}
+	obj.Spec.Hostname = "opaque"
 
 	contents := &bytes.Buffer{}
-	wrapper := DiffWrapper(contents, func(s *v1.Secret) error {
-		s.Type = "docker-creds"
+	wrapper := DiffWrapper(contents, func(obj *v1.Pod) error {
+		obj.Spec.Hostname = "docker-creds"
 		return nil
 	})
 
-	fmt.Println("Error:", wrapper(secret))
+	fmt.Println("Error:", wrapper(obj))
 	firstLine := strings.Split(contents.String(), "\n")[0]
 	fmt.Println("First line:", firstLine)
 
@@ -276,13 +276,13 @@ func ExampleDiffWrapper_changes() {
 }
 
 func ExampleDiffWrapper_err() {
-	secret := &v1.Secret{}
+	obj := &v1.Pod{}
 
-	wrapper := DiffWrapper(os.Stdout, func(s *v1.Secret) error {
+	wrapper := DiffWrapper(os.Stdout, func(_ *v1.Pod) error {
 		return errors.New("some-error")
 	})
 
-	fmt.Println(wrapper(secret))
+	fmt.Println(wrapper(obj))
 
 	// Output: some-error
 }
@@ -295,7 +295,7 @@ func TestConditionDeleted(t *testing.T) {
 		wantErr  error
 	}{
 		"not found error": {
-			apiErr:   apierrors.NewNotFound(schema.GroupResource{}, "my-secret"),
+			apiErr:   apierrors.NewNotFound(schema.GroupResource{}, "my-obj"),
 			wantDone: true,
 		},
 		"nil error": {
@@ -321,9 +321,9 @@ func TestConditionDeleted(t *testing.T) {
 
 func ExampleClient_WaitForE_conditionDeleted() {
 	mockK8s := testclient.NewSimpleClientset().CoreV1()
-	secretsClient := NewExampleClient(mockK8s)
+	client := NewExampleClient(mockK8s)
 
-	instance, err := secretsClient.WaitForE(context.Background(), "default", "secret-name", 1*time.Second, ConditionDeleted)
+	instance, err := client.WaitForE(context.Background(), "default", "obj-name", 1*time.Second, ConditionDeleted)
 	fmt.Println("Instance:", instance)
 	fmt.Println("Error:", err)
 
@@ -333,13 +333,13 @@ func ExampleClient_WaitForE_conditionDeleted() {
 
 func ExampleClient_WaitForE_timeout() {
 	mockK8s := testclient.NewSimpleClientset().CoreV1()
-	secretsClient := NewExampleClient(mockK8s)
+	client := NewExampleClient(mockK8s)
 
 	ctx, cancel := context.WithTimeout(context.Background(), 250*time.Millisecond)
 	defer cancel()
 
 	called := 0
-	instance, err := secretsClient.WaitForE(ctx, "default", "secret-name", 100*time.Millisecond, func(_ *v1.Secret, _ error) (bool, error) {
+	instance, err := client.WaitForE(ctx, "default", "object-name", 100*time.Millisecond, func(_ *v1.Pod, _ error) (bool, error) {
 		called++
 		return false, nil
 	})
@@ -383,7 +383,7 @@ func TestWrapPredicate(t *testing.T) {
 	for tn, tc := range cases {
 		t.Run(tn, func(t *testing.T) {
 			called := false
-			wrapped := wrapPredicate(func(_ *v1.Secret) bool {
+			wrapped := wrapPredicate(func(_ *v1.Pod) bool {
 				called = true
 				return tc.predicateResponse
 			})
@@ -395,3 +395,72 @@ func TestWrapPredicate(t *testing.T) {
 		})
 	}
 }
+
+func TestCheckConditionTrue(t *testing.T) {
+	cases := map[string]struct {
+		conditions []v1.PodCondition
+		err        error
+
+		wantDone bool
+		wantErr  error
+	}{
+		"error supplied": {
+			err: errors.New("test"),
+
+			wantDone: true,
+			wantErr:  errors.New("test"),
+		},
+		"condition missing": {
+			conditions: []v1.PodCondition{},
+			wantDone:   false,
+			wantErr:    nil,
+		},
+		"condition unknown": {
+			conditions: []v1.PodCondition{
+				{
+					Type:   v1.PodConditionType(ConditionReady),
+					Status: v1.ConditionUnknown,
+				},
+			},
+			wantDone: false,
+			wantErr:  nil,
+		},
+		"condition true": {
+			conditions: []v1.PodCondition{
+				{
+					Type:   v1.PodConditionType(ConditionReady),
+					Status: v1.ConditionTrue,
+				},
+			},
+			wantDone: true,
+			wantErr:  nil,
+		},
+		"condition false": {
+			conditions: []v1.PodCondition{
+				{
+					Type:    v1.PodConditionType(ConditionReady),
+					Status:  v1.ConditionFalse,
+					Message: "SomeMessage",
+					Reason:  "SomeReason",
+				},
+			},
+			wantDone: true,
+			wantErr:  errors.New("checking Ready failed, status: False message: SomeMessage reason: SomeReason"),
+		},
+	}
+
+	for tn, tc := range cases {
+		t.Run(tn, func(t *testing.T) {
+			pod := &v1.Pod{
+				Status: v1.PodStatus{
+					Conditions: tc.conditions,
+				},
+			}
+
+			actualDone, actualErr := checkConditionTrue(pod, tc.err, ConditionReady)
+
+			testutil.AssertErrorsEqual(t, tc.wantErr, actualErr)
+			testutil.AssertEqual(t, "done", tc.wantDone, actualDone)
+		})
+	}
+}
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 a542713..c56c9c9 100644
--- a/pkg/kf/internal/tools/clientgen/gentest/zz_generated.client.go
+++ b/pkg/kf/internal/tools/clientgen/gentest/zz_generated.client.go
@@ -27,6 +27,7 @@ import (
 
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"knative.dev/pkg/apis"
 	"knative.dev/pkg/kmp"
 )
 
@@ -42,22 +43,27 @@ import (
 
 const (
 	// Kind contains the kind for the backing Kubernetes API.
-	Kind = "Secret"
+	Kind = "Pod"
 
 	// APIVersion contains the version for the backing Kubernetes API.
 	APIVersion = "v1"
 )
 
-// Predicate is a boolean function for a v1.Secret.
-type Predicate func(*v1.Secret) bool
+var (
+	ConditionReady       = apis.ConditionType("Ready")
+	ConditionInitialized = apis.ConditionType(v1.PodInitialized)
+)
+
+// Predicate is a boolean function for a v1.Pod.
+type Predicate func(*v1.Pod) bool
 
-// Mutator is a function that changes v1.Secret.
-type Mutator func(*v1.Secret) error
+// Mutator is a function that changes v1.Pod.
+type Mutator func(*v1.Pod) error
 
 // DiffWrapper wraps a mutator and prints out the diff between the original object
 // and the one it returns if there's no error.
 func DiffWrapper(w io.Writer, mutator Mutator) Mutator {
-	return func(mutable *v1.Secret) error {
+	return func(mutable *v1.Pod) error {
 		before := mutable.DeepCopy()
 
 		if err := mutator(mutable); err != nil {
@@ -70,9 +76,9 @@ func DiffWrapper(w io.Writer, mutator Mutator) Mutator {
 	}
 }
 
-// FormatDiff creates a diff between two v1.Secrets and writes it to the given
+// FormatDiff creates a diff between two v1.Pods and writes it to the given
 // writer.
-func FormatDiff(w io.Writer, leftName, rightName string, left, right *v1.Secret) {
+func FormatDiff(w io.Writer, leftName, rightName string, left, right *v1.Pod) {
 	diff, err := kmp.SafeDiff(left, right)
 	switch {
 	case err != nil:
@@ -90,8 +96,8 @@ func FormatDiff(w io.Writer, leftName, rightName string, left, right *v1.Secret)
 	}
 }
 
-// List represents a collection of v1.Secret.
-type List []v1.Secret
+// List represents a collection of v1.Pod.
+type List []v1.Pod
 
 // Filter returns a new list items for which the predicates fails removed.
 func (list List) Filter(filter Predicate) (out List) {
@@ -104,32 +110,60 @@ func (list List) Filter(filter Predicate) (out List) {
 	return
 }
 
+// ObservedGenerationMatchesGeneration is a predicate that returns true if the
+// object's ObservedGeneration matches the genration of the object.
+func ObservedGenerationMatchesGeneration(obj *v1.Pod) bool {
+	return obj.Generation == obj.Generation
+}
+
+// ExtractConditions converts the native condition types into an apis.Condition
+// array with the Type, Status, Reason, and Message fields intact.
+func ExtractConditions(obj *v1.Pod) (extracted []apis.Condition) {
+	for _, cond := range obj.Status.Conditions {
+		// Only copy the following four fields to be compatible with
+		// recommended Kuberntes fields.
+		extracted = append(extracted, apis.Condition{
+			Type:    apis.ConditionType(cond.Type),
+			Status:  cond.Status,
+			Reason:  cond.Reason,
+			Message: cond.Message,
+		})
+	}
+
+	return
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Client
 ////////////////////////////////////////////////////////////////////////////////
 
-// Client is the interface for interacting with v1.Secret types as OperatorConfig CF style objects.
+// Client is the interface for interacting with v1.Pod types as OperatorConfig CF style objects.
 type Client interface {
-	Create(namespace string, obj *v1.Secret, opts ...CreateOption) (*v1.Secret, error)
-	Update(namespace string, obj *v1.Secret, opts ...UpdateOption) (*v1.Secret, error)
-	Transform(namespace string, name string, transformer Mutator) (*v1.Secret, error)
-	Get(namespace string, name string, opts ...GetOption) (*v1.Secret, error)
+	Create(namespace string, obj *v1.Pod, opts ...CreateOption) (*v1.Pod, error)
+	Update(namespace string, obj *v1.Pod, opts ...UpdateOption) (*v1.Pod, error)
+	Transform(namespace string, name string, transformer Mutator) (*v1.Pod, error)
+	Get(namespace string, name string, opts ...GetOption) (*v1.Pod, error)
 	Delete(namespace string, name string, opts ...DeleteOption) error
-	List(namespace string, opts ...ListOption) ([]v1.Secret, error)
-	Upsert(namespace string, newObj *v1.Secret, merge Merger) (*v1.Secret, error)
-	WaitFor(ctx context.Context, namespace string, name string, interval time.Duration, condition Predicate) (*v1.Secret, error)
-	WaitForE(ctx context.Context, namespace string, name string, interval time.Duration, condition ConditionFuncE) (*v1.Secret, error)
+	List(namespace string, opts ...ListOption) ([]v1.Pod, error)
+	Upsert(namespace string, newObj *v1.Pod, merge Merger) (*v1.Pod, error)
+	WaitFor(ctx context.Context, namespace string, name string, interval time.Duration, condition Predicate) (*v1.Pod, error)
+	WaitForE(ctx context.Context, namespace string, name string, interval time.Duration, condition ConditionFuncE) (*v1.Pod, error)
+
+	// Utility functions
+	WaitForDeletion(ctx context.Context, namespace string, name string, interval time.Duration) (*v1.Pod, error)
+	WaitForConditionReadyTrue(ctx context.Context, namespace string, name string, interval time.Duration) (*v1.Pod, error)
+	WaitForConditionInitializedTrue(ctx context.Context, namespace string, name string, interval time.Duration) (*v1.Pod, error)
 
 	// ClientExtension can be used by the developer to extend the client.
 	ClientExtension
 }
 
 type coreClient struct {
-	kclient      cv1.SecretsGetter
+	kclient      cv1.PodsGetter
 	upsertMutate Mutator
 }
 
-func (core *coreClient) preprocessUpsert(obj *v1.Secret) error {
+func (core *coreClient) preprocessUpsert(obj *v1.Pod) error {
 	if core.upsertMutate == nil {
 		return nil
 	}
@@ -137,30 +171,30 @@ func (core *coreClient) preprocessUpsert(obj *v1.Secret) error {
 	return core.upsertMutate(obj)
 }
 
-// Create inserts the given v1.Secret into the cluster.
+// Create inserts the given v1.Pod into the cluster.
 // The value to be inserted will be preprocessed and validated before being sent.
-func (core *coreClient) Create(namespace string, obj *v1.Secret, opts ...CreateOption) (*v1.Secret, error) {
+func (core *coreClient) Create(namespace string, obj *v1.Pod, opts ...CreateOption) (*v1.Pod, error) {
 	if err := core.preprocessUpsert(obj); err != nil {
 		return nil, err
 	}
 
-	return core.kclient.Secrets(namespace).Create(obj)
+	return core.kclient.Pods(namespace).Create(obj)
 }
 
 // Update replaces the existing object in the cluster with the new one.
 // The value to be inserted will be preprocessed and validated before being sent.
-func (core *coreClient) Update(namespace string, obj *v1.Secret, opts ...UpdateOption) (*v1.Secret, error) {
+func (core *coreClient) Update(namespace string, obj *v1.Pod, opts ...UpdateOption) (*v1.Pod, error) {
 	if err := core.preprocessUpsert(obj); err != nil {
 		return nil, err
 	}
 
-	return core.kclient.Secrets(namespace).Update(obj)
+	return core.kclient.Pods(namespace).Update(obj)
 }
 
 // Transform performs a read/modify/write on the object with the given name
 // and returns the updated object. Transform manages the options for the Get and
 // Update calls.
-func (core *coreClient) Transform(namespace string, name string, mutator Mutator) (*v1.Secret, error) {
+func (core *coreClient) Transform(namespace string, name string, mutator Mutator) (*v1.Pod, error) {
 	obj, err := core.Get(namespace, name)
 	if err != nil {
 		return nil, err
@@ -176,8 +210,8 @@ func (core *coreClient) Transform(namespace string, name string, mutator Mutator
 // Get retrieves an existing object in the cluster with the given name.
 // The function will return an error if an object is retrieved from the cluster
 // but doesn't pass the membership test of this client.
-func (core *coreClient) Get(namespace string, name string, opts ...GetOption) (*v1.Secret, error) {
-	res, err := core.kclient.Secrets(namespace).Get(name, metav1.GetOptions{})
+func (core *coreClient) Get(namespace string, name string, opts ...GetOption) (*v1.Pod, error) {
+	res, err := core.kclient.Pods(namespace).Get(name, metav1.GetOptions{})
 	if err != nil {
 		return nil, fmt.Errorf("couldn't get the OperatorConfig with the name %q: %v", name, err)
 	}
@@ -190,7 +224,7 @@ func (core *coreClient) Get(namespace string, name string, opts ...GetOption) (*
 func (core *coreClient) Delete(namespace string, name string, opts ...DeleteOption) error {
 	cfg := DeleteOptionDefaults().Extend(opts).toConfig()
 
-	if err := core.kclient.Secrets(namespace).Delete(name, cfg.ToDeleteOptions()); err != nil {
+	if err := core.kclient.Pods(namespace).Delete(name, cfg.ToDeleteOptions()); err != nil {
 		return fmt.Errorf("couldn't delete the OperatorConfig with the name %q: %v", name, err)
 	}
 
@@ -210,10 +244,10 @@ func (cfg deleteConfig) ToDeleteOptions() *metav1.DeleteOptions {
 
 // List gets objects in the cluster and filters the results based on the
 // internal membership test.
-func (core *coreClient) List(namespace string, opts ...ListOption) ([]v1.Secret, error) {
+func (core *coreClient) List(namespace string, opts ...ListOption) ([]v1.Pod, error) {
 	cfg := ListOptionDefaults().Extend(opts).toConfig()
 
-	res, err := core.kclient.Secrets(namespace).List(cfg.ToListOptions())
+	res, err := core.kclient.Pods(namespace).List(cfg.ToListOptions())
 	if err != nil {
 		return nil, fmt.Errorf("couldn't list OperatorConfigs: %v", err)
 	}
@@ -234,11 +268,11 @@ func (cfg listConfig) ToListOptions() (resp metav1.ListOptions) {
 }
 
 // Merger is a type to merge an existing value with a new one.
-type Merger func(newObj, oldObj *v1.Secret) *v1.Secret
+type Merger func(newObj, oldObj *v1.Pod) *v1.Pod
 
 // Upsert inserts the object into the cluster if it doesn't already exist, or else
 // calls the merge function to merge the existing and new then performs an Update.
-func (core *coreClient) Upsert(namespace string, newObj *v1.Secret, merge Merger) (*v1.Secret, error) {
+func (core *coreClient) Upsert(namespace string, newObj *v1.Pod, merge Merger) (*v1.Pod, error) {
 	// NOTE: the field selector may be ignored by some Kubernetes resources
 	// so we double check down below.
 	existing, err := core.List(namespace, WithListFieldSelector(map[string]string{"metadata.name": newObj.Name}))
@@ -257,7 +291,7 @@ func (core *coreClient) Upsert(namespace string, newObj *v1.Secret, merge Merger
 
 // WaitFor is a convenience wrapper for WaitForE that fails if the error
 // passed is non-nil. It allows the use of Predicates instead of ConditionFuncE.
-func (core *coreClient) WaitFor(ctx context.Context, namespace string, name string, interval time.Duration, condition Predicate) (*v1.Secret, error) {
+func (core *coreClient) WaitFor(ctx context.Context, namespace string, name string, interval time.Duration, condition Predicate) (*v1.Pod, error) {
 	return core.WaitForE(ctx, namespace, name, interval, wrapPredicate(condition))
 }
 
@@ -267,19 +301,19 @@ func (core *coreClient) WaitFor(ctx context.Context, namespace string, name stri
 //
 // This function MAY retrieve a nil instance and an apiErr. It's up to the
 // function to decide how to handle the apiErr.
-type ConditionFuncE func(instance *v1.Secret, apiErr error) (done bool, err error)
+type ConditionFuncE func(instance *v1.Pod, apiErr error) (done bool, err error)
 
 // WaitForE polls for the given object every interval until the condition
 // function becomes done or the timeout expires. The first poll occurs
 // immediately after the function is invoked.
 //
 // The function polls infinitely if no timeout is supplied.
-func (core *coreClient) WaitForE(ctx context.Context, namespace string, name string, interval time.Duration, condition ConditionFuncE) (instance *v1.Secret, err error) {
+func (core *coreClient) WaitForE(ctx context.Context, namespace string, name string, interval time.Duration, condition ConditionFuncE) (instance *v1.Pod, err error) {
 	var done bool
 	tick := time.Tick(interval)
 
 	for {
-		instance, err = core.kclient.Secrets(namespace).Get(name, metav1.GetOptions{})
+		instance, err = core.kclient.Pods(namespace).Get(name, metav1.GetOptions{})
 		if done, err = condition(instance, err); done {
 			return
 		}
@@ -295,7 +329,7 @@ func (core *coreClient) WaitForE(ctx context.Context, namespace string, name str
 
 // ConditionDeleted is a ConditionFuncE that succeeds if the error returned by
 // the cluster was a not found error.
-func ConditionDeleted(_ *v1.Secret, apiErr error) (bool, error) {
+func ConditionDeleted(_ *v1.Pod, apiErr error) (bool, error) {
 	if apiErr != nil {
 		if apierrors.IsNotFound(apiErr) {
 			apiErr = nil
@@ -310,7 +344,7 @@ func ConditionDeleted(_ *v1.Secret, apiErr error) (bool, error) {
 // wrapPredicate converts a predicate to a ConditionFuncE that fails if the
 // error is not nil
 func wrapPredicate(condition Predicate) ConditionFuncE {
-	return func(obj *v1.Secret, err error) (bool, error) {
+	return func(obj *v1.Pod, err error) (bool, error) {
 		if err != nil {
 			return true, err
 		}
@@ -318,3 +352,60 @@ func wrapPredicate(condition Predicate) ConditionFuncE {
 		return condition(obj), nil
 	}
 }
+
+// WaitForDeletion is a utility function that combines WaitForE with ConditionDeleted.
+func (core *coreClient) WaitForDeletion(ctx context.Context, namespace string, name string, interval time.Duration) (instance *v1.Pod, err error) {
+	return core.WaitForE(ctx, namespace, name, interval, ConditionDeleted)
+}
+
+func checkConditionTrue(obj *v1.Pod, err error, condition apis.ConditionType) (bool, error) {
+	if err != nil {
+		return true, err
+	}
+
+	// don't propagate old statuses
+	if !ObservedGenerationMatchesGeneration(obj) {
+		return false, nil
+	}
+
+	for _, cond := range ExtractConditions(obj) {
+		if cond.Type == condition {
+			switch {
+			case cond.IsTrue():
+				return true, nil
+
+			case cond.IsUnknown():
+				return false, nil
+
+			default:
+				// return true and a failure assuming IsFalse and other statuses can't be
+				// recovered from because they violate the K8s spec
+				return true, fmt.Errorf("checking %s failed, status: %s message: %s reason: %s", cond.Type, cond.Status, cond.Message, cond.Reason)
+			}
+		}
+	}
+
+	return false, nil
+}
+
+// ConditionReadyTrue is a ConditionFuncE that waits for Condition{Ready  Ready} to
+// become true and fails with an error if the condition becomes false.
+func ConditionReadyTrue(obj *v1.Pod, err error) (bool, error) {
+	return checkConditionTrue(obj, err, ConditionReady)
+}
+
+// WaitForConditionReadyTrue is a utility function that combines WaitForE with ConditionReadyTrue.
+func (core *coreClient) WaitForConditionReadyTrue(ctx context.Context, namespace string, name string, interval time.Duration) (instance *v1.Pod, err error) {
+	return core.WaitForE(ctx, namespace, name, interval, ConditionReadyTrue)
+}
+
+// ConditionInitializedTrue is a ConditionFuncE that waits for Condition{Initialized v1.PodInitialized } to
+// become true and fails with an error if the condition becomes false.
+func ConditionInitializedTrue(obj *v1.Pod, err error) (bool, error) {
+	return checkConditionTrue(obj, err, ConditionInitialized)
+}
+
+// WaitForConditionInitializedTrue is a utility function that combines WaitForE with ConditionInitializedTrue.
+func (core *coreClient) WaitForConditionInitializedTrue(ctx context.Context, namespace string, name string, interval time.Duration) (instance *v1.Pod, err error) {
+	return core.WaitForE(ctx, namespace, name, interval, ConditionInitializedTrue)
+}
diff --git a/pkg/kf/marketplace/client_test.go b/pkg/kf/marketplace/client_test.go
new file mode 100644
index 0000000..c2e968e
--- /dev/null
+++ b/pkg/kf/marketplace/client_test.go
@@ -0,0 +1,155 @@
+// 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 marketplace
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+
+	"github.com/google/kf/pkg/kf/testutil"
+	"github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
+	servicecatalog "github.com/poy/service-catalog/pkg/svcat/service-catalog"
+	servicecatalogfakes "github.com/poy/service-catalog/pkg/svcat/service-catalog/service-catalogfakes"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestClient_Marketplace(t *testing.T) {
+	t.Parallel()
+
+	cases := map[string]struct {
+		InstanceName  string
+		Namespace     string
+		GetClassesErr error
+		GetPlansErr   error
+
+		ExpectErr error
+	}{
+		"default values": {
+			InstanceName: "instance-name",
+			ExpectErr:    nil,
+		},
+		"custom values": {
+			InstanceName: "instance-name",
+			Namespace:    "custom-namespace",
+			ExpectErr:    nil,
+		},
+		"error in get classes": {
+			InstanceName:  "instance-name",
+			GetClassesErr: errors.New("server-err"),
+			ExpectErr:     errors.New("server-err"),
+		},
+		"error in get plans": {
+			InstanceName: "instance-name",
+			GetPlansErr:  errors.New("server-err"),
+			ExpectErr:    errors.New("server-err"),
+		},
+	}
+
+	for tn, tc := range cases {
+		t.Run(tn, func(t *testing.T) {
+			fakeClient := &servicecatalogfakes.FakeSvcatClient{}
+
+			fakeClient.RetrieveClassesStub = func(scope servicecatalog.ScopeOptions) ([]servicecatalog.Class, error) {
+				testutil.AssertEqual(t, "namespace", tc.Namespace, scope.Namespace)
+
+				return nil, tc.GetClassesErr
+			}
+
+			fakeClient.RetrievePlansStub = func(classFilter string, scope servicecatalog.ScopeOptions) ([]servicecatalog.Plan, error) {
+				testutil.AssertEqual(t, "namespace", tc.Namespace, scope.Namespace)
+				testutil.AssertEqual(t, "classFilter", "", classFilter)
+
+				return nil, tc.GetPlansErr
+			}
+
+			client := NewClient(func(ns string) servicecatalog.SvcatClient {
+				testutil.AssertEqual(t, "namespace", tc.Namespace, ns)
+
+				return fakeClient
+			})
+
+			_, actualErr := client.Marketplace(tc.Namespace)
+			if tc.ExpectErr != nil || actualErr != nil {
+				if fmt.Sprint(tc.ExpectErr) != fmt.Sprint(actualErr) {
+					t.Fatalf("wanted err: %v, got: %v", tc.ExpectErr, actualErr)
+				}
+
+				return
+			}
+
+			testutil.AssertEqual(t, "calls to RetrieveClasses", 1, fakeClient.RetrieveClassesCallCount())
+			testutil.AssertEqual(t, "calls to RetrievePlans", 1, fakeClient.RetrievePlansCallCount())
+		})
+	}
+}
+
+func TestClient_BrokerName(t *testing.T) {
+	t.Parallel()
+
+	cases := map[string]struct {
+		ExpectedName                   string
+		Namespace                      string
+		ExpectErr                      error
+		ExpectedRetrieveClassByNameErr error
+	}{
+		"returns broker name": {
+			ExpectedName: "some-broker-name",
+		},
+		"fetching class fails": {
+			ExpectedRetrieveClassByNameErr: errors.New("some-error"),
+			ExpectErr:                      errors.New("some-error"),
+		},
+	}
+
+	for tn, tc := range cases {
+		t.Run(tn, func(t *testing.T) {
+			fakeClient := &servicecatalogfakes.FakeSvcatClient{}
+			fakeClass := &fakeClass{brokerName: tc.ExpectedName}
+			expectedSvc := v1beta1.ServiceInstance{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: tc.Namespace,
+				},
+			}
+
+			fakeClient.RetrieveClassByNameStub = func(name string, opts servicecatalog.ScopeOptions) (servicecatalog.Class, error) {
+				return fakeClass, tc.ExpectedRetrieveClassByNameErr
+			}
+
+			client := NewClient(func(ns string) servicecatalog.SvcatClient {
+				testutil.AssertEqual(t, "namespace", tc.Namespace, ns)
+				return fakeClient
+			})
+
+			name, actualErr := client.BrokerName(expectedSvc)
+			if tc.ExpectErr != nil || actualErr != nil {
+				testutil.AssertErrorsEqual(t, tc.ExpectErr, actualErr)
+				return
+			}
+
+			testutil.AssertEqual(t, "broker name", tc.ExpectedName, name)
+		})
+	}
+}
+
+// fakeClass implements servicecatalog.Class. There isn't a fake provided.
+type fakeClass struct {
+	servicecatalog.Class
+	brokerName string
+}
+
+func (f *fakeClass) GetServiceBrokerName() string {
+	return f.brokerName
+}
diff --git a/pkg/kf/services/services_test.go b/pkg/kf/services/services_test.go
deleted file mode 100644
index 555c6c5..0000000
--- a/pkg/kf/services/services_test.go
+++ /dev/null
@@ -1,336 +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 services
-
-import (
-	"errors"
-	"fmt"
-	"testing"
-
-	"github.com/google/kf/pkg/kf/testutil"
-	"github.com/poy/service-catalog/pkg/apis/servicecatalog/v1beta1"
-	servicecatalog "github.com/poy/service-catalog/pkg/svcat/service-catalog"
-	servicecatalogfakes "github.com/poy/service-catalog/pkg/svcat/service-catalog/service-catalogfakes"
-)
-
-func TestClient_DeleteService(t *testing.T) {
-	t.Parallel()
-
-	cases := map[string]struct {
-		InstanceName string
-		Options      []DeleteServiceOption
-		ServerErr    error
-
-		ExpectErr error
-	}{
-		"default values": {
-			InstanceName: "instance-name",
-			Options:      []DeleteServiceOption{},
-			ExpectErr:    nil,
-		},
-		"custom values": {
-			InstanceName: "instance-name",
-			Options: []DeleteServiceOption{
-				WithDeleteServiceNamespace("custom-namespace"),
-			},
-			ExpectErr: nil,
-		},
-		"error in delete": {
-			InstanceName: "instance-name",
-			ServerErr:    errors.New("delete-err"),
-			ExpectErr:    errors.New("delete-err"),
-		},
-	}
-
-	for tn, tc := range cases {
-		t.Run(tn, func(t *testing.T) {
-			expectedCfg := DeleteServiceOptionDefaults().Extend(tc.Options).toConfig()
-			fakeClient := &servicecatalogfakes.FakeSvcatClient{}
-
-			fakeClient.DeprovisionStub = func(namespace, instanceName string) error {
-				testutil.AssertEqual(t, "instanceName", tc.InstanceName, instanceName)
-				testutil.AssertEqual(t, "namespace", expectedCfg.Namespace, namespace)
-
-				return tc.ServerErr
-			}
-
-			client := NewClient(func(ns string) servicecatalog.SvcatClient {
-				testutil.AssertEqual(t, "namespace", expectedCfg.Namespace, ns)
-
-				return fakeClient
-			})
-
-			actualErr := client.DeleteService(tc.InstanceName, tc.Options...)
-			if tc.ExpectErr != nil || actualErr != nil {
-				testutil.AssertErrorsEqual(t, tc.ExpectErr, actualErr)
-
-				return
-			}
-
-			testutil.AssertEqual(t, "calls to deprovision", 1, fakeClient.DeprovisionCallCount())
-		})
-	}
-}
-
-func TestClient_GetService(t *testing.T) {
-	t.Parallel()
-
-	cases := map[string]struct {
-		InstanceName string
-		Options      []GetServiceOption
-		ServerErr    error
-
-		ExpectErr error
-	}{
-		"default values": {
-			InstanceName: "instance-name",
-			Options:      []GetServiceOption{},
-			ExpectErr:    nil,
-		},
-		"custom values": {
-			InstanceName: "instance-name",
-			Options: []GetServiceOption{
-				WithGetServiceNamespace("custom-namespace"),
-			},
-			ExpectErr: nil,
-		},
-		"error in get": {
-			InstanceName: "instance-name",
-			ServerErr:    errors.New("delete-err"),
-			ExpectErr:    errors.New("delete-err"),
-		},
-	}
-
-	for tn, tc := range cases {
-		t.Run(tn, func(t *testing.T) {
-			expectedCfg := GetServiceOptionDefaults().Extend(tc.Options).toConfig()
-			fakeClient := &servicecatalogfakes.FakeSvcatClient{}
-
-			fakeClient.RetrieveInstanceStub = func(namespace, instanceName string) (*v1beta1.ServiceInstance, error) {
-				testutil.AssertEqual(t, "instanceName", tc.InstanceName, instanceName)
-				testutil.AssertEqual(t, "namespace", expectedCfg.Namespace, namespace)
-
-				return nil, tc.ServerErr
-			}
-
-			client := NewClient(func(ns string) servicecatalog.SvcatClient {
-				testutil.AssertEqual(t, "namespace", expectedCfg.Namespace, ns)
-
-				return fakeClient
-			})
-
-			_, actualErr := client.GetService(tc.InstanceName, tc.Options...)
-			if tc.ExpectErr != nil || actualErr != nil {
-				testutil.AssertErrorsEqual(t, tc.ExpectErr, actualErr)
-
-				return
-			}
-
-			testutil.AssertEqual(t, "calls to RetrieveInstance", 1, fakeClient.RetrieveInstanceCallCount())
-		})
-	}
-}
-
-func TestClient_ListServices(t *testing.T) {
-	t.Parallel()
-
-	cases := map[string]struct {
-		InstanceName string
-		Options      []ListServicesOption
-		ServerErr    error
-
-		ExpectErr error
-	}{
-		"default values": {
-			InstanceName: "instance-name",
-			Options:      []ListServicesOption{},
-			ExpectErr:    nil,
-		},
-		"custom values": {
-			InstanceName: "instance-name",
-			Options: []ListServicesOption{
-				WithListServicesNamespace("custom-namespace"),
-			},
-			ExpectErr: nil,
-		},
-		"error in get": {
-			InstanceName: "instance-name",
-			ServerErr:    errors.New("server-err"),
-			ExpectErr:    errors.New("server-err"),
-		},
-	}
-
-	for tn, tc := range cases {
-		t.Run(tn, func(t *testing.T) {
-			expectedCfg := ListServicesOptionDefaults().Extend(tc.Options).toConfig()
-			fakeClient := &servicecatalogfakes.FakeSvcatClient{}
-
-			fakeClient.RetrieveInstancesStub = func(namespace, classFilter, planFilter string) (*v1beta1.ServiceInstanceList, error) {
-				testutil.AssertEqual(t, "namespace", expectedCfg.Namespace, namespace)
-				testutil.AssertEqual(t, "classFilter", "", classFilter)
-				testutil.AssertEqual(t, "planFilter", "", planFilter)
-
-				return nil, tc.ServerErr
-			}
-
-			client := NewClient(func(ns string) servicecatalog.SvcatClient {
-				testutil.AssertEqual(t, "namespace", expectedCfg.Namespace, ns)
-
-				return fakeClient
-			})
-
-			_, actualErr := client.ListServices(tc.Options...)
-			if tc.ExpectErr != nil || actualErr != nil {
-				if fmt.Sprint(tc.ExpectErr) != fmt.Sprint(actualErr) {
-					t.Fatalf("wanted err: %v, got: %v", tc.ExpectErr, actualErr)
-				}
-
-				return
-			}
-
-			testutil.AssertEqual(t, "calls to RetrieveInstances", 1, fakeClient.RetrieveInstancesCallCount())
-		})
-	}
-}
-
-func TestClient_Marketplace(t *testing.T) {
-	t.Parallel()
-
-	cases := map[string]struct {
-		InstanceName  string
-		Options       []MarketplaceOption
-		GetClassesErr error
-		GetPlansErr   error
-
-		ExpectErr error
-	}{
-		"default values": {
-			InstanceName: "instance-name",
-			Options:      []MarketplaceOption{},
-			ExpectErr:    nil,
-		},
-		"custom values": {
-			InstanceName: "instance-name",
-			Options: []MarketplaceOption{
-				WithMarketplaceNamespace("custom-namespace"),
-			},
-			ExpectErr: nil,
-		},
-		"error in get classes": {
-			InstanceName:  "instance-name",
-			GetClassesErr: errors.New("server-err"),
-			ExpectErr:     errors.New("server-err"),
-		},
-		"error in get plans": {
-			InstanceName: "instance-name",
-			GetPlansErr:  errors.New("server-err"),
-			ExpectErr:    errors.New("server-err"),
-		},
-	}
-
-	for tn, tc := range cases {
-		t.Run(tn, func(t *testing.T) {
-			expectedCfg := MarketplaceOptionDefaults().Extend(tc.Options).toConfig()
-			fakeClient := &servicecatalogfakes.FakeSvcatClient{}
-
-			fakeClient.RetrieveClassesStub = func(scope servicecatalog.ScopeOptions) ([]servicecatalog.Class, error) {
-				testutil.AssertEqual(t, "namespace", expectedCfg.Namespace, scope.Namespace)
-
-				return nil, tc.GetClassesErr
-			}
-
-			fakeClient.RetrievePlansStub = func(classFilter string, scope servicecatalog.ScopeOptions) ([]servicecatalog.Plan, error) {
-				testutil.AssertEqual(t, "namespace", expectedCfg.Namespace, scope.Namespace)
-				testutil.AssertEqual(t, "classFilter", "", classFilter)
-
-				return nil, tc.GetPlansErr
-			}
-
-			client := NewClient(func(ns string) servicecatalog.SvcatClient {
-				testutil.AssertEqual(t, "namespace", expectedCfg.Namespace, ns)
-
-				return fakeClient
-			})
-
-			_, actualErr := client.Marketplace(tc.Options...)
-			if tc.ExpectErr != nil || actualErr != nil {
-				if fmt.Sprint(tc.ExpectErr) != fmt.Sprint(actualErr) {
-					t.Fatalf("wanted err: %v, got: %v", tc.ExpectErr, actualErr)
-				}
-
-				return
-			}
-
-			testutil.AssertEqual(t, "calls to RetrieveClasses", 1, fakeClient.RetrieveClassesCallCount())
-			testutil.AssertEqual(t, "calls to RetrievePlans", 1, fakeClient.RetrievePlansCallCount())
-		})
-	}
-}
-
-func TestClient_BrokerName(t *testing.T) {
-	t.Parallel()
-
-	cases := map[string]struct {
-		ExpectedName string
-		Options      []BrokerNameOption
-
-		ExpectErr                      error
-		ExpectedRetrieveClassByNameErr error
-	}{
-		"returns broker name": {
-			ExpectedName: "some-broker-name",
-		},
-		"fetching class fails": {
-			ExpectedRetrieveClassByNameErr: errors.New("some-error"),
-			ExpectErr:                      errors.New("some-error"),
-		},
-	}
-
-	for tn, tc := range cases {
-		t.Run(tn, func(t *testing.T) {
-			expectedCfg := BrokerNameOptionDefaults().Extend(tc.Options).toConfig()
-			fakeClient := &servicecatalogfakes.FakeSvcatClient{}
-			fakeClass := &fakeClass{brokerName: tc.ExpectedName}
-			expectedSvc := v1beta1.ServiceInstance{}
-
-			fakeClient.RetrieveClassByNameStub = func(name string, opts servicecatalog.ScopeOptions) (servicecatalog.Class, error) {
-				return fakeClass, tc.ExpectedRetrieveClassByNameErr
-			}
-
-			client := NewClient(func(ns string) servicecatalog.SvcatClient {
-				testutil.AssertEqual(t, "namespace", expectedCfg.Namespace, ns)
-				return fakeClient
-			})
-
-			name, actualErr := client.BrokerName(expectedSvc, tc.Options...)
-			if tc.ExpectErr != nil || actualErr != nil {
-				testutil.AssertErrorsEqual(t, tc.ExpectErr, actualErr)
-				return
-			}
-
-			testutil.AssertEqual(t, "broker name", tc.ExpectedName, name)
-		})
-	}
-}
-
-// fakeClass implements servicecatalog.Class. There isn't a fake provided.
-type fakeClass struct {
-	servicecatalog.Class
-	brokerName string
-}
-
-func (f *fakeClass) GetServiceBrokerName() string {
-	return f.brokerName
-}
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)
 	}