Repository: operator-framework/operator-lifecycle-manager
Pull request #1782 (2020-09-29 02:37:37)

[release-4.4] Bug 1870784: Retry conflicting catalog source pod update after fetching latest pod spec
**Description of the change:**
Updating the catalog source pod was intermittently failing due to resource version conflicts. This change switches to using Server side apply to do perform the catalog source pod update.

cherry-pick of https://github.com/operator-framework/operator-lifecycle-manager/pull/1734


Patch:
diff --git a/cmd/catalog/main.go b/cmd/catalog/main.go
index 70b3a67c3..917b8290e 100644
--- a/cmd/catalog/main.go
+++ b/cmd/catalog/main.go
@@ -13,6 +13,7 @@ import (
 	"github.com/prometheus/client_golang/prometheus/promhttp"
 	log "github.com/sirupsen/logrus"
 	utilclock "k8s.io/apimachinery/pkg/util/clock"
+	k8sscheme "k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/tools/clientcmd"
 
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client"
@@ -172,7 +173,7 @@ func main() {
 	}
 
 	// Create a new instance of the operator.
-	op, err := catalog.NewOperator(ctx, *kubeConfigPath, utilclock.RealClock{}, logger, *wakeupInterval, *configmapServerImage, *utilImage, *catalogNamespace)
+	op, err := catalog.NewOperator(ctx, *kubeConfigPath, utilclock.RealClock{}, logger, *wakeupInterval, *configmapServerImage, *utilImage, *catalogNamespace, k8sscheme.Scheme)
 	if err != nil {
 		log.Panicf("error configuring operator: %s", err.Error())
 	}
diff --git a/go.mod b/go.mod
index 28dbd26c3..fe550617e 100644
--- a/go.mod
+++ b/go.mod
@@ -37,6 +37,7 @@ require (
 	k8s.io/kube-aggregator v0.0.0
 	k8s.io/kube-openapi v0.0.0-20190918143330-0270cf2f1c1d
 	k8s.io/kubernetes v1.16.0
+	sigs.k8s.io/controller-runtime v0.4.0
 	sigs.k8s.io/controller-tools v0.2.4
 )
 
diff --git a/go.sum b/go.sum
index a9f947b41..30bb188ef 100644
--- a/go.sum
+++ b/go.sum
@@ -228,7 +228,10 @@ github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG
 github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54=
+github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
 github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
 github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
@@ -306,6 +309,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8=
 github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE=
 github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -315,6 +319,7 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
 github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
 github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -402,6 +407,7 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
 github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
 github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
 github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
@@ -529,6 +535,7 @@ github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86w
 github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
 github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
@@ -536,6 +543,7 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
 github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@@ -703,12 +711,14 @@ go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -742,6 +752,7 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -782,7 +793,10 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -812,6 +826,7 @@ golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST
 golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -843,7 +858,9 @@ golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db h1:9hRk1xeL9LTT3yX/941DqeB
 golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
 golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 h1:s5lp4ug7qHzUccgyFdjsX7OZDzHXRaePrF3B3vmUiuM=
 golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU=
 gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
 gonum.org/v1/gonum v0.0.0-20190710053202-4340aa3071a0 h1:2qZ38BsejXrhuetzb8UxucqrWDZKjypFSZA82hLCpZ4=
 gonum.org/v1/gonum v0.0.0-20190710053202-4340aa3071a0/go.mod h1:03dgh78c4UvU1WksguQ/lvJQXbezKQGJSrwwRq5MraQ=
@@ -888,6 +905,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@@ -911,6 +930,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -988,6 +1008,8 @@ modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs
 modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
 rsc.io/letsencrypt v0.0.1 h1:DV0d09Ne9E7UUa9ZqWktZ9L2VmybgTgfq7xlfFR/bbU=
 rsc.io/letsencrypt v0.0.1/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY=
+sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg=
+sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns=
 sigs.k8s.io/controller-tools v0.2.4 h1:la1h46EzElvWefWLqfsXrnsO3lZjpkI0asTpX6h8PLA=
 sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA=
 sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
@@ -995,6 +1017,8 @@ sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5
 sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca h1:6dsH6AYQWbyZmtttJNe8Gq1cXOeS1BdV3eW37zHilAQ=
 sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA=
+sigs.k8s.io/testing_frameworks v0.1.2 h1:vK0+tvjF0BZ/RYFeZ1E6BYBwHJJXhjuZ3TdsEKH+UQM=
+sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w=
 sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go
index ee494235c..32f1514fd 100644
--- a/pkg/controller/operators/catalog/operator.go
+++ b/pkg/controller/operators/catalog/operator.go
@@ -11,6 +11,8 @@ import (
 	"sync"
 	"time"
 
+	"k8s.io/apimachinery/pkg/runtime"
+
 	errorwrap "github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"google.golang.org/grpc/connectivity"
@@ -47,6 +49,7 @@ import (
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/grpc"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
+	controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client"
 	index "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/index"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
@@ -70,6 +73,7 @@ const (
 	generatedByKey         = "olm.generated-by"
 	maxInstallPlanCount    = 5
 	maxDeletesPerSweep     = 5
+	RegistryFieldManager   = "olm.registry"
 )
 
 // Operator represents a Kubernetes operator that executes InstallPlans by
@@ -102,7 +106,7 @@ type Operator struct {
 type CatalogSourceSyncFunc func(logger *logrus.Entry, in *v1alpha1.CatalogSource) (out *v1alpha1.CatalogSource, continueSync bool, syncError error)
 
 // NewOperator creates a new Catalog Operator.
-func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clock, logger *logrus.Logger, resync time.Duration, configmapRegistryImage, utilImage string, operatorNamespace string) (*Operator, error) {
+func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clock, logger *logrus.Logger, resync time.Duration, configmapRegistryImage, utilImage string, operatorNamespace string, scheme *runtime.Scheme) (*Operator, error) {
 	resyncPeriod := queueinformer.ResyncWithJitter(resync, 0.2)
 	config, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
 	if err != nil {
@@ -131,6 +135,11 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
 	// Create an OperatorLister
 	lister := operatorlister.NewLister()
 
+	ssaClient, err := controllerclient.NewForConfig(config, scheme, RegistryFieldManager)
+	if err != nil {
+		return nil, err
+	}
+
 	// Allocate the new instance of an Operator.
 	op := &Operator{
 		Operator:                 queueOperator,
@@ -151,7 +160,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
 		clientAttenuator:         scoped.NewClientAttenuator(logger, config, opClient, crClient, dynamicClient),
 	}
 	op.sources = grpc.NewSourceStore(logger, 10*time.Second, 10*time.Minute, op.syncSourceState)
-	op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, opClient, configmapRegistryImage, op.now)
+	op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, opClient, configmapRegistryImage, op.now, ssaClient)
 
 	// Wire OLM CR sharedIndexInformers
 	crInformerFactory := externalversions.NewSharedInformerFactoryWithOptions(op.client, resyncPeriod())
diff --git a/pkg/controller/registry/reconciler/grpc.go b/pkg/controller/registry/reconciler/grpc.go
index 78e77f3bc..2a1cde643 100644
--- a/pkg/controller/registry/reconciler/grpc.go
+++ b/pkg/controller/registry/reconciler/grpc.go
@@ -1,9 +1,11 @@
 package reconciler
 
 import (
+	"context"
 	"fmt"
 
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
+	controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
@@ -69,9 +71,10 @@ func (s *grpcCatalogSourceDecorator) Pod() *v1.Pod {
 }
 
 type GrpcRegistryReconciler struct {
-	now      nowFunc
-	Lister   operatorlister.OperatorLister
-	OpClient operatorclient.ClientInterface
+	now       nowFunc
+	Lister    operatorlister.OperatorLister
+	OpClient  operatorclient.ClientInterface
+	SSAClient *controllerclient.ServerSideApplier
 }
 
 var _ RegistryReconciler = &GrpcRegistryReconciler{}
@@ -193,16 +196,17 @@ func (c *GrpcRegistryReconciler) ensureUpdatePod(source grpcCatalogSourceDecorat
 			logrus.WithField("CatalogSource", source.GetName()).Info("detect image update for catalogsource pod")
 
 			updateFlag = true
-			updatePod.Labels[CatalogSourceLabelKey] = source.GetName()
-			updatePod.Labels[CatalogSourceUpdateKey] = ""
 
 			// Update the update pod to promote it to serving pod
-			_, err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Update(updatePod)
+			err := c.SSAClient.Apply(context.TODO(), updatePod, func(p *v1.Pod) error {
+				p.Labels[CatalogSourceLabelKey] = source.GetName()
+				p.Labels[CatalogSourceUpdateKey] = ""
+				return nil
+			})()
+
 			if err != nil {
-				return errors.Wrapf(err, "error creating new pod: %s", source.Pod().GetName())
+				return errors.Wrapf(err, "error updating catalog source pod: %s", source.Pod().GetName())
 			}
-
-			break
 		}
 	}
 
diff --git a/pkg/controller/registry/reconciler/reconciler.go b/pkg/controller/registry/reconciler/reconciler.go
index fac7ee7f5..4e8a0f03e 100644
--- a/pkg/controller/registry/reconciler/reconciler.go
+++ b/pkg/controller/registry/reconciler/reconciler.go
@@ -3,6 +3,7 @@ package reconciler
 
 import (
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
+	controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
 	v1 "k8s.io/api/core/v1"
@@ -46,6 +47,7 @@ type registryReconcilerFactory struct {
 	Lister               operatorlister.OperatorLister
 	OpClient             operatorclient.ClientInterface
 	ConfigMapServerImage string
+	SSAClient            *controllerclient.ServerSideApplier
 }
 
 // ReconcilerForSource returns a RegistryReconciler based on the configuration of the given CatalogSource.
@@ -62,9 +64,10 @@ func (r *registryReconcilerFactory) ReconcilerForSource(source *v1alpha1.Catalog
 	case v1alpha1.SourceTypeGrpc:
 		if source.Spec.Image != "" {
 			return &GrpcRegistryReconciler{
-				now:      r.now,
-				Lister:   r.Lister,
-				OpClient: r.OpClient,
+				now:       r.now,
+				Lister:    r.Lister,
+				OpClient:  r.OpClient,
+				SSAClient: r.SSAClient,
 			}
 		} else if source.Spec.Address != "" {
 			return &GrpcAddressRegistryReconciler{
@@ -76,12 +79,13 @@ func (r *registryReconcilerFactory) ReconcilerForSource(source *v1alpha1.Catalog
 }
 
 // NewRegistryReconcilerFactory returns an initialized RegistryReconcilerFactory.
-func NewRegistryReconcilerFactory(lister operatorlister.OperatorLister, opClient operatorclient.ClientInterface, configMapServerImage string, now nowFunc) RegistryReconcilerFactory {
+func NewRegistryReconcilerFactory(lister operatorlister.OperatorLister, opClient operatorclient.ClientInterface, configMapServerImage string, now nowFunc, ssaClient *controllerclient.ServerSideApplier) RegistryReconcilerFactory {
 	return &registryReconcilerFactory{
 		now:                  now,
 		Lister:               lister,
 		OpClient:             opClient,
 		ConfigMapServerImage: configMapServerImage,
+		SSAClient:            ssaClient,
 	}
 }
 
diff --git a/pkg/lib/controller-runtime/client/fake_ssa.go b/pkg/lib/controller-runtime/client/fake_ssa.go
new file mode 100644
index 000000000..520832110
--- /dev/null
+++ b/pkg/lib/controller-runtime/client/fake_ssa.go
@@ -0,0 +1,57 @@
+package client
+
+import (
+	"context"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	k8scontrollerclient "sigs.k8s.io/controller-runtime/pkg/client"
+	fakecontrollerclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
+
+// FakeApplier provides a wrapper around the fake k8s controller client to convert the unsupported apply-type patches to merge patches.
+func NewFakeApplier(scheme *runtime.Scheme, owner string, objs ...runtime.Object) *ServerSideApplier {
+	return &ServerSideApplier{
+		client: &fakeApplier{fakecontrollerclient.NewFakeClientWithScheme(scheme, objs...)},
+		Scheme: scheme,
+		Owner:  k8scontrollerclient.FieldOwner(owner),
+	}
+}
+
+type fakeApplier struct {
+	k8scontrollerclient.Client
+}
+
+func (c *fakeApplier) Patch(ctx context.Context, obj runtime.Object, patch k8scontrollerclient.Patch, opts ...k8scontrollerclient.PatchOption) error {
+	patch, opts = convertApplyToMergePatch(patch, obj, opts...)
+	return c.Client.Patch(ctx, obj, patch, opts...)
+}
+
+func (c *fakeApplier) Status() k8scontrollerclient.StatusWriter {
+	return fakeStatusWriter{c.Client.Status()}
+}
+
+type fakeStatusWriter struct {
+	k8scontrollerclient.StatusWriter
+}
+
+func (c fakeStatusWriter) Patch(ctx context.Context, obj runtime.Object, patch k8scontrollerclient.Patch, opts ...k8scontrollerclient.PatchOption) error {
+	patch, opts = convertApplyToMergePatch(patch, obj, opts...)
+	return c.StatusWriter.Patch(ctx, obj, patch, opts...)
+}
+
+func convertApplyToMergePatch(patch k8scontrollerclient.Patch, obj runtime.Object, opts ...k8scontrollerclient.PatchOption) (k8scontrollerclient.Patch, []k8scontrollerclient.PatchOption) {
+	// Apply patch type is not supported on the fake controller
+	if patch.Type() == types.ApplyPatchType {
+		data, _ := patch.Data(obj)
+		patch = k8scontrollerclient.ConstantPatch(types.MergePatchType, data)
+		patchOptions := make([]k8scontrollerclient.PatchOption, 0, len(opts))
+		for _, opt := range opts {
+			if opt == k8scontrollerclient.ForceOwnership {
+				continue
+			}
+			patchOptions = append(patchOptions, opt)
+		}
+		opts = patchOptions
+	}
+	return patch, opts
+}
diff --git a/pkg/lib/controller-runtime/client/ssa.go b/pkg/lib/controller-runtime/client/ssa.go
new file mode 100644
index 000000000..cae89e56d
--- /dev/null
+++ b/pkg/lib/controller-runtime/client/ssa.go
@@ -0,0 +1,196 @@
+package client
+
+import (
+	"context"
+	"fmt"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	kscheme "k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/rest"
+	apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
+	"reflect"
+	k8scontrollerclient "sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+const (
+	defaultOwner = "olm.registry"
+)
+
+type Object interface {
+	runtime.Object
+	metav1.Object
+}
+
+func NewForConfig(c *rest.Config, scheme *runtime.Scheme, owner string) (*ServerSideApplier, error) {
+	if scheme == nil {
+		scheme = runtime.NewScheme()
+		localSchemeBuilder := runtime.NewSchemeBuilder(
+			kscheme.AddToScheme,
+			apiextensionsv1.AddToScheme,
+			apiregistrationv1.AddToScheme,
+		)
+		if err := localSchemeBuilder.AddToScheme(scheme); err != nil {
+			return nil, err
+		}
+	}
+	restClient, err := k8scontrollerclient.New(c, k8scontrollerclient.Options{
+		Scheme: scheme,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	if len(owner) == 0 {
+		owner = defaultOwner
+	}
+
+	return &ServerSideApplier{
+		client: restClient,
+		Scheme: scheme,
+		Owner:  k8scontrollerclient.FieldOwner(owner),
+	}, nil
+}
+
+func SetDefaultGroupVersionKind(obj Object, s *runtime.Scheme) {
+	gvk := obj.GetObjectKind().GroupVersionKind()
+	if gvk.Empty() && s != nil {
+		// Best-effort guess the GVK
+		gvks, _, err := s.ObjectKinds(obj)
+		if err != nil {
+			panic(fmt.Sprintf("unable to get gvks for object %T: %s", obj, err))
+		}
+		if len(gvks) == 0 || gvks[0].Empty() {
+			panic(fmt.Sprintf("unexpected gvks registered for object %T: %v", obj, gvks))
+		}
+		// TODO: The same object can be registered for multiple group versions
+		// (although in practise this doesn't seem to be used).
+		// In such case, the version set may not be correct.
+		gvk = gvks[0]
+	}
+	obj.GetObjectKind().SetGroupVersionKind(gvk)
+}
+
+type ServerSideApplier struct {
+	client k8scontrollerclient.Client
+	Scheme *runtime.Scheme
+	// Owner becomes the Field Manager for whatever field the Server-Side apply acts on
+	Owner k8scontrollerclient.FieldOwner
+}
+
+// Apply returns a function that invokes a change func on an object and performs a server-side apply patch with the result and its status subresource.
+// The given resource must be a pointer to a struct that specifies its Name, Namespace, APIVersion, and Kind at minimum.
+// The given change function must be unary, matching the signature: "func(<obj type>) error".
+// The returned function is suitable for use w/ asyncronous assertions.
+// The underlying value of the given resource pointer is updated to reflect the latest cluster state each time the closure is successfully invoked.
+// Ex. Change the spec of an existing InstallPlan
+//
+// plan := &InstallPlan{}
+// plan.SetNamespace("ns")
+// plan.SetName("install-123def")
+// Eventually(c.Apply(plan, func(p *v1alpha1.InstallPlan) error {
+//		p.Spec.Approved = true
+//		return nil
+// })).Should(Succeed())
+func (c *ServerSideApplier) Apply(ctx context.Context, obj Object, changeFunc interface{}) func() error {
+	// Ensure given object is a pointer
+	objType := reflect.TypeOf(obj)
+	if objType.Kind() != reflect.Ptr {
+		panic(fmt.Sprintf("argument object must be a pointer"))
+	}
+
+	// Ensure given function matches expected signature
+	var (
+		change     = reflect.ValueOf(changeFunc)
+		changeType = change.Type()
+	)
+	if n := changeType.NumIn(); n != 1 {
+		panic(fmt.Sprintf("unexpected number of formal parameters in change function signature: expected 1, present %d", n))
+	}
+	if pt := changeType.In(0); pt.Kind() != reflect.Interface {
+		if objType != pt {
+			panic(fmt.Sprintf("argument object type does not match the change function parameter type: argument %s, parameter: %s", objType, pt))
+		}
+	} else if !objType.Implements(pt) {
+		panic(fmt.Sprintf("argument object type does not implement the change function parameter type: argument %s, parameter: %s", objType, pt))
+	}
+	if n := changeType.NumOut(); n != 1 {
+		panic(fmt.Sprintf("unexpected number of return values in change function signature: expected 1, present %d", n))
+	}
+	var err error
+	if rt := changeType.Out(0); !rt.Implements(reflect.TypeOf((*error)(nil)).Elem()) {
+		panic(fmt.Sprintf("unexpected return type in change function signature: expected %t, present %s", err, rt))
+	}
+
+	// Determine if we need to apply a status subresource
+	_, applyStatus := objType.Elem().FieldByName("Status")
+
+	if unstructuredObj, ok := obj.(*unstructured.Unstructured); ok {
+		_, applyStatus = unstructuredObj.Object["status"]
+	}
+
+	key, err := k8scontrollerclient.ObjectKeyFromObject(obj)
+	if err != nil {
+		panic(fmt.Sprintf("unable to extract key from resource: %s", err))
+	}
+
+	// Ensure the GVK is set before patching
+	SetDefaultGroupVersionKind(obj, c.Scheme)
+
+	return func() error {
+		changed := func(obj Object) (Object, error) {
+			cp := obj.DeepCopyObject().(Object)
+			if err := c.client.Get(ctx, key, cp); err != nil {
+				return nil, err
+			}
+			// Reset the GVK after the client call strips it
+			SetDefaultGroupVersionKind(cp, c.Scheme)
+			cp.SetManagedFields(nil)
+
+			out := change.Call([]reflect.Value{reflect.ValueOf(cp)})
+			if len(out) != 1 {
+				panic(fmt.Sprintf("unexpected number of return values from apply mutation func: expected 1, returned %d", len(out)))
+			}
+
+			if err := out[0].Interface(); err != nil {
+				return nil, err.(error)
+			}
+
+			return cp, nil
+		}
+
+		cp, err := changed(obj)
+		if err != nil {
+			return err
+		}
+
+		if len(c.Owner) == 0 {
+			c.Owner = defaultOwner
+		}
+
+		if err := c.client.Patch(ctx, cp, k8scontrollerclient.Apply, k8scontrollerclient.ForceOwnership, c.Owner); err != nil {
+			fmt.Printf("first patch error: %s\n", err)
+			return err
+		}
+
+		if !applyStatus {
+			reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(cp).Elem())
+			return nil
+		}
+
+		cp, err = changed(cp)
+		if err != nil {
+			return err
+		}
+
+		if err := c.client.Status().Patch(ctx, cp, k8scontrollerclient.Apply, k8scontrollerclient.ForceOwnership, c.Owner); err != nil {
+			fmt.Printf("second patch error: %s\n", err)
+			return err
+		}
+
+		reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(cp).Elem())
+
+		return nil
+	}
+}
diff --git a/vendor/golang.org/x/xerrors/LICENSE b/vendor/golang.org/x/xerrors/LICENSE
new file mode 100644
index 000000000..e4a47e17f
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2019 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/golang.org/x/xerrors/PATENTS b/vendor/golang.org/x/xerrors/PATENTS
new file mode 100644
index 000000000..733099041
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/PATENTS
@@ -0,0 +1,22 @@
+Additional IP Rights Grant (Patents)
+
+"This implementation" means the copyrightable works distributed by
+Google as part of the Go project.
+
+Google hereby grants to You a perpetual, worldwide, non-exclusive,
+no-charge, royalty-free, irrevocable (except as stated in this section)
+patent license to make, have made, use, offer to sell, sell, import,
+transfer and otherwise run, modify and propagate the contents of this
+implementation of Go, where such license applies only to those patent
+claims, both currently owned or controlled by Google and acquired in
+the future, licensable by Google that are necessarily infringed by this
+implementation of Go.  This grant does not include claims that would be
+infringed only as a consequence of further modification of this
+implementation.  If you or your agent or exclusive licensee institute or
+order or agree to the institution of patent litigation against any
+entity (including a cross-claim or counterclaim in a lawsuit) alleging
+that this implementation of Go or any code incorporated within this
+implementation of Go constitutes direct or contributory patent
+infringement, or inducement of patent infringement, then any patent
+rights granted to you under this License for this implementation of Go
+shall terminate as of the date such litigation is filed.
diff --git a/vendor/golang.org/x/xerrors/README b/vendor/golang.org/x/xerrors/README
new file mode 100644
index 000000000..aac7867a5
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/README
@@ -0,0 +1,2 @@
+This repository holds the transition packages for the new Go 1.13 error values.
+See golang.org/design/29934-error-values.
diff --git a/vendor/golang.org/x/xerrors/adaptor.go b/vendor/golang.org/x/xerrors/adaptor.go
new file mode 100644
index 000000000..4317f2483
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/adaptor.go
@@ -0,0 +1,193 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xerrors
+
+import (
+	"bytes"
+	"fmt"
+	"io"
+	"reflect"
+	"strconv"
+)
+
+// FormatError calls the FormatError method of f with an errors.Printer
+// configured according to s and verb, and writes the result to s.
+func FormatError(f Formatter, s fmt.State, verb rune) {
+	// Assuming this function is only called from the Format method, and given
+	// that FormatError takes precedence over Format, it cannot be called from
+	// any package that supports errors.Formatter. It is therefore safe to
+	// disregard that State may be a specific printer implementation and use one
+	// of our choice instead.
+
+	// limitations: does not support printing error as Go struct.
+
+	var (
+		sep    = " " // separator before next error
+		p      = &state{State: s}
+		direct = true
+	)
+
+	var err error = f
+
+	switch verb {
+	// Note that this switch must match the preference order
+	// for ordinary string printing (%#v before %+v, and so on).
+
+	case 'v':
+		if s.Flag('#') {
+			if stringer, ok := err.(fmt.GoStringer); ok {
+				io.WriteString(&p.buf, stringer.GoString())
+				goto exit
+			}
+			// proceed as if it were %v
+		} else if s.Flag('+') {
+			p.printDetail = true
+			sep = "\n  - "
+		}
+	case 's':
+	case 'q', 'x', 'X':
+		// Use an intermediate buffer in the rare cases that precision,
+		// truncation, or one of the alternative verbs (q, x, and X) are
+		// specified.
+		direct = false
+
+	default:
+		p.buf.WriteString("%!")
+		p.buf.WriteRune(verb)
+		p.buf.WriteByte('(')
+		switch {
+		case err != nil:
+			p.buf.WriteString(reflect.TypeOf(f).String())
+		default:
+			p.buf.WriteString("<nil>")
+		}
+		p.buf.WriteByte(')')
+		io.Copy(s, &p.buf)
+		return
+	}
+
+loop:
+	for {
+		switch v := err.(type) {
+		case Formatter:
+			err = v.FormatError((*printer)(p))
+		case fmt.Formatter:
+			v.Format(p, 'v')
+			break loop
+		default:
+			io.WriteString(&p.buf, v.Error())
+			break loop
+		}
+		if err == nil {
+			break
+		}
+		if p.needColon || !p.printDetail {
+			p.buf.WriteByte(':')
+			p.needColon = false
+		}
+		p.buf.WriteString(sep)
+		p.inDetail = false
+		p.needNewline = false
+	}
+
+exit:
+	width, okW := s.Width()
+	prec, okP := s.Precision()
+
+	if !direct || (okW && width > 0) || okP {
+		// Construct format string from State s.
+		format := []byte{'%'}
+		if s.Flag('-') {
+			format = append(format, '-')
+		}
+		if s.Flag('+') {
+			format = append(format, '+')
+		}
+		if s.Flag(' ') {
+			format = append(format, ' ')
+		}
+		if okW {
+			format = strconv.AppendInt(format, int64(width), 10)
+		}
+		if okP {
+			format = append(format, '.')
+			format = strconv.AppendInt(format, int64(prec), 10)
+		}
+		format = append(format, string(verb)...)
+		fmt.Fprintf(s, string(format), p.buf.String())
+	} else {
+		io.Copy(s, &p.buf)
+	}
+}
+
+var detailSep = []byte("\n    ")
+
+// state tracks error printing state. It implements fmt.State.
+type state struct {
+	fmt.State
+	buf bytes.Buffer
+
+	printDetail bool
+	inDetail    bool
+	needColon   bool
+	needNewline bool
+}
+
+func (s *state) Write(b []byte) (n int, err error) {
+	if s.printDetail {
+		if len(b) == 0 {
+			return 0, nil
+		}
+		if s.inDetail && s.needColon {
+			s.needNewline = true
+			if b[0] == '\n' {
+				b = b[1:]
+			}
+		}
+		k := 0
+		for i, c := range b {
+			if s.needNewline {
+				if s.inDetail && s.needColon {
+					s.buf.WriteByte(':')
+					s.needColon = false
+				}
+				s.buf.Write(detailSep)
+				s.needNewline = false
+			}
+			if c == '\n' {
+				s.buf.Write(b[k:i])
+				k = i + 1
+				s.needNewline = true
+			}
+		}
+		s.buf.Write(b[k:])
+		if !s.inDetail {
+			s.needColon = true
+		}
+	} else if !s.inDetail {
+		s.buf.Write(b)
+	}
+	return len(b), nil
+}
+
+// printer wraps a state to implement an xerrors.Printer.
+type printer state
+
+func (s *printer) Print(args ...interface{}) {
+	if !s.inDetail || s.printDetail {
+		fmt.Fprint((*state)(s), args...)
+	}
+}
+
+func (s *printer) Printf(format string, args ...interface{}) {
+	if !s.inDetail || s.printDetail {
+		fmt.Fprintf((*state)(s), format, args...)
+	}
+}
+
+func (s *printer) Detail() bool {
+	s.inDetail = true
+	return s.printDetail
+}
diff --git a/vendor/golang.org/x/xerrors/codereview.cfg b/vendor/golang.org/x/xerrors/codereview.cfg
new file mode 100644
index 000000000..3f8b14b64
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/codereview.cfg
@@ -0,0 +1,1 @@
+issuerepo: golang/go
diff --git a/vendor/golang.org/x/xerrors/doc.go b/vendor/golang.org/x/xerrors/doc.go
new file mode 100644
index 000000000..1ad48f50b
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/doc.go
@@ -0,0 +1,25 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package xerrors implements functions to manipulate errors.
+//
+// This package supports transitioning to the Go 2 proposal for error values:
+//   https://golang.org/design/29934-error-values
+//
+// Most of the functions and types in this package will be incorporated into the
+// standard library's errors package in Go 1.13; the behavior of this package's
+// Errorf function will be incorporated into the standard library's fmt.Errorf.
+// Use this package to get equivalent behavior in all supported Go versions. For
+// example, create errors using
+//
+//    xerrors.New("write failed")
+//
+// or
+//
+//    xerrors.Errorf("while reading: %v", err)
+//
+// If you want your error type to participate in the new formatting
+// implementation for %v and %+v, provide it with a Format method that calls
+// xerrors.FormatError, as shown in the example for FormatError.
+package xerrors // import "golang.org/x/xerrors"
diff --git a/vendor/golang.org/x/xerrors/errors.go b/vendor/golang.org/x/xerrors/errors.go
new file mode 100644
index 000000000..e88d3772d
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/errors.go
@@ -0,0 +1,33 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xerrors
+
+import "fmt"
+
+// errorString is a trivial implementation of error.
+type errorString struct {
+	s     string
+	frame Frame
+}
+
+// New returns an error that formats as the given text.
+//
+// The returned error contains a Frame set to the caller's location and
+// implements Formatter to show this information when printed with details.
+func New(text string) error {
+	return &errorString{text, Caller(1)}
+}
+
+func (e *errorString) Error() string {
+	return e.s
+}
+
+func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) }
+
+func (e *errorString) FormatError(p Printer) (next error) {
+	p.Print(e.s)
+	e.frame.Format(p)
+	return nil
+}
diff --git a/vendor/golang.org/x/xerrors/fmt.go b/vendor/golang.org/x/xerrors/fmt.go
new file mode 100644
index 000000000..74c1c93ec
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/fmt.go
@@ -0,0 +1,109 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xerrors
+
+import (
+	"fmt"
+	"strings"
+
+	"golang.org/x/xerrors/internal"
+)
+
+// Errorf formats according to a format specifier and returns the string as a
+// value that satisfies error.
+//
+// The returned error includes the file and line number of the caller when
+// formatted with additional detail enabled. If the last argument is an error
+// the returned error's Format method will return it if the format string ends
+// with ": %s", ": %v", or ": %w". If the last argument is an error and the
+// format string ends with ": %w", the returned error implements Wrapper
+// with an Unwrap method returning it.
+func Errorf(format string, a ...interface{}) error {
+	err, wrap := lastError(format, a)
+	format = formatPlusW(format)
+	if err == nil {
+		return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)}
+	}
+
+	// TODO: this is not entirely correct. The error value could be
+	// printed elsewhere in format if it mixes numbered with unnumbered
+	// substitutions. With relatively small changes to doPrintf we can
+	// have it optionally ignore extra arguments and pass the argument
+	// list in its entirety.
+	msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
+	frame := Frame{}
+	if internal.EnableTrace {
+		frame = Caller(1)
+	}
+	if wrap {
+		return &wrapError{msg, err, frame}
+	}
+	return &noWrapError{msg, err, frame}
+}
+
+// formatPlusW is used to avoid the vet check that will barf at %w.
+func formatPlusW(s string) string {
+	return s
+}
+
+func lastError(format string, a []interface{}) (err error, wrap bool) {
+	wrap = strings.HasSuffix(format, ": %w")
+	if !wrap &&
+		!strings.HasSuffix(format, ": %s") &&
+		!strings.HasSuffix(format, ": %v") {
+		return nil, false
+	}
+
+	if len(a) == 0 {
+		return nil, false
+	}
+
+	err, ok := a[len(a)-1].(error)
+	if !ok {
+		return nil, false
+	}
+
+	return err, wrap
+}
+
+type noWrapError struct {
+	msg   string
+	err   error
+	frame Frame
+}
+
+func (e *noWrapError) Error() string {
+	return fmt.Sprint(e)
+}
+
+func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
+
+func (e *noWrapError) FormatError(p Printer) (next error) {
+	p.Print(e.msg)
+	e.frame.Format(p)
+	return e.err
+}
+
+type wrapError struct {
+	msg   string
+	err   error
+	frame Frame
+}
+
+func (e *wrapError) Error() string {
+	return fmt.Sprint(e)
+}
+
+func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
+
+func (e *wrapError) FormatError(p Printer) (next error) {
+	p.Print(e.msg)
+	e.frame.Format(p)
+	return e.err
+}
+
+func (e *wrapError) Unwrap() error {
+	return e.err
+}
diff --git a/vendor/golang.org/x/xerrors/format.go b/vendor/golang.org/x/xerrors/format.go
new file mode 100644
index 000000000..1bc9c26b9
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/format.go
@@ -0,0 +1,34 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xerrors
+
+// A Formatter formats error messages.
+type Formatter interface {
+	error
+
+	// FormatError prints the receiver's first error and returns the next error in
+	// the error chain, if any.
+	FormatError(p Printer) (next error)
+}
+
+// A Printer formats error messages.
+//
+// The most common implementation of Printer is the one provided by package fmt
+// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message
+// typically provide their own implementations.
+type Printer interface {
+	// Print appends args to the message output.
+	Print(args ...interface{})
+
+	// Printf writes a formatted string.
+	Printf(format string, args ...interface{})
+
+	// Detail reports whether error detail is requested.
+	// After the first call to Detail, all text written to the Printer
+	// is formatted as additional detail, or ignored when
+	// detail has not been requested.
+	// If Detail returns false, the caller can avoid printing the detail at all.
+	Detail() bool
+}
diff --git a/vendor/golang.org/x/xerrors/frame.go b/vendor/golang.org/x/xerrors/frame.go
new file mode 100644
index 000000000..0de628ec5
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/frame.go
@@ -0,0 +1,56 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xerrors
+
+import (
+	"runtime"
+)
+
+// A Frame contains part of a call stack.
+type Frame struct {
+	// Make room for three PCs: the one we were asked for, what it called,
+	// and possibly a PC for skipPleaseUseCallersFrames. See:
+	// https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169
+	frames [3]uintptr
+}
+
+// Caller returns a Frame that describes a frame on the caller's stack.
+// The argument skip is the number of frames to skip over.
+// Caller(0) returns the frame for the caller of Caller.
+func Caller(skip int) Frame {
+	var s Frame
+	runtime.Callers(skip+1, s.frames[:])
+	return s
+}
+
+// location reports the file, line, and function of a frame.
+//
+// The returned function may be "" even if file and line are not.
+func (f Frame) location() (function, file string, line int) {
+	frames := runtime.CallersFrames(f.frames[:])
+	if _, ok := frames.Next(); !ok {
+		return "", "", 0
+	}
+	fr, ok := frames.Next()
+	if !ok {
+		return "", "", 0
+	}
+	return fr.Function, fr.File, fr.Line
+}
+
+// Format prints the stack as error detail.
+// It should be called from an error's Format implementation
+// after printing any other error detail.
+func (f Frame) Format(p Printer) {
+	if p.Detail() {
+		function, file, line := f.location()
+		if function != "" {
+			p.Printf("%s\n    ", function)
+		}
+		if file != "" {
+			p.Printf("%s:%d\n", file, line)
+		}
+	}
+}
diff --git a/vendor/golang.org/x/xerrors/go.mod b/vendor/golang.org/x/xerrors/go.mod
new file mode 100644
index 000000000..870d4f612
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/go.mod
@@ -0,0 +1,3 @@
+module golang.org/x/xerrors
+
+go 1.11
diff --git a/vendor/golang.org/x/xerrors/internal/internal.go b/vendor/golang.org/x/xerrors/internal/internal.go
new file mode 100644
index 000000000..89f4eca5d
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/internal/internal.go
@@ -0,0 +1,8 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+// EnableTrace indicates whether stack information should be recorded in errors.
+var EnableTrace = true
diff --git a/vendor/golang.org/x/xerrors/wrap.go b/vendor/golang.org/x/xerrors/wrap.go
new file mode 100644
index 000000000..9a3b51037
--- /dev/null
+++ b/vendor/golang.org/x/xerrors/wrap.go
@@ -0,0 +1,106 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xerrors
+
+import (
+	"reflect"
+)
+
+// A Wrapper provides context around another error.
+type Wrapper interface {
+	// Unwrap returns the next error in the error chain.
+	// If there is no next error, Unwrap returns nil.
+	Unwrap() error
+}
+
+// Opaque returns an error with the same error formatting as err
+// but that does not match err and cannot be unwrapped.
+func Opaque(err error) error {
+	return noWrapper{err}
+}
+
+type noWrapper struct {
+	error
+}
+
+func (e noWrapper) FormatError(p Printer) (next error) {
+	if f, ok := e.error.(Formatter); ok {
+		return f.FormatError(p)
+	}
+	p.Print(e.error)
+	return nil
+}
+
+// Unwrap returns the result of calling the Unwrap method on err, if err implements
+// Unwrap. Otherwise, Unwrap returns nil.
+func Unwrap(err error) error {
+	u, ok := err.(Wrapper)
+	if !ok {
+		return nil
+	}
+	return u.Unwrap()
+}
+
+// Is reports whether any error in err's chain matches target.
+//
+// An error is considered to match a target if it is equal to that target or if
+// it implements a method Is(error) bool such that Is(target) returns true.
+func Is(err, target error) bool {
+	if target == nil {
+		return err == target
+	}
+
+	isComparable := reflect.TypeOf(target).Comparable()
+	for {
+		if isComparable && err == target {
+			return true
+		}
+		if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
+			return true
+		}
+		// TODO: consider supporing target.Is(err). This would allow
+		// user-definable predicates, but also may allow for coping with sloppy
+		// APIs, thereby making it easier to get away with them.
+		if err = Unwrap(err); err == nil {
+			return false
+		}
+	}
+}
+
+// As finds the first error in err's chain that matches the type to which target
+// points, and if so, sets the target to its value and returns true. An error
+// matches a type if it is assignable to the target type, or if it has a method
+// As(interface{}) bool such that As(target) returns true. As will panic if target
+// is not a non-nil pointer to a type which implements error or is of interface type.
+//
+// The As method should set the target to its value and return true if err
+// matches the type to which target points.
+func As(err error, target interface{}) bool {
+	if target == nil {
+		panic("errors: target cannot be nil")
+	}
+	val := reflect.ValueOf(target)
+	typ := val.Type()
+	if typ.Kind() != reflect.Ptr || val.IsNil() {
+		panic("errors: target must be a non-nil pointer")
+	}
+	if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) {
+		panic("errors: *target must be interface or implement error")
+	}
+	targetType := typ.Elem()
+	for err != nil {
+		if reflect.TypeOf(err).AssignableTo(targetType) {
+			val.Elem().Set(reflect.ValueOf(err))
+			return true
+		}
+		if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
+			return true
+		}
+		err = Unwrap(err)
+	}
+	return false
+}
+
+var errorType = reflect.TypeOf((*error)(nil)).Elem()
diff --git a/vendor/modules.txt b/vendor/modules.txt
index c1fe2fe7f..3591250ec 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -463,7 +463,7 @@ golang.org/x/oauth2/google
 golang.org/x/oauth2/internal
 golang.org/x/oauth2/jws
 golang.org/x/oauth2/jwt
-# golang.org/x/sync v0.0.0-20190423024810-112230192c58
+# golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
 golang.org/x/sync/errgroup
 golang.org/x/sync/semaphore
 # golang.org/x/sys v0.0.0-20191210023423-ac6580df4449
@@ -498,6 +498,9 @@ golang.org/x/tools/internal/imports
 golang.org/x/tools/internal/module
 golang.org/x/tools/internal/semver
 golang.org/x/tools/internal/span
+# golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
+golang.org/x/xerrors
+golang.org/x/xerrors/internal
 # gonum.org/v1/gonum v0.0.0-20190710053202-4340aa3071a0
 gonum.org/v1/gonum/blas
 gonum.org/v1/gonum/blas/blas64
@@ -1201,6 +1204,11 @@ k8s.io/utils/integer
 k8s.io/utils/path
 k8s.io/utils/pointer
 k8s.io/utils/trace
+# sigs.k8s.io/controller-runtime v0.4.0
+sigs.k8s.io/controller-runtime/pkg/client
+sigs.k8s.io/controller-runtime/pkg/client/apiutil
+sigs.k8s.io/controller-runtime/pkg/client/fake
+sigs.k8s.io/controller-runtime/pkg/internal/objectutil
 # sigs.k8s.io/controller-tools v0.2.4
 sigs.k8s.io/controller-tools/cmd/controller-gen
 sigs.k8s.io/controller-tools/pkg/crd
diff --git a/vendor/sigs.k8s.io/controller-runtime/LICENSE b/vendor/sigs.k8s.io/controller-runtime/LICENSE
new file mode 100644
index 000000000..8dada3eda
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/LICENSE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "{}"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright {yyyy} {name of copyright owner}
+
+   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
+
+       http://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.
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go
new file mode 100644
index 000000000..157f11f7c
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/apimachinery.go
@@ -0,0 +1,97 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 apiutil contains utilities for working with raw Kubernetes
+// API machinery, such as creating RESTMappers and raw REST clients,
+// and extracting the GVK of an object.
+package apiutil
+
+import (
+	"fmt"
+
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/client-go/discovery"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/restmapper"
+)
+
+// NewDiscoveryRESTMapper constructs a new RESTMapper based on discovery
+// information fetched by a new client with the given config.
+func NewDiscoveryRESTMapper(c *rest.Config) (meta.RESTMapper, error) {
+	// Get a mapper
+	dc, err := discovery.NewDiscoveryClientForConfig(c)
+	if err != nil {
+		return nil, err
+	}
+	gr, err := restmapper.GetAPIGroupResources(dc)
+	if err != nil {
+		return nil, err
+	}
+	return restmapper.NewDiscoveryRESTMapper(gr), nil
+}
+
+// GVKForObject finds the GroupVersionKind associated with the given object, if there is only a single such GVK.
+func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error) {
+	gvks, isUnversioned, err := scheme.ObjectKinds(obj)
+	if err != nil {
+		return schema.GroupVersionKind{}, err
+	}
+	if isUnversioned {
+		return schema.GroupVersionKind{}, fmt.Errorf("cannot create a new informer for the unversioned type %T", obj)
+	}
+
+	if len(gvks) < 1 {
+		return schema.GroupVersionKind{}, fmt.Errorf("no group-version-kinds associated with type %T", obj)
+	}
+	if len(gvks) > 1 {
+		// this should only trigger for things like metav1.XYZ --
+		// normal versioned types should be fine
+		return schema.GroupVersionKind{}, fmt.Errorf(
+			"multiple group-version-kinds associated with type %T, refusing to guess at one", obj)
+	}
+	return gvks[0], nil
+}
+
+// RESTClientForGVK constructs a new rest.Interface capable of accessing the resource associated
+// with the given GroupVersionKind. The REST client will be configured to use the negotiated serializer from
+// baseConfig, if set, otherwise a default serializer will be set.
+func RESTClientForGVK(gvk schema.GroupVersionKind, baseConfig *rest.Config, codecs serializer.CodecFactory) (rest.Interface, error) {
+	cfg := createRestConfig(gvk, baseConfig)
+	if cfg.NegotiatedSerializer == nil {
+		cfg.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: codecs}
+	}
+	return rest.RESTClientFor(cfg)
+}
+
+//createRestConfig copies the base config and updates needed fields for a new rest config
+func createRestConfig(gvk schema.GroupVersionKind, baseConfig *rest.Config) *rest.Config {
+	gv := gvk.GroupVersion()
+
+	cfg := rest.CopyConfig(baseConfig)
+	cfg.GroupVersion = &gv
+	if gvk.Group == "" {
+		cfg.APIPath = "/api"
+	} else {
+		cfg.APIPath = "/apis"
+	}
+	if cfg.UserAgent == "" {
+		cfg.UserAgent = rest.DefaultKubernetesUserAgent()
+	}
+	return cfg
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/dynamicrestmapper.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/dynamicrestmapper.go
new file mode 100644
index 000000000..4588fb06c
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/apiutil/dynamicrestmapper.go
@@ -0,0 +1,322 @@
+/*
+Copyright 2019 The Kubernetes Authors.
+
+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
+
+    http://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 apiutil
+
+import (
+	"sync"
+	"time"
+
+	"golang.org/x/time/rate"
+	"golang.org/x/xerrors"
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/discovery"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/restmapper"
+)
+
+// ErrRateLimited is returned by a RESTMapper method if the number of API
+// calls has exceeded a limit within a certain time period.
+type ErrRateLimited struct {
+	// Duration to wait until the next API call can be made.
+	Delay time.Duration
+}
+
+func (e ErrRateLimited) Error() string {
+	return "too many API calls to the RESTMapper within a timeframe"
+}
+
+// DelayIfRateLimited returns the delay time until the next API call is
+// allowed and true if err is of type ErrRateLimited. The zero
+// time.Duration value and false are returned if err is not a ErrRateLimited.
+func DelayIfRateLimited(err error) (time.Duration, bool) {
+	var rlerr ErrRateLimited
+	if xerrors.As(err, &rlerr) {
+		return rlerr.Delay, true
+	}
+	return 0, false
+}
+
+// dynamicRESTMapper is a RESTMapper that dynamically discovers resource
+// types at runtime.
+type dynamicRESTMapper struct {
+	mu           sync.RWMutex // protects the following fields
+	staticMapper meta.RESTMapper
+	limiter      *dynamicLimiter
+	newMapper    func() (meta.RESTMapper, error)
+
+	lazy bool
+	// Used for lazy init.
+	initOnce sync.Once
+}
+
+// DynamicRESTMapperOption is a functional option on the dynamicRESTMapper
+type DynamicRESTMapperOption func(*dynamicRESTMapper) error
+
+// WithLimiter sets the RESTMapper's underlying limiter to lim.
+func WithLimiter(lim *rate.Limiter) DynamicRESTMapperOption {
+	return func(drm *dynamicRESTMapper) error {
+		drm.limiter = &dynamicLimiter{lim}
+		return nil
+	}
+}
+
+// WithLazyDiscovery prevents the RESTMapper from discovering REST mappings
+// until an API call is made.
+var WithLazyDiscovery DynamicRESTMapperOption = func(drm *dynamicRESTMapper) error {
+	drm.lazy = true
+	return nil
+}
+
+// WithCustomMapper supports setting a custom RESTMapper refresher instead of
+// the default method, which uses a discovery client.
+//
+// This exists mainly for testing, but can be useful if you need tighter control
+// over how discovery is performed, which discovery endpoints are queried, etc.
+func WithCustomMapper(newMapper func() (meta.RESTMapper, error)) DynamicRESTMapperOption {
+	return func(drm *dynamicRESTMapper) error {
+		drm.newMapper = newMapper
+		return nil
+	}
+}
+
+// NewDynamicRESTMapper returns a dynamic RESTMapper for cfg. The dynamic
+// RESTMapper dynamically discovers resource types at runtime. opts
+// configure the RESTMapper.
+func NewDynamicRESTMapper(cfg *rest.Config, opts ...DynamicRESTMapperOption) (meta.RESTMapper, error) {
+	client, err := discovery.NewDiscoveryClientForConfig(cfg)
+	if err != nil {
+		return nil, err
+	}
+	drm := &dynamicRESTMapper{
+		limiter: &dynamicLimiter{
+			rate.NewLimiter(rate.Limit(defaultRefillRate), defaultLimitSize),
+		},
+		newMapper: func() (meta.RESTMapper, error) {
+			groupResources, err := restmapper.GetAPIGroupResources(client)
+			if err != nil {
+				return nil, err
+			}
+			return restmapper.NewDiscoveryRESTMapper(groupResources), nil
+		},
+	}
+	for _, opt := range opts {
+		if err = opt(drm); err != nil {
+			return nil, err
+		}
+	}
+	if !drm.lazy {
+		if err := drm.setStaticMapper(); err != nil {
+			return nil, err
+		}
+	}
+	return drm, nil
+}
+
+var (
+	// defaultRefilRate is the default rate at which potential calls are
+	// added back to the "bucket" of allowed calls.
+	defaultRefillRate = 5
+	// defaultLimitSize is the default starting/max number of potential calls
+	// per second.  Once a call is used, it's added back to the bucket at a rate
+	// of defaultRefillRate per second.
+	defaultLimitSize = 5
+)
+
+// setStaticMapper sets drm's staticMapper by querying its client, regardless
+// of reload backoff.
+func (drm *dynamicRESTMapper) setStaticMapper() error {
+	newMapper, err := drm.newMapper()
+	if err != nil {
+		return err
+	}
+	drm.staticMapper = newMapper
+	return nil
+}
+
+// init initializes drm only once if drm is lazy.
+func (drm *dynamicRESTMapper) init() (err error) {
+	drm.initOnce.Do(func() {
+		if drm.lazy {
+			err = drm.setStaticMapper()
+		}
+	})
+	return err
+}
+
+// checkAndReload attempts to call the given callback, which is assumed to be dependent
+// on the data in the restmapper.
+//
+// If the callback returns a NoKindMatchError, it will attempt to reload
+// the RESTMapper's data and re-call the callback once that's occurred.
+// If the callback returns any other error, the function will return immediately regardless.
+//
+// It will take care
+// ensuring that reloads are rate-limitted and that extraneous calls aren't made.
+// It's thread-safe, and worries about thread-safety for the callback (so the callback does
+// not need to attempt to lock the restmapper).
+func (drm *dynamicRESTMapper) checkAndReload(needsReloadErr error, checkNeedsReload func() error) error {
+	// first, check the common path -- data is fresh enough
+	// (use an IIFE for the lock's defer)
+	err := func() error {
+		drm.mu.RLock()
+		defer drm.mu.RUnlock()
+
+		return checkNeedsReload()
+	}()
+
+	// NB(directxman12): `Is` and `As` have a confusing relationship --
+	// `Is` is like `== or does this implement .Is`, whereas `As` says
+	// `can I type-assert into`
+	needsReload := xerrors.As(err, &needsReloadErr)
+	if !needsReload {
+		return err
+	}
+
+	// if the data wasn't fresh, we'll need to try and update it, so grab the lock...
+	drm.mu.Lock()
+	defer drm.mu.Unlock()
+
+	// ... and double-check that we didn't reload in the meantime
+	err = checkNeedsReload()
+	needsReload = xerrors.As(err, &needsReloadErr)
+	if !needsReload {
+		return err
+	}
+
+	// we're still stale, so grab a rate-limit token if we can...
+	if err := drm.limiter.checkRate(); err != nil {
+		return err
+	}
+
+	// ...reload...
+	if err := drm.setStaticMapper(); err != nil {
+		return err
+	}
+
+	// ...and return the results of the closure regardless
+	return checkNeedsReload()
+}
+
+// TODO: wrap reload errors on NoKindMatchError with go 1.13 errors.
+
+func (drm *dynamicRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
+	if err := drm.init(); err != nil {
+		return schema.GroupVersionKind{}, err
+	}
+	var gvk schema.GroupVersionKind
+	err := drm.checkAndReload(&meta.NoResourceMatchError{}, func() error {
+		var err error
+		gvk, err = drm.staticMapper.KindFor(resource)
+		return err
+	})
+	return gvk, err
+}
+
+func (drm *dynamicRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
+	if err := drm.init(); err != nil {
+		return nil, err
+	}
+	var gvks []schema.GroupVersionKind
+	err := drm.checkAndReload(&meta.NoResourceMatchError{}, func() error {
+		var err error
+		gvks, err = drm.staticMapper.KindsFor(resource)
+		return err
+	})
+	return gvks, err
+}
+
+func (drm *dynamicRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) {
+	if err := drm.init(); err != nil {
+		return schema.GroupVersionResource{}, err
+	}
+
+	var gvr schema.GroupVersionResource
+	err := drm.checkAndReload(&meta.NoResourceMatchError{}, func() error {
+		var err error
+		gvr, err = drm.staticMapper.ResourceFor(input)
+		return err
+	})
+	return gvr, err
+}
+
+func (drm *dynamicRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
+	if err := drm.init(); err != nil {
+		return nil, err
+	}
+	var gvrs []schema.GroupVersionResource
+	err := drm.checkAndReload(&meta.NoResourceMatchError{}, func() error {
+		var err error
+		gvrs, err = drm.staticMapper.ResourcesFor(input)
+		return err
+	})
+	return gvrs, err
+}
+
+func (drm *dynamicRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
+	if err := drm.init(); err != nil {
+		return nil, err
+	}
+	var mapping *meta.RESTMapping
+	err := drm.checkAndReload(&meta.NoKindMatchError{}, func() error {
+		var err error
+		mapping, err = drm.staticMapper.RESTMapping(gk, versions...)
+		return err
+	})
+	return mapping, err
+}
+
+func (drm *dynamicRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
+	if err := drm.init(); err != nil {
+		return nil, err
+	}
+	var mappings []*meta.RESTMapping
+	err := drm.checkAndReload(&meta.NoKindMatchError{}, func() error {
+		var err error
+		mappings, err = drm.staticMapper.RESTMappings(gk, versions...)
+		return err
+	})
+	return mappings, err
+}
+
+func (drm *dynamicRESTMapper) ResourceSingularizer(resource string) (string, error) {
+	if err := drm.init(); err != nil {
+		return "", err
+	}
+	var singular string
+	err := drm.checkAndReload(&meta.NoResourceMatchError{}, func() error {
+		var err error
+		singular, err = drm.staticMapper.ResourceSingularizer(resource)
+		return err
+	})
+	return singular, err
+}
+
+// dynamicLimiter holds a rate limiter used to throttle chatty RESTMapper users.
+type dynamicLimiter struct {
+	*rate.Limiter
+}
+
+// checkRate returns an ErrRateLimited if too many API calls have been made
+// within the set limit.
+func (b *dynamicLimiter) checkRate() error {
+	res := b.Reserve()
+	if res.Delay() == 0 {
+		return nil
+	}
+	return ErrRateLimited{res.Delay()}
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go
new file mode 100644
index 000000000..a3a0ddc98
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client.go
@@ -0,0 +1,213 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 client
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/client-go/dynamic"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/rest"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+)
+
+// Options are creation options for a Client
+type Options struct {
+	// Scheme, if provided, will be used to map go structs to GroupVersionKinds
+	Scheme *runtime.Scheme
+
+	// Mapper, if provided, will be used to map GroupVersionKinds to Resources
+	Mapper meta.RESTMapper
+}
+
+// New returns a new Client using the provided config and Options.
+// The returned client reads *and* writes directly from the server
+// (it doesn't use object caches).  It understands how to work with
+// normal types (both custom resources and aggregated/built-in resources),
+// as well as unstructured types.
+//
+// In the case of normal types, the scheme will be used to look up the
+// corresponding group, version, and kind for the given type.  In the
+// case of unstructured types, the group, version, and kind will be extracted
+// from the corresponding fields on the object.
+func New(config *rest.Config, options Options) (Client, error) {
+	if config == nil {
+		return nil, fmt.Errorf("must provide non-nil rest.Config to client.New")
+	}
+
+	// Init a scheme if none provided
+	if options.Scheme == nil {
+		options.Scheme = scheme.Scheme
+	}
+
+	// Init a Mapper if none provided
+	if options.Mapper == nil {
+		var err error
+		options.Mapper, err = apiutil.NewDiscoveryRESTMapper(config)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	dynamicClient, err := dynamic.NewForConfig(config)
+	if err != nil {
+		return nil, err
+	}
+
+	c := &client{
+		typedClient: typedClient{
+			cache: clientCache{
+				config:         config,
+				scheme:         options.Scheme,
+				mapper:         options.Mapper,
+				codecs:         serializer.NewCodecFactory(options.Scheme),
+				resourceByType: make(map[reflect.Type]*resourceMeta),
+			},
+			paramCodec: runtime.NewParameterCodec(options.Scheme),
+		},
+		unstructuredClient: unstructuredClient{
+			client:     dynamicClient,
+			restMapper: options.Mapper,
+		},
+	}
+
+	return c, nil
+}
+
+var _ Client = &client{}
+
+// client is a client.Client that reads and writes directly from/to an API server.  It lazily initializes
+// new clients at the time they are used, and caches the client.
+type client struct {
+	typedClient        typedClient
+	unstructuredClient unstructuredClient
+}
+
+// resetGroupVersionKind is a helper function to restore and preserve GroupVersionKind on an object.
+// TODO(vincepri): Remove this function and its calls once controller-runtime dependencies are upgraded to 1.16?
+func (c *client) resetGroupVersionKind(obj runtime.Object, gvk schema.GroupVersionKind) {
+	if gvk != schema.EmptyObjectKind.GroupVersionKind() {
+		if v, ok := obj.(schema.ObjectKind); ok {
+			v.SetGroupVersionKind(gvk)
+		}
+	}
+}
+
+// Create implements client.Client
+func (c *client) Create(ctx context.Context, obj runtime.Object, opts ...CreateOption) error {
+	_, ok := obj.(*unstructured.Unstructured)
+	if ok {
+		return c.unstructuredClient.Create(ctx, obj, opts...)
+	}
+	return c.typedClient.Create(ctx, obj, opts...)
+}
+
+// Update implements client.Client
+func (c *client) Update(ctx context.Context, obj runtime.Object, opts ...UpdateOption) error {
+	defer c.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
+	_, ok := obj.(*unstructured.Unstructured)
+	if ok {
+		return c.unstructuredClient.Update(ctx, obj, opts...)
+	}
+	return c.typedClient.Update(ctx, obj, opts...)
+}
+
+// Delete implements client.Client
+func (c *client) Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOption) error {
+	_, ok := obj.(*unstructured.Unstructured)
+	if ok {
+		return c.unstructuredClient.Delete(ctx, obj, opts...)
+	}
+	return c.typedClient.Delete(ctx, obj, opts...)
+}
+
+// DeleteAllOf implements client.Client
+func (c *client) DeleteAllOf(ctx context.Context, obj runtime.Object, opts ...DeleteAllOfOption) error {
+	_, ok := obj.(*unstructured.Unstructured)
+	if ok {
+		return c.unstructuredClient.DeleteAllOf(ctx, obj, opts...)
+	}
+	return c.typedClient.DeleteAllOf(ctx, obj, opts...)
+}
+
+// Patch implements client.Client
+func (c *client) Patch(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOption) error {
+	defer c.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
+	_, ok := obj.(*unstructured.Unstructured)
+	if ok {
+		return c.unstructuredClient.Patch(ctx, obj, patch, opts...)
+	}
+	return c.typedClient.Patch(ctx, obj, patch, opts...)
+}
+
+// Get implements client.Client
+func (c *client) Get(ctx context.Context, key ObjectKey, obj runtime.Object) error {
+	_, ok := obj.(*unstructured.Unstructured)
+	if ok {
+		return c.unstructuredClient.Get(ctx, key, obj)
+	}
+	return c.typedClient.Get(ctx, key, obj)
+}
+
+// List implements client.Client
+func (c *client) List(ctx context.Context, obj runtime.Object, opts ...ListOption) error {
+	_, ok := obj.(*unstructured.UnstructuredList)
+	if ok {
+		return c.unstructuredClient.List(ctx, obj, opts...)
+	}
+	return c.typedClient.List(ctx, obj, opts...)
+}
+
+// Status implements client.StatusClient
+func (c *client) Status() StatusWriter {
+	return &statusWriter{client: c}
+}
+
+// statusWriter is client.StatusWriter that writes status subresource
+type statusWriter struct {
+	client *client
+}
+
+// ensure statusWriter implements client.StatusWriter
+var _ StatusWriter = &statusWriter{}
+
+// Update implements client.StatusWriter
+func (sw *statusWriter) Update(ctx context.Context, obj runtime.Object, opts ...UpdateOption) error {
+	defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
+	_, ok := obj.(*unstructured.Unstructured)
+	if ok {
+		return sw.client.unstructuredClient.UpdateStatus(ctx, obj, opts...)
+	}
+	return sw.client.typedClient.UpdateStatus(ctx, obj, opts...)
+}
+
+// Patch implements client.Client
+func (sw *statusWriter) Patch(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOption) error {
+	defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
+	_, ok := obj.(*unstructured.Unstructured)
+	if ok {
+		return sw.client.unstructuredClient.PatchStatus(ctx, obj, patch, opts...)
+	}
+	return sw.client.typedClient.PatchStatus(ctx, obj, patch, opts...)
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/client_cache.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client_cache.go
new file mode 100644
index 000000000..2a1ff05d5
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/client_cache.go
@@ -0,0 +1,145 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 client
+
+import (
+	"reflect"
+	"strings"
+	"sync"
+
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/client-go/rest"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+)
+
+// clientCache creates and caches rest clients and metadata for Kubernetes types
+type clientCache struct {
+	// config is the rest.Config to talk to an apiserver
+	config *rest.Config
+
+	// scheme maps go structs to GroupVersionKinds
+	scheme *runtime.Scheme
+
+	// mapper maps GroupVersionKinds to Resources
+	mapper meta.RESTMapper
+
+	// codecs are used to create a REST client for a gvk
+	codecs serializer.CodecFactory
+
+	// resourceByType caches type metadata
+	resourceByType map[reflect.Type]*resourceMeta
+	mu             sync.RWMutex
+}
+
+// newResource maps obj to a Kubernetes Resource and constructs a client for that Resource.
+// If the object is a list, the resource represents the item's type instead.
+func (c *clientCache) newResource(obj runtime.Object) (*resourceMeta, error) {
+	gvk, err := apiutil.GVKForObject(obj, c.scheme)
+	if err != nil {
+		return nil, err
+	}
+
+	if strings.HasSuffix(gvk.Kind, "List") && meta.IsListType(obj) {
+		// if this was a list, treat it as a request for the item's resource
+		gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
+	}
+
+	client, err := apiutil.RESTClientForGVK(gvk, c.config, c.codecs)
+	if err != nil {
+		return nil, err
+	}
+	mapping, err := c.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
+	if err != nil {
+		return nil, err
+	}
+	return &resourceMeta{Interface: client, mapping: mapping, gvk: gvk}, nil
+}
+
+// getResource returns the resource meta information for the given type of object.
+// If the object is a list, the resource represents the item's type instead.
+func (c *clientCache) getResource(obj runtime.Object) (*resourceMeta, error) {
+	typ := reflect.TypeOf(obj)
+
+	// It's better to do creation work twice than to not let multiple
+	// people make requests at once
+	c.mu.RLock()
+	r, known := c.resourceByType[typ]
+	c.mu.RUnlock()
+
+	if known {
+		return r, nil
+	}
+
+	// Initialize a new Client
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	r, err := c.newResource(obj)
+	if err != nil {
+		return nil, err
+	}
+	c.resourceByType[typ] = r
+	return r, err
+}
+
+// getObjMeta returns objMeta containing both type and object metadata and state
+func (c *clientCache) getObjMeta(obj runtime.Object) (*objMeta, error) {
+	r, err := c.getResource(obj)
+	if err != nil {
+		return nil, err
+	}
+	m, err := meta.Accessor(obj)
+	if err != nil {
+		return nil, err
+	}
+	return &objMeta{resourceMeta: r, Object: m}, err
+}
+
+// resourceMeta caches state for a Kubernetes type.
+type resourceMeta struct {
+	// client is the rest client used to talk to the apiserver
+	rest.Interface
+	// gvk is the GroupVersionKind of the resourceMeta
+	gvk schema.GroupVersionKind
+	// mapping is the rest mapping
+	mapping *meta.RESTMapping
+}
+
+// isNamespaced returns true if the type is namespaced
+func (r *resourceMeta) isNamespaced() bool {
+	if r.mapping.Scope.Name() == meta.RESTScopeNameRoot {
+		return false
+	}
+	return true
+}
+
+// resource returns the resource name of the type
+func (r *resourceMeta) resource() string {
+	return r.mapping.Resource.Resource
+}
+
+// objMeta stores type and object information about a Kubernetes type
+type objMeta struct {
+	// resourceMeta contains type information for the object
+	*resourceMeta
+
+	// Object contains meta data for the object instance
+	metav1.Object
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/doc.go
new file mode 100644
index 000000000..2965e5fa9
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/doc.go
@@ -0,0 +1,49 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 client contains functionality for interacting with Kubernetes API
+// servers.
+//
+// Clients
+//
+// Clients are split into two interfaces -- Readers and Writers.   Readers
+// get and list, while writers create, update, and delete.
+//
+// The New function can be used to create a new client that talks directly
+// to the API server.
+//
+// A common pattern in Kubernetes to read from a cache and write to the API
+// server.  This pattern is covered by the DelegatingClient type, which can
+// be used to have a client whose Reader is different from the Writer.
+//
+// Options
+//
+// Many client operations in Kubernetes support options.  These options are
+// represented as variadic arguments at the end of a given method call.
+// For instance, to use a label selector on list, you can call
+//  err := someReader.List(context.Background(), &podList, client.MatchingLabels{"somelabel": "someval"})
+//
+// Indexing
+//
+// Indexes may be added to caches using a FieldIndexer.  This allows you to easily
+// and efficiently look up objects with certain properties.  You can then make
+// use of the index by specifying a field selector on calls to List on the Reader
+// corresponding to the given Cache.
+//
+// For instance, a Secret controller might have an index on the
+// `.spec.volumes.secret.secretName` field in Pod objects, so that it could
+// easily look up all pods that reference a given secret.
+package client
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go
new file mode 100644
index 000000000..a11fde4e7
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/client.go
@@ -0,0 +1,360 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 (
+	"context"
+	"encoding/json"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/testing"
+
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+	"sigs.k8s.io/controller-runtime/pkg/internal/objectutil"
+)
+
+type versionedTracker struct {
+	testing.ObjectTracker
+}
+
+type fakeClient struct {
+	tracker versionedTracker
+	scheme  *runtime.Scheme
+}
+
+var _ client.Client = &fakeClient{}
+
+// NewFakeClient creates a new fake client for testing.
+// You can choose to initialize it with a slice of runtime.Object.
+// Deprecated: use NewFakeClientWithScheme.  You should always be
+// passing an explicit Scheme.
+func NewFakeClient(initObjs ...runtime.Object) client.Client {
+	return NewFakeClientWithScheme(scheme.Scheme, initObjs...)
+}
+
+// NewFakeClientWithScheme creates a new fake client with the given scheme
+// for testing.
+// You can choose to initialize it with a slice of runtime.Object.
+func NewFakeClientWithScheme(clientScheme *runtime.Scheme, initObjs ...runtime.Object) client.Client {
+	tracker := testing.NewObjectTracker(clientScheme, scheme.Codecs.UniversalDecoder())
+	for _, obj := range initObjs {
+		err := tracker.Add(obj)
+		if err != nil {
+			panic(fmt.Errorf("failed to add object %v to fake client: %v", obj, err))
+		}
+	}
+	return &fakeClient{
+		tracker: versionedTracker{tracker},
+		scheme:  clientScheme,
+	}
+}
+
+func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
+	if accessor, err := meta.Accessor(obj); err == nil {
+		if accessor.GetResourceVersion() == "" {
+			accessor.SetResourceVersion("1")
+		}
+	} else {
+		return err
+	}
+	return t.ObjectTracker.Create(gvr, obj, ns)
+}
+
+func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
+	if accessor, err := meta.Accessor(obj); err == nil {
+		version := 0
+		if rv := accessor.GetResourceVersion(); rv != "" {
+			version, err = strconv.Atoi(rv)
+		}
+		if err == nil {
+			accessor.SetResourceVersion(strconv.Itoa(version + 1))
+		}
+	} else {
+		return err
+	}
+	return t.ObjectTracker.Update(gvr, obj, ns)
+}
+
+func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
+	gvr, err := getGVRFromObject(obj, c.scheme)
+	if err != nil {
+		return err
+	}
+	o, err := c.tracker.Get(gvr, key.Namespace, key.Name)
+	if err != nil {
+		return err
+	}
+
+	gvk, err := apiutil.GVKForObject(obj, c.scheme)
+	if err != nil {
+		return err
+	}
+	ta, err := meta.TypeAccessor(o)
+	if err != nil {
+		return err
+	}
+	ta.SetKind(gvk.Kind)
+	ta.SetAPIVersion(gvk.GroupVersion().String())
+
+	j, err := json.Marshal(o)
+	if err != nil {
+		return err
+	}
+	decoder := scheme.Codecs.UniversalDecoder()
+	_, _, err = decoder.Decode(j, nil, obj)
+	return err
+}
+
+func (c *fakeClient) List(ctx context.Context, obj runtime.Object, opts ...client.ListOption) error {
+	gvk, err := apiutil.GVKForObject(obj, c.scheme)
+	if err != nil {
+		return err
+	}
+
+	OriginalKind := gvk.Kind
+
+	if !strings.HasSuffix(gvk.Kind, "List") {
+		return fmt.Errorf("non-list type %T (kind %q) passed as output", obj, gvk)
+	}
+	// we need the non-list GVK, so chop off the "List" from the end of the kind
+	gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
+
+	listOpts := client.ListOptions{}
+	listOpts.ApplyOptions(opts)
+
+	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
+	o, err := c.tracker.List(gvr, gvk, listOpts.Namespace)
+	if err != nil {
+		return err
+	}
+
+	ta, err := meta.TypeAccessor(o)
+	if err != nil {
+		return err
+	}
+	ta.SetKind(OriginalKind)
+	ta.SetAPIVersion(gvk.GroupVersion().String())
+
+	j, err := json.Marshal(o)
+	if err != nil {
+		return err
+	}
+	decoder := scheme.Codecs.UniversalDecoder()
+	_, _, err = decoder.Decode(j, nil, obj)
+	if err != nil {
+		return err
+	}
+
+	if listOpts.LabelSelector != nil {
+		objs, err := meta.ExtractList(obj)
+		if err != nil {
+			return err
+		}
+		filteredObjs, err := objectutil.FilterWithLabels(objs, listOpts.LabelSelector)
+		if err != nil {
+			return err
+		}
+		err = meta.SetList(obj, filteredObjs)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (c *fakeClient) Create(ctx context.Context, obj runtime.Object, opts ...client.CreateOption) error {
+	createOptions := &client.CreateOptions{}
+	createOptions.ApplyOptions(opts)
+
+	for _, dryRunOpt := range createOptions.DryRun {
+		if dryRunOpt == metav1.DryRunAll {
+			return nil
+		}
+	}
+
+	gvr, err := getGVRFromObject(obj, c.scheme)
+	if err != nil {
+		return err
+	}
+	accessor, err := meta.Accessor(obj)
+	if err != nil {
+		return err
+	}
+	return c.tracker.Create(gvr, obj, accessor.GetNamespace())
+}
+
+func (c *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...client.DeleteOption) error {
+	gvr, err := getGVRFromObject(obj, c.scheme)
+	if err != nil {
+		return err
+	}
+	accessor, err := meta.Accessor(obj)
+	if err != nil {
+		return err
+	}
+	delOptions := client.DeleteOptions{}
+	delOptions.ApplyOptions(opts)
+
+	//TODO: implement propagation
+	return c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
+}
+
+func (c *fakeClient) DeleteAllOf(ctx context.Context, obj runtime.Object, opts ...client.DeleteAllOfOption) error {
+	gvk, err := apiutil.GVKForObject(obj, scheme.Scheme)
+	if err != nil {
+		return err
+	}
+
+	dcOptions := client.DeleteAllOfOptions{}
+	dcOptions.ApplyOptions(opts)
+
+	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
+	o, err := c.tracker.List(gvr, gvk, dcOptions.Namespace)
+	if err != nil {
+		return err
+	}
+
+	objs, err := meta.ExtractList(o)
+	if err != nil {
+		return err
+	}
+	filteredObjs, err := objectutil.FilterWithLabels(objs, dcOptions.LabelSelector)
+	if err != nil {
+		return err
+	}
+	for _, o := range filteredObjs {
+		accessor, err := meta.Accessor(o)
+		if err != nil {
+			return err
+		}
+		err = c.tracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (c *fakeClient) Update(ctx context.Context, obj runtime.Object, opts ...client.UpdateOption) error {
+	updateOptions := &client.UpdateOptions{}
+	updateOptions.ApplyOptions(opts)
+
+	for _, dryRunOpt := range updateOptions.DryRun {
+		if dryRunOpt == metav1.DryRunAll {
+			return nil
+		}
+	}
+
+	gvr, err := getGVRFromObject(obj, c.scheme)
+	if err != nil {
+		return err
+	}
+	accessor, err := meta.Accessor(obj)
+	if err != nil {
+		return err
+	}
+	return c.tracker.Update(gvr, obj, accessor.GetNamespace())
+}
+
+func (c *fakeClient) Patch(ctx context.Context, obj runtime.Object, patch client.Patch, opts ...client.PatchOption) error {
+	patchOptions := &client.PatchOptions{}
+	patchOptions.ApplyOptions(opts)
+
+	for _, dryRunOpt := range patchOptions.DryRun {
+		if dryRunOpt == metav1.DryRunAll {
+			return nil
+		}
+	}
+
+	gvr, err := getGVRFromObject(obj, c.scheme)
+	if err != nil {
+		return err
+	}
+	accessor, err := meta.Accessor(obj)
+	if err != nil {
+		return err
+	}
+	data, err := patch.Data(obj)
+	if err != nil {
+		return err
+	}
+
+	reaction := testing.ObjectReaction(c.tracker)
+	handled, o, err := reaction(testing.NewPatchAction(gvr, accessor.GetNamespace(), accessor.GetName(), patch.Type(), data))
+	if err != nil {
+		return err
+	}
+	if !handled {
+		panic("tracker could not handle patch method")
+	}
+
+	gvk, err := apiutil.GVKForObject(obj, c.scheme)
+	if err != nil {
+		return err
+	}
+	ta, err := meta.TypeAccessor(o)
+	if err != nil {
+		return err
+	}
+	ta.SetKind(gvk.Kind)
+	ta.SetAPIVersion(gvk.GroupVersion().String())
+
+	j, err := json.Marshal(o)
+	if err != nil {
+		return err
+	}
+	decoder := scheme.Codecs.UniversalDecoder()
+	_, _, err = decoder.Decode(j, nil, obj)
+	return err
+}
+
+func (c *fakeClient) Status() client.StatusWriter {
+	return &fakeStatusWriter{client: c}
+}
+
+func getGVRFromObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionResource, error) {
+	gvk, err := apiutil.GVKForObject(obj, scheme)
+	if err != nil {
+		return schema.GroupVersionResource{}, err
+	}
+	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
+	return gvr, nil
+}
+
+type fakeStatusWriter struct {
+	client *fakeClient
+}
+
+func (sw *fakeStatusWriter) Update(ctx context.Context, obj runtime.Object, opts ...client.UpdateOption) error {
+	// TODO(droot): This results in full update of the obj (spec + status). Need
+	// a way to update status field only.
+	return sw.client.Update(ctx, obj, opts...)
+}
+
+func (sw *fakeStatusWriter) Patch(ctx context.Context, obj runtime.Object, patch client.Patch, opts ...client.PatchOption) error {
+	// TODO(droot): This results in full update of the obj (spec + status). Need
+	// a way to update status field only.
+	return sw.client.Patch(ctx, obj, patch, opts...)
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/doc.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/doc.go
new file mode 100644
index 000000000..9d5d85648
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/fake/doc.go
@@ -0,0 +1,32 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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.
+*/
+
+/*
+Deprecated: please use pkg/envtest for testing. This package will be dropped
+before the v1.0.0 release.
+Package fake provides a fake client for testing.
+
+An fake client is backed by its simple object store indexed by GroupVersionResource.
+You can create a fake client with optional objects.
+
+	client := NewFakeClient(initObjs...) // initObjs is a slice of runtime.Object
+
+You can invoke the methods defined in the Client interface.
+
+When it doubt, it's almost always better not to use this package and instead use
+envtest.Environment with a real client and API server.
+*/
+package fake
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go
new file mode 100644
index 000000000..00b43caee
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/interfaces.go
@@ -0,0 +1,135 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 client
+
+import (
+	"context"
+
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+
+	"k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+// ObjectKey identifies a Kubernetes Object.
+type ObjectKey = types.NamespacedName
+
+// ObjectKeyFromObject returns the ObjectKey given a runtime.Object
+func ObjectKeyFromObject(obj runtime.Object) (ObjectKey, error) {
+	accessor, err := meta.Accessor(obj)
+	if err != nil {
+		return ObjectKey{}, err
+	}
+	return ObjectKey{Namespace: accessor.GetNamespace(), Name: accessor.GetName()}, nil
+}
+
+// Patch is a patch that can be applied to a Kubernetes object.
+type Patch interface {
+	// Type is the PatchType of the patch.
+	Type() types.PatchType
+	// Data is the raw data representing the patch.
+	Data(obj runtime.Object) ([]byte, error)
+}
+
+// TODO(directxman12): is there a sane way to deal with get/delete options?
+
+// Reader knows how to read and list Kubernetes objects.
+type Reader interface {
+	// Get retrieves an obj for the given object key from the Kubernetes Cluster.
+	// obj must be a struct pointer so that obj can be updated with the response
+	// returned by the Server.
+	Get(ctx context.Context, key ObjectKey, obj runtime.Object) error
+
+	// List retrieves list of objects for a given namespace and list options. On a
+	// successful call, Items field in the list will be populated with the
+	// result returned from the server.
+	List(ctx context.Context, list runtime.Object, opts ...ListOption) error
+}
+
+// Writer knows how to create, delete, and update Kubernetes objects.
+type Writer interface {
+	// Create saves the object obj in the Kubernetes cluster.
+	Create(ctx context.Context, obj runtime.Object, opts ...CreateOption) error
+
+	// Delete deletes the given obj from Kubernetes cluster.
+	Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOption) error
+
+	// Update updates the given obj in the Kubernetes cluster. obj must be a
+	// struct pointer so that obj can be updated with the content returned by the Server.
+	Update(ctx context.Context, obj runtime.Object, opts ...UpdateOption) error
+
+	// Patch patches the given obj in the Kubernetes cluster. obj must be a
+	// struct pointer so that obj can be updated with the content returned by the Server.
+	Patch(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOption) error
+
+	// DeleteAllOf deletes all objects of the given type matching the given options.
+	DeleteAllOf(ctx context.Context, obj runtime.Object, opts ...DeleteAllOfOption) error
+}
+
+// StatusClient knows how to create a client which can update status subresource
+// for kubernetes objects.
+type StatusClient interface {
+	Status() StatusWriter
+}
+
+// StatusWriter knows how to update status subresource of a Kubernetes object.
+type StatusWriter interface {
+	// Update updates the fields corresponding to the status subresource for the
+	// given obj. obj must be a struct pointer so that obj can be updated
+	// with the content returned by the Server.
+	Update(ctx context.Context, obj runtime.Object, opts ...UpdateOption) error
+
+	// Patch patches the given object's subresource. obj must be a struct
+	// pointer so that obj can be updated with the content returned by the
+	// Server.
+	Patch(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOption) error
+}
+
+// Client knows how to perform CRUD operations on Kubernetes objects.
+type Client interface {
+	Reader
+	Writer
+	StatusClient
+}
+
+// IndexerFunc knows how to take an object and turn it into a series
+// of non-namespaced keys. Namespaced objects are automatically given
+// namespaced and non-spaced variants, so keys do not need to include namespace.
+type IndexerFunc func(runtime.Object) []string
+
+// FieldIndexer knows how to index over a particular "field" such that it
+// can later be used by a field selector.
+type FieldIndexer interface {
+	// IndexFields adds an index with the given field name on the given object type
+	// by using the given function to extract the value for that field.  If you want
+	// compatibility with the Kubernetes API server, only return one key, and only use
+	// fields that the API server supports.  Otherwise, you can return multiple keys,
+	// and "equality" in the field selector means that at least one key matches the value.
+	// The FieldIndexer will automatically take care of indexing over namespace
+	// and supporting efficient all-namespace queries.
+	IndexField(obj runtime.Object, field string, extractValue IndexerFunc) error
+}
+
+// IgnoreNotFound returns nil on NotFound errors.
+// All other values that are not NotFound errors or nil are returned unmodified.
+func IgnoreNotFound(err error) error {
+	if apierrors.IsNotFound(err) {
+		return nil
+	}
+	return err
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go
new file mode 100644
index 000000000..62b5a8624
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/options.go
@@ -0,0 +1,655 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 client
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/fields"
+	"k8s.io/apimachinery/pkg/labels"
+)
+
+// {{{ "Functional" Option Interfaces
+
+// CreateOption is some configuration that modifies options for a create request.
+type CreateOption interface {
+	// ApplyToCreate applies this configuration to the given create options.
+	ApplyToCreate(*CreateOptions)
+}
+
+// DeleteOption is some configuration that modifies options for a delete request.
+type DeleteOption interface {
+	// ApplyToDelete applies this configuration to the given delete options.
+	ApplyToDelete(*DeleteOptions)
+}
+
+// ListOption is some configuration that modifies options for a list request.
+type ListOption interface {
+	// ApplyToList applies this configuration to the given list options.
+	ApplyToList(*ListOptions)
+}
+
+// UpdateOption is some configuration that modifies options for a update request.
+type UpdateOption interface {
+	// ApplyToUpdate applies this configuration to the given update options.
+	ApplyToUpdate(*UpdateOptions)
+}
+
+// PatchOption is some configuration that modifies options for a patch request.
+type PatchOption interface {
+	// ApplyToPatch applies this configuration to the given patch options.
+	ApplyToPatch(*PatchOptions)
+}
+
+// DeleteAllOfOption is some configuration that modifies options for a delete request.
+type DeleteAllOfOption interface {
+	// ApplyToDeleteAllOf applies this configuration to the given deletecollection options.
+	ApplyToDeleteAllOf(*DeleteAllOfOptions)
+}
+
+// }}}
+
+// {{{ Multi-Type Options
+
+// DryRunAll sets the "dry run" option to "all", executing all
+// validation, etc without persisting the change to storage.
+var DryRunAll = dryRunAll{}
+
+type dryRunAll struct{}
+
+func (dryRunAll) ApplyToCreate(opts *CreateOptions) {
+	opts.DryRun = []string{metav1.DryRunAll}
+}
+func (dryRunAll) ApplyToUpdate(opts *UpdateOptions) {
+	opts.DryRun = []string{metav1.DryRunAll}
+}
+func (dryRunAll) ApplyToPatch(opts *PatchOptions) {
+	opts.DryRun = []string{metav1.DryRunAll}
+}
+func (dryRunAll) ApplyToDelete(opts *DeleteOptions) {
+	opts.DryRun = []string{metav1.DryRunAll}
+}
+
+// FieldOwner set the field manager name for the given server-side apply patch.
+type FieldOwner string
+
+func (f FieldOwner) ApplyToPatch(opts *PatchOptions) {
+	opts.FieldManager = string(f)
+}
+func (f FieldOwner) ApplyToCreate(opts *CreateOptions) {
+	opts.FieldManager = string(f)
+}
+func (f FieldOwner) ApplyToUpdate(opts *UpdateOptions) {
+	opts.FieldManager = string(f)
+}
+
+// }}}
+
+// {{{ Create Options
+
+// CreateOptions contains options for create requests. It's generally a subset
+// of metav1.CreateOptions.
+type CreateOptions struct {
+	// When present, indicates that modifications should not be
+	// persisted. An invalid or unrecognized dryRun directive will
+	// result in an error response and no further processing of the
+	// request. Valid values are:
+	// - All: all dry run stages will be processed
+	DryRun []string
+
+	// FieldManager is the name of the user or component submitting
+	// this request.  It must be set with server-side apply.
+	FieldManager string
+
+	// Raw represents raw CreateOptions, as passed to the API server.
+	Raw *metav1.CreateOptions
+}
+
+// AsCreateOptions returns these options as a metav1.CreateOptions.
+// This may mutate the Raw field.
+func (o *CreateOptions) AsCreateOptions() *metav1.CreateOptions {
+	if o == nil {
+		return &metav1.CreateOptions{}
+	}
+	if o.Raw == nil {
+		o.Raw = &metav1.CreateOptions{}
+	}
+
+	o.Raw.DryRun = o.DryRun
+	o.Raw.FieldManager = o.FieldManager
+	return o.Raw
+}
+
+// ApplyOptions applies the given create options on these options,
+// and then returns itself (for convenient chaining).
+func (o *CreateOptions) ApplyOptions(opts []CreateOption) *CreateOptions {
+	for _, opt := range opts {
+		opt.ApplyToCreate(o)
+	}
+	return o
+}
+
+// ApplyToCreate implements CreateOption
+func (o *CreateOptions) ApplyToCreate(co *CreateOptions) {
+	if o.DryRun != nil {
+		co.DryRun = o.DryRun
+	}
+	if o.FieldManager != "" {
+		co.FieldManager = o.FieldManager
+	}
+	if o.Raw != nil {
+		co.Raw = o.Raw
+	}
+}
+
+var _ CreateOption = &CreateOptions{}
+
+// CreateDryRunAll sets the "dry run" option to "all".
+//
+// Deprecated: Use DryRunAll
+var CreateDryRunAll = DryRunAll
+
+// }}}
+
+// {{{ Delete Options
+
+// DeleteOptions contains options for delete requests. It's generally a subset
+// of metav1.DeleteOptions.
+type DeleteOptions struct {
+	// GracePeriodSeconds is the duration in seconds before the object should be
+	// deleted. Value must be non-negative integer. The value zero indicates
+	// delete immediately. If this value is nil, the default grace period for the
+	// specified type will be used.
+	GracePeriodSeconds *int64
+
+	// Preconditions must be fulfilled before a deletion is carried out. If not
+	// possible, a 409 Conflict status will be returned.
+	Preconditions *metav1.Preconditions
+
+	// PropagationPolicy determined whether and how garbage collection will be
+	// performed. Either this field or OrphanDependents may be set, but not both.
+	// The default policy is decided by the existing finalizer set in the
+	// metadata.finalizers and the resource-specific default policy.
+	// Acceptable values are: 'Orphan' - orphan the dependents; 'Background' -
+	// allow the garbage collector to delete the dependents in the background;
+	// 'Foreground' - a cascading policy that deletes all dependents in the
+	// foreground.
+	PropagationPolicy *metav1.DeletionPropagation
+
+	// Raw represents raw DeleteOptions, as passed to the API server.
+	Raw *metav1.DeleteOptions
+
+	// When present, indicates that modifications should not be
+	// persisted. An invalid or unrecognized dryRun directive will
+	// result in an error response and no further processing of the
+	// request. Valid values are:
+	// - All: all dry run stages will be processed
+	DryRun []string
+}
+
+// AsDeleteOptions returns these options as a metav1.DeleteOptions.
+// This may mutate the Raw field.
+func (o *DeleteOptions) AsDeleteOptions() *metav1.DeleteOptions {
+	if o == nil {
+		return &metav1.DeleteOptions{}
+	}
+	if o.Raw == nil {
+		o.Raw = &metav1.DeleteOptions{}
+	}
+
+	o.Raw.GracePeriodSeconds = o.GracePeriodSeconds
+	o.Raw.Preconditions = o.Preconditions
+	o.Raw.PropagationPolicy = o.PropagationPolicy
+	o.Raw.DryRun = o.DryRun
+	return o.Raw
+}
+
+// ApplyOptions applies the given delete options on these options,
+// and then returns itself (for convenient chaining).
+func (o *DeleteOptions) ApplyOptions(opts []DeleteOption) *DeleteOptions {
+	for _, opt := range opts {
+		opt.ApplyToDelete(o)
+	}
+	return o
+}
+
+var _ DeleteOption = &DeleteOptions{}
+
+// ApplyToDelete implements DeleteOption
+func (o *DeleteOptions) ApplyToDelete(do *DeleteOptions) {
+	if o.GracePeriodSeconds != nil {
+		do.GracePeriodSeconds = o.GracePeriodSeconds
+	}
+	if o.Preconditions != nil {
+		do.Preconditions = o.Preconditions
+	}
+	if o.PropagationPolicy != nil {
+		do.PropagationPolicy = o.PropagationPolicy
+	}
+	if o.Raw != nil {
+		do.Raw = o.Raw
+	}
+	if o.DryRun != nil {
+		do.DryRun = o.DryRun
+	}
+}
+
+// GracePeriodSeconds sets the grace period for the deletion
+// to the given number of seconds.
+type GracePeriodSeconds int64
+
+func (s GracePeriodSeconds) ApplyToDelete(opts *DeleteOptions) {
+	secs := int64(s)
+	opts.GracePeriodSeconds = &secs
+}
+
+func (s GracePeriodSeconds) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
+	s.ApplyToDelete(&opts.DeleteOptions)
+}
+
+type Preconditions metav1.Preconditions
+
+func (p Preconditions) ApplyToDelete(opts *DeleteOptions) {
+	preconds := metav1.Preconditions(p)
+	opts.Preconditions = &preconds
+}
+
+func (p Preconditions) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
+	p.ApplyToDelete(&opts.DeleteOptions)
+}
+
+type PropagationPolicy metav1.DeletionPropagation
+
+func (p PropagationPolicy) ApplyToDelete(opts *DeleteOptions) {
+	policy := metav1.DeletionPropagation(p)
+	opts.PropagationPolicy = &policy
+}
+
+func (p PropagationPolicy) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
+	p.ApplyToDelete(&opts.DeleteOptions)
+}
+
+// }}}
+
+// {{{ List Options
+
+// ListOptions contains options for limiting or filtering results.
+// It's generally a subset of metav1.ListOptions, with support for
+// pre-parsed selectors (since generally, selectors will be executed
+// against the cache).
+type ListOptions struct {
+	// LabelSelector filters results by label.  Use SetLabelSelector to
+	// set from raw string form.
+	LabelSelector labels.Selector
+	// FieldSelector filters results by a particular field.  In order
+	// to use this with cache-based implementations, restrict usage to
+	// a single field-value pair that's been added to the indexers.
+	FieldSelector fields.Selector
+
+	// Namespace represents the namespace to list for, or empty for
+	// non-namespaced objects, or to list across all namespaces.
+	Namespace string
+
+	// Limit specifies the maximum number of results to return from the server. The server may
+	// not support this field on all resource types, but if it does and more results remain it
+	// will set the continue field on the returned list object. This field is not supported if watch
+	// is true in the Raw ListOptions.
+	Limit int64
+	// Continue is a token returned by the server that lets a client retrieve chunks of results
+	// from the server by specifying limit. The server may reject requests for continuation tokens
+	// it does not recognize and will return a 410 error if the token can no longer be used because
+	// it has expired. This field is not supported if watch is true in the Raw ListOptions.
+	Continue string
+
+	// Raw represents raw ListOptions, as passed to the API server.  Note
+	// that these may not be respected by all implementations of interface,
+	// and the LabelSelector, FieldSelector, Limit and Continue fields are ignored.
+	Raw *metav1.ListOptions
+}
+
+var _ ListOption = &ListOptions{}
+
+// ApplyToList implements ListOption for ListOptions
+func (o *ListOptions) ApplyToList(lo *ListOptions) {
+	if o.LabelSelector != nil {
+		lo.LabelSelector = o.LabelSelector
+	}
+	if o.FieldSelector != nil {
+		lo.FieldSelector = o.FieldSelector
+	}
+	if o.Namespace != "" {
+		lo.Namespace = o.Namespace
+	}
+	if o.Raw != nil {
+		lo.Raw = o.Raw
+	}
+	if o.Limit > 0 {
+		lo.Limit = o.Limit
+	}
+	if o.Continue != "" {
+		lo.Continue = o.Continue
+	}
+}
+
+// AsListOptions returns these options as a flattened metav1.ListOptions.
+// This may mutate the Raw field.
+func (o *ListOptions) AsListOptions() *metav1.ListOptions {
+	if o == nil {
+		return &metav1.ListOptions{}
+	}
+	if o.Raw == nil {
+		o.Raw = &metav1.ListOptions{}
+	}
+	if o.LabelSelector != nil {
+		o.Raw.LabelSelector = o.LabelSelector.String()
+	}
+	if o.FieldSelector != nil {
+		o.Raw.FieldSelector = o.FieldSelector.String()
+	}
+	if !o.Raw.Watch {
+		o.Raw.Limit = o.Limit
+		o.Raw.Continue = o.Continue
+	}
+	return o.Raw
+}
+
+// ApplyOptions applies the given list options on these options,
+// and then returns itself (for convenient chaining).
+func (o *ListOptions) ApplyOptions(opts []ListOption) *ListOptions {
+	for _, opt := range opts {
+		opt.ApplyToList(o)
+	}
+	return o
+}
+
+// MatchingLabels filters the list/delete operation on the given set of labels.
+type MatchingLabels map[string]string
+
+func (m MatchingLabels) ApplyToList(opts *ListOptions) {
+	// TODO(directxman12): can we avoid reserializing this over and over?
+	sel := labels.SelectorFromSet(map[string]string(m))
+	opts.LabelSelector = sel
+}
+
+func (m MatchingLabels) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
+	m.ApplyToList(&opts.ListOptions)
+}
+
+// MatchingLabelsSelector filters the list/delete operation on the given label
+// selector (or index in the case of cached lists). A struct is used because
+// labels.Selector is an interface, which cannot be aliased.
+type MatchingLabelsSelector struct {
+	labels.Selector
+}
+
+func (m MatchingLabelsSelector) ApplyToList(opts *ListOptions) {
+	opts.LabelSelector = m
+}
+
+func (m MatchingLabelsSelector) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
+	m.ApplyToList(&opts.ListOptions)
+}
+
+// MatchingField filters the list operation on the given field selector
+// (or index in the case of cached lists).
+//
+// Deprecated: Use MatchingFields
+func MatchingField(name, val string) MatchingFields {
+	return MatchingFields{name: val}
+}
+
+// MatchingField filters the list/delete operation on the given field Set
+// (or index in the case of cached lists).
+type MatchingFields fields.Set
+
+func (m MatchingFields) ApplyToList(opts *ListOptions) {
+	// TODO(directxman12): can we avoid re-serializing this?
+	sel := fields.SelectorFromSet(fields.Set(m))
+	opts.FieldSelector = sel
+}
+
+func (m MatchingFields) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
+	m.ApplyToList(&opts.ListOptions)
+}
+
+// MatchingFieldsSelector filters the list/delete operation on the given field
+// selector (or index in the case of cached lists). A struct is used because
+// fields.Selector is an interface, which cannot be aliased.
+type MatchingFieldsSelector struct {
+	fields.Selector
+}
+
+func (m MatchingFieldsSelector) ApplyToList(opts *ListOptions) {
+	opts.FieldSelector = m
+}
+
+func (m MatchingFieldsSelector) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
+	m.ApplyToList(&opts.ListOptions)
+}
+
+// InNamespace restricts the list/delete operation to the given namespace.
+type InNamespace string
+
+func (n InNamespace) ApplyToList(opts *ListOptions) {
+	opts.Namespace = string(n)
+}
+
+func (n InNamespace) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
+	n.ApplyToList(&opts.ListOptions)
+}
+
+// Limit specifies the maximum number of results to return from the server.
+// Limit does not implement DeleteAllOfOption interface because the server
+// does not support setting it for deletecollection operations.
+type Limit int64
+
+func (l Limit) ApplyToList(opts *ListOptions) {
+	opts.Limit = int64(l)
+}
+
+// Continue sets a continuation token to retrieve chunks of results when using limit.
+// Continue does not implement DeleteAllOfOption interface because the server
+// does not support setting it for deletecollection operations.
+type Continue string
+
+func (c Continue) ApplyToList(opts *ListOptions) {
+	opts.Continue = string(c)
+}
+
+// }}}
+
+// {{{ Update Options
+
+// UpdateOptions contains options for create requests. It's generally a subset
+// of metav1.UpdateOptions.
+type UpdateOptions struct {
+	// When present, indicates that modifications should not be
+	// persisted. An invalid or unrecognized dryRun directive will
+	// result in an error response and no further processing of the
+	// request. Valid values are:
+	// - All: all dry run stages will be processed
+	DryRun []string
+
+	// FieldManager is the name of the user or component submitting
+	// this request.  It must be set with server-side apply.
+	FieldManager string
+
+	// Raw represents raw UpdateOptions, as passed to the API server.
+	Raw *metav1.UpdateOptions
+}
+
+// AsUpdateOptions returns these options as a metav1.UpdateOptions.
+// This may mutate the Raw field.
+func (o *UpdateOptions) AsUpdateOptions() *metav1.UpdateOptions {
+	if o == nil {
+		return &metav1.UpdateOptions{}
+	}
+	if o.Raw == nil {
+		o.Raw = &metav1.UpdateOptions{}
+	}
+
+	o.Raw.DryRun = o.DryRun
+	o.Raw.FieldManager = o.FieldManager
+	return o.Raw
+}
+
+// ApplyOptions applies the given update options on these options,
+// and then returns itself (for convenient chaining).
+func (o *UpdateOptions) ApplyOptions(opts []UpdateOption) *UpdateOptions {
+	for _, opt := range opts {
+		opt.ApplyToUpdate(o)
+	}
+	return o
+}
+
+var _ UpdateOption = &UpdateOptions{}
+
+// ApplyToUpdate implements UpdateOption
+func (o *UpdateOptions) ApplyToUpdate(uo *UpdateOptions) {
+	if o.DryRun != nil {
+		uo.DryRun = o.DryRun
+	}
+	if o.FieldManager != "" {
+		uo.FieldManager = o.FieldManager
+	}
+	if o.Raw != nil {
+		uo.Raw = o.Raw
+	}
+}
+
+// UpdateDryRunAll sets the "dry run" option to "all".
+//
+// Deprecated: Use DryRunAll
+var UpdateDryRunAll = DryRunAll
+
+// }}}
+
+// {{{ Patch Options
+
+// PatchOptions contains options for patch requests.
+type PatchOptions struct {
+	// When present, indicates that modifications should not be
+	// persisted. An invalid or unrecognized dryRun directive will
+	// result in an error response and no further processing of the
+	// request. Valid values are:
+	// - All: all dry run stages will be processed
+	DryRun []string
+
+	// Force is going to "force" Apply requests. It means user will
+	// re-acquire conflicting fields owned by other people. Force
+	// flag must be unset for non-apply patch requests.
+	// +optional
+	Force *bool
+
+	// FieldManager is the name of the user or component submitting
+	// this request.  It must be set with server-side apply.
+	FieldManager string
+
+	// Raw represents raw PatchOptions, as passed to the API server.
+	Raw *metav1.PatchOptions
+}
+
+// ApplyOptions applies the given patch options on these options,
+// and then returns itself (for convenient chaining).
+func (o *PatchOptions) ApplyOptions(opts []PatchOption) *PatchOptions {
+	for _, opt := range opts {
+		opt.ApplyToPatch(o)
+	}
+	return o
+}
+
+// AsPatchOptions returns these options as a metav1.PatchOptions.
+// This may mutate the Raw field.
+func (o *PatchOptions) AsPatchOptions() *metav1.PatchOptions {
+	if o == nil {
+		return &metav1.PatchOptions{}
+	}
+	if o.Raw == nil {
+		o.Raw = &metav1.PatchOptions{}
+	}
+
+	o.Raw.DryRun = o.DryRun
+	o.Raw.Force = o.Force
+	o.Raw.FieldManager = o.FieldManager
+	return o.Raw
+}
+
+var _ PatchOption = &PatchOptions{}
+
+// ApplyToPatch implements PatchOptions
+func (o *PatchOptions) ApplyToPatch(po *PatchOptions) {
+	if o.DryRun != nil {
+		po.DryRun = o.DryRun
+	}
+	if o.Force != nil {
+		po.Force = o.Force
+	}
+	if o.FieldManager != "" {
+		po.FieldManager = o.FieldManager
+	}
+	if o.Raw != nil {
+		po.Raw = o.Raw
+	}
+}
+
+// ForceOwnership indicates that in case of conflicts with server-side apply,
+// the client should acquire ownership of the conflicting field.  Most
+// controllers should use this.
+var ForceOwnership = forceOwnership{}
+
+type forceOwnership struct{}
+
+func (forceOwnership) ApplyToPatch(opts *PatchOptions) {
+	definitelyTrue := true
+	opts.Force = &definitelyTrue
+}
+
+// PatchDryRunAll sets the "dry run" option to "all".
+//
+// Deprecated: Use DryRunAll
+var PatchDryRunAll = DryRunAll
+
+// }}}
+
+// {{{ DeleteAllOf Options
+
+// these are all just delete options and list options
+
+// DeleteAllOfOptions contains options for deletecollection (deleteallof) requests.
+// It's just list and delete options smooshed together.
+type DeleteAllOfOptions struct {
+	ListOptions
+	DeleteOptions
+}
+
+// ApplyOptions applies the given deleteallof options on these options,
+// and then returns itself (for convenient chaining).
+func (o *DeleteAllOfOptions) ApplyOptions(opts []DeleteAllOfOption) *DeleteAllOfOptions {
+	for _, opt := range opts {
+		opt.ApplyToDeleteAllOf(o)
+	}
+	return o
+}
+
+var _ DeleteAllOfOption = &DeleteAllOfOptions{}
+
+// ApplyToDeleteAllOf implements DeleteAllOfOption
+func (o *DeleteAllOfOptions) ApplyToDeleteAllOf(do *DeleteAllOfOptions) {
+	o.ApplyToList(&do.ListOptions)
+	o.ApplyToDelete(&do.DeleteOptions)
+}
+
+// }}}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/patch.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/patch.go
new file mode 100644
index 000000000..164de17f3
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/patch.go
@@ -0,0 +1,95 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 client
+
+import (
+	jsonpatch "github.com/evanphx/json-patch"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/json"
+)
+
+var (
+	// Apply uses server-side apply to patch the given object.
+	Apply = applyPatch{}
+)
+
+type patch struct {
+	patchType types.PatchType
+	data      []byte
+}
+
+// Type implements Patch.
+func (s *patch) Type() types.PatchType {
+	return s.patchType
+}
+
+// Data implements Patch.
+func (s *patch) Data(obj runtime.Object) ([]byte, error) {
+	return s.data, nil
+}
+
+// ConstantPatch constructs a new Patch with the given PatchType and data.
+func ConstantPatch(patchType types.PatchType, data []byte) Patch {
+	return &patch{patchType, data}
+}
+
+type mergeFromPatch struct {
+	from runtime.Object
+}
+
+// Type implements patch.
+func (s *mergeFromPatch) Type() types.PatchType {
+	return types.MergePatchType
+}
+
+// Data implements Patch.
+func (s *mergeFromPatch) Data(obj runtime.Object) ([]byte, error) {
+	originalJSON, err := json.Marshal(s.from)
+	if err != nil {
+		return nil, err
+	}
+
+	modifiedJSON, err := json.Marshal(obj)
+	if err != nil {
+		return nil, err
+	}
+
+	return jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
+}
+
+// MergeFrom creates a Patch that patches using the merge-patch strategy with the given object as base.
+func MergeFrom(obj runtime.Object) Patch {
+	return &mergeFromPatch{obj}
+}
+
+// applyPatch uses server-side apply to patch the object.
+type applyPatch struct{}
+
+// Type implements Patch.
+func (p applyPatch) Type() types.PatchType {
+	return types.ApplyPatchType
+}
+
+// Data implements Patch.
+func (p applyPatch) Data(obj runtime.Object) ([]byte, error) {
+	// NB(directxman12): we might technically want to be using an actual encoder
+	// here (in case some more performant encoder is introduced) but this is
+	// correct and sufficient for our uses (it's what the JSON serializer in
+	// client-go does, more-or-less).
+	return json.Marshal(obj)
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/split.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/split.go
new file mode 100644
index 000000000..47cba9576
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/split.go
@@ -0,0 +1,61 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 client
+
+import (
+	"context"
+
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+// DelegatingClient forms a Client by composing separate reader, writer and
+// statusclient interfaces.  This way, you can have an Client that reads from a
+// cache and writes to the API server.
+type DelegatingClient struct {
+	Reader
+	Writer
+	StatusClient
+}
+
+// DelegatingReader forms a Reader that will cause Get and List requests for
+// unstructured types to use the ClientReader while requests for any other type
+// of object with use the CacheReader.  This avoids accidentally caching the
+// entire cluster in the common case of loading arbitrary unstructured objects
+// (e.g. from OwnerReferences).
+type DelegatingReader struct {
+	CacheReader  Reader
+	ClientReader Reader
+}
+
+// Get retrieves an obj for a given object key from the Kubernetes Cluster.
+func (d *DelegatingReader) Get(ctx context.Context, key ObjectKey, obj runtime.Object) error {
+	_, isUnstructured := obj.(*unstructured.Unstructured)
+	if isUnstructured {
+		return d.ClientReader.Get(ctx, key, obj)
+	}
+	return d.CacheReader.Get(ctx, key, obj)
+}
+
+// List retrieves list of objects for a given namespace and list options.
+func (d *DelegatingReader) List(ctx context.Context, list runtime.Object, opts ...ListOption) error {
+	_, isUnstructured := list.(*unstructured.UnstructuredList)
+	if isUnstructured {
+		return d.ClientReader.List(ctx, list, opts...)
+	}
+	return d.CacheReader.List(ctx, list, opts...)
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go
new file mode 100644
index 000000000..6ddc1171d
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/typed_client.go
@@ -0,0 +1,210 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 client
+
+import (
+	"context"
+
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+// client is a client.Client that reads and writes directly from/to an API server.  It lazily initializes
+// new clients at the time they are used, and caches the client.
+type typedClient struct {
+	cache      clientCache
+	paramCodec runtime.ParameterCodec
+}
+
+// Create implements client.Client
+func (c *typedClient) Create(ctx context.Context, obj runtime.Object, opts ...CreateOption) error {
+	o, err := c.cache.getObjMeta(obj)
+	if err != nil {
+		return err
+	}
+
+	createOpts := &CreateOptions{}
+	createOpts.ApplyOptions(opts)
+	return o.Post().
+		NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
+		Resource(o.resource()).
+		Body(obj).
+		VersionedParams(createOpts.AsCreateOptions(), c.paramCodec).
+		Context(ctx).
+		Do().
+		Into(obj)
+}
+
+// Update implements client.Client
+func (c *typedClient) Update(ctx context.Context, obj runtime.Object, opts ...UpdateOption) error {
+	o, err := c.cache.getObjMeta(obj)
+	if err != nil {
+		return err
+	}
+
+	updateOpts := &UpdateOptions{}
+	updateOpts.ApplyOptions(opts)
+	return o.Put().
+		NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
+		Resource(o.resource()).
+		Name(o.GetName()).
+		Body(obj).
+		VersionedParams(updateOpts.AsUpdateOptions(), c.paramCodec).
+		Context(ctx).
+		Do().
+		Into(obj)
+}
+
+// Delete implements client.Client
+func (c *typedClient) Delete(ctx context.Context, obj runtime.Object, opts ...DeleteOption) error {
+	o, err := c.cache.getObjMeta(obj)
+	if err != nil {
+		return err
+	}
+
+	deleteOpts := DeleteOptions{}
+	deleteOpts.ApplyOptions(opts)
+
+	return o.Delete().
+		NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
+		Resource(o.resource()).
+		Name(o.GetName()).
+		Body(deleteOpts.AsDeleteOptions()).
+		Context(ctx).
+		Do().
+		Error()
+}
+
+// DeleteAllOf implements client.Client
+func (c *typedClient) DeleteAllOf(ctx context.Context, obj runtime.Object, opts ...DeleteAllOfOption) error {
+	o, err := c.cache.getObjMeta(obj)
+	if err != nil {
+		return err
+	}
+
+	deleteAllOfOpts := DeleteAllOfOptions{}
+	deleteAllOfOpts.ApplyOptions(opts)
+
+	return o.Delete().
+		NamespaceIfScoped(deleteAllOfOpts.ListOptions.Namespace, o.isNamespaced()).
+		Resource(o.resource()).
+		VersionedParams(deleteAllOfOpts.AsListOptions(), c.paramCodec).
+		Body(deleteAllOfOpts.AsDeleteOptions()).
+		Context(ctx).
+		Do().
+		Error()
+}
+
+// Patch implements client.Client
+func (c *typedClient) Patch(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOption) error {
+	o, err := c.cache.getObjMeta(obj)
+	if err != nil {
+		return err
+	}
+
+	data, err := patch.Data(obj)
+	if err != nil {
+		return err
+	}
+
+	patchOpts := &PatchOptions{}
+	return o.Patch(patch.Type()).
+		NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
+		Resource(o.resource()).
+		Name(o.GetName()).
+		VersionedParams(patchOpts.ApplyOptions(opts).AsPatchOptions(), c.paramCodec).
+		Body(data).
+		Context(ctx).
+		Do().
+		Into(obj)
+}
+
+// Get implements client.Client
+func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj runtime.Object) error {
+	r, err := c.cache.getResource(obj)
+	if err != nil {
+		return err
+	}
+	return r.Get().
+		NamespaceIfScoped(key.Namespace, r.isNamespaced()).
+		Resource(r.resource()).
+		Context(ctx).
+		Name(key.Name).Do().Into(obj)
+}
+
+// List implements client.Client
+func (c *typedClient) List(ctx context.Context, obj runtime.Object, opts ...ListOption) error {
+	r, err := c.cache.getResource(obj)
+	if err != nil {
+		return err
+	}
+	listOpts := ListOptions{}
+	listOpts.ApplyOptions(opts)
+	return r.Get().
+		NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()).
+		Resource(r.resource()).
+		VersionedParams(listOpts.AsListOptions(), c.paramCodec).
+		Context(ctx).
+		Do().
+		Into(obj)
+}
+
+// UpdateStatus used by StatusWriter to write status.
+func (c *typedClient) UpdateStatus(ctx context.Context, obj runtime.Object, opts ...UpdateOption) error {
+	o, err := c.cache.getObjMeta(obj)
+	if err != nil {
+		return err
+	}
+	// TODO(droot): examine the returned error and check if it error needs to be
+	// wrapped to improve the UX ?
+	// It will be nice to receive an error saying the object doesn't implement
+	// status subresource and check CRD definition
+	return o.Put().
+		NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
+		Resource(o.resource()).
+		Name(o.GetName()).
+		SubResource("status").
+		Body(obj).
+		VersionedParams((&UpdateOptions{}).ApplyOptions(opts).AsUpdateOptions(), c.paramCodec).
+		Context(ctx).
+		Do().
+		Into(obj)
+}
+
+// PatchStatus used by StatusWriter to write status.
+func (c *typedClient) PatchStatus(ctx context.Context, obj runtime.Object, patch Patch, opts ...PatchOption) error {
+	o, err := c.cache.getObjMeta(obj)
+	if err != nil {
+		return err
+	}
+
+	data, err := patch.Data(obj)
+	if err != nil {
+		return err
+	}
+
+	patchOpts := &PatchOptions{}
+	return o.Patch(patch.Type()).
+		NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
+		Resource(o.resource()).
+		Name(o.GetName()).
+		SubResource("status").
+		Body(data).
+		VersionedParams(patchOpts.ApplyOptions(opts).AsPatchOptions(), c.paramCodec).
+		Context(ctx).
+		Do().
+		Into(obj)
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go b/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go
new file mode 100644
index 000000000..440ad2f97
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/client/unstructured_client.go
@@ -0,0 +1,230 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 client
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/dynamic"
+)
+
+// client is a client.Client that reads and writes directly from/to an API server.  It lazily initializes
+// new clients at the time they are used, and caches the client.
+type unstructuredClient struct {
+	client     dynamic.Interface
+	restMapper meta.RESTMapper
+}
+
+// Create implements client.Client
+func (uc *unstructuredClient) Create(_ context.Context, obj runtime.Object, opts ...CreateOption) error {
+	u, ok := obj.(*unstructured.Unstructured)
+	if !ok {
+		return fmt.Errorf("unstructured client did not understand object: %T", obj)
+	}
+	createOpts := CreateOptions{}
+	createOpts.ApplyOptions(opts)
+	r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
+	if err != nil {
+		return err
+	}
+	i, err := r.Create(u, *createOpts.AsCreateOptions())
+	if err != nil {
+		return err
+	}
+	u.Object = i.Object
+	return nil
+}
+
+// Update implements client.Client
+func (uc *unstructuredClient) Update(_ context.Context, obj runtime.Object, opts ...UpdateOption) error {
+	u, ok := obj.(*unstructured.Unstructured)
+	if !ok {
+		return fmt.Errorf("unstructured client did not understand object: %T", obj)
+	}
+	updateOpts := UpdateOptions{}
+	updateOpts.ApplyOptions(opts)
+	r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
+	if err != nil {
+		return err
+	}
+	i, err := r.Update(u, *updateOpts.AsUpdateOptions())
+	if err != nil {
+		return err
+	}
+	u.Object = i.Object
+	return nil
+}
+
+// Delete implements client.Client
+func (uc *unstructuredClient) Delete(_ context.Context, obj runtime.Object, opts ...DeleteOption) error {
+	u, ok := obj.(*unstructured.Unstructured)
+	if !ok {
+		return fmt.Errorf("unstructured client did not understand object: %T", obj)
+	}
+	r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
+	if err != nil {
+		return err
+	}
+	deleteOpts := DeleteOptions{}
+	deleteOpts.ApplyOptions(opts)
+	err = r.Delete(u.GetName(), deleteOpts.AsDeleteOptions())
+	return err
+}
+
+// DeleteAllOf implements client.Client
+func (uc *unstructuredClient) DeleteAllOf(_ context.Context, obj runtime.Object, opts ...DeleteAllOfOption) error {
+	u, ok := obj.(*unstructured.Unstructured)
+	if !ok {
+		return fmt.Errorf("unstructured client did not understand object: %T", obj)
+	}
+	r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
+	if err != nil {
+		return err
+	}
+
+	deleteAllOfOpts := DeleteAllOfOptions{}
+	deleteAllOfOpts.ApplyOptions(opts)
+	err = r.DeleteCollection(deleteAllOfOpts.AsDeleteOptions(), *deleteAllOfOpts.AsListOptions())
+	return err
+}
+
+// Patch implements client.Client
+func (uc *unstructuredClient) Patch(_ context.Context, obj runtime.Object, patch Patch, opts ...PatchOption) error {
+	u, ok := obj.(*unstructured.Unstructured)
+	if !ok {
+		return fmt.Errorf("unstructured client did not understand object: %T", obj)
+	}
+	r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
+	if err != nil {
+		return err
+	}
+
+	data, err := patch.Data(obj)
+	if err != nil {
+		return err
+	}
+
+	patchOpts := &PatchOptions{}
+	i, err := r.Patch(u.GetName(), patch.Type(), data, *patchOpts.ApplyOptions(opts).AsPatchOptions())
+	if err != nil {
+		return err
+	}
+	u.Object = i.Object
+	return nil
+}
+
+// Get implements client.Client
+func (uc *unstructuredClient) Get(_ context.Context, key ObjectKey, obj runtime.Object) error {
+	u, ok := obj.(*unstructured.Unstructured)
+	if !ok {
+		return fmt.Errorf("unstructured client did not understand object: %T", obj)
+	}
+	r, err := uc.getResourceInterface(u.GroupVersionKind(), key.Namespace)
+	if err != nil {
+		return err
+	}
+	i, err := r.Get(key.Name, metav1.GetOptions{})
+	if err != nil {
+		return err
+	}
+	u.Object = i.Object
+	return nil
+}
+
+// List implements client.Client
+func (uc *unstructuredClient) List(_ context.Context, obj runtime.Object, opts ...ListOption) error {
+	u, ok := obj.(*unstructured.UnstructuredList)
+	if !ok {
+		return fmt.Errorf("unstructured client did not understand object: %T", obj)
+	}
+	gvk := u.GroupVersionKind()
+	if strings.HasSuffix(gvk.Kind, "List") {
+		gvk.Kind = gvk.Kind[:len(gvk.Kind)-4]
+	}
+	listOpts := ListOptions{}
+	listOpts.ApplyOptions(opts)
+	r, err := uc.getResourceInterface(gvk, listOpts.Namespace)
+	if err != nil {
+		return err
+	}
+
+	i, err := r.List(*listOpts.AsListOptions())
+	if err != nil {
+		return err
+	}
+	u.Items = i.Items
+	u.Object = i.Object
+	return nil
+}
+
+func (uc *unstructuredClient) UpdateStatus(_ context.Context, obj runtime.Object, opts ...UpdateOption) error {
+	u, ok := obj.(*unstructured.Unstructured)
+	if !ok {
+		return fmt.Errorf("unstructured client did not understand object: %T", obj)
+	}
+	r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
+	if err != nil {
+		return err
+	}
+	i, err := r.UpdateStatus(u, *(&UpdateOptions{}).ApplyOptions(opts).AsUpdateOptions())
+	if err != nil {
+		return err
+	}
+	u.Object = i.Object
+	return nil
+}
+
+func (uc *unstructuredClient) PatchStatus(_ context.Context, obj runtime.Object, patch Patch, opts ...PatchOption) error {
+	u, ok := obj.(*unstructured.Unstructured)
+	if !ok {
+		return fmt.Errorf("unstructured client did not understand object: %T", obj)
+	}
+	r, err := uc.getResourceInterface(u.GroupVersionKind(), u.GetNamespace())
+	if err != nil {
+		return err
+	}
+
+	data, err := patch.Data(obj)
+	if err != nil {
+		return err
+	}
+
+	i, err := r.Patch(u.GetName(), patch.Type(), data, *(&PatchOptions{}).ApplyOptions(opts).AsPatchOptions(), "status")
+	if err != nil {
+		return err
+	}
+	u.Object = i.Object
+	return nil
+}
+
+func (uc *unstructuredClient) getResourceInterface(gvk schema.GroupVersionKind, ns string) (dynamic.ResourceInterface, error) {
+	mapping, err := uc.restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
+	if err != nil {
+		return nil, err
+	}
+	if mapping.Scope.Name() == meta.RESTScopeNameRoot {
+		return uc.client.Resource(mapping.Resource), nil
+	}
+	return uc.client.Resource(mapping.Resource).Namespace(ns), nil
+}
diff --git a/vendor/sigs.k8s.io/controller-runtime/pkg/internal/objectutil/filter.go b/vendor/sigs.k8s.io/controller-runtime/pkg/internal/objectutil/filter.go
new file mode 100644
index 000000000..8513846e2
--- /dev/null
+++ b/vendor/sigs.k8s.io/controller-runtime/pkg/internal/objectutil/filter.go
@@ -0,0 +1,42 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+
+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
+
+    http://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 objectutil
+
+import (
+	apimeta "k8s.io/apimachinery/pkg/api/meta"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/runtime"
+)
+
+// FilterWithLabels returns a copy of the items in objs matching labelSel
+func FilterWithLabels(objs []runtime.Object, labelSel labels.Selector) ([]runtime.Object, error) {
+	outItems := make([]runtime.Object, 0, len(objs))
+	for _, obj := range objs {
+		meta, err := apimeta.Accessor(obj)
+		if err != nil {
+			return nil, err
+		}
+		if labelSel != nil {
+			lbls := labels.Set(meta.GetLabels())
+			if !labelSel.Matches(lbls) {
+				continue
+			}
+		}
+		outItems = append(outItems, obj.DeepCopyObject())
+	}
+	return outItems, nil
+}


Test patch:
diff --git a/pkg/controller/operators/catalog/operator_test.go b/pkg/controller/operators/catalog/operator_test.go
index f8f4d5236..f1ca225bf 100644
--- a/pkg/controller/operators/catalog/operator_test.go
+++ b/pkg/controller/operators/catalog/operator_test.go
@@ -49,6 +49,7 @@ import (
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/fakes"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/clientfake"
+	controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
@@ -1301,7 +1302,14 @@ func NewFakeOperator(ctx context.Context, namespace string, namespaces []string,
 	}
 	op.sources = grpc.NewSourceStore(config.logger, 1*time.Second, 5*time.Second, op.syncSourceState)
 	if op.reconciler == nil {
-		op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, op.opClient, "test:pod", op.now)
+		s := runtime.NewScheme()
+		err := k8sfake.AddToScheme(s)
+		if err != nil {
+			return nil, err
+		}
+		applier := controllerclient.NewFakeApplier(s, "testowner")
+
+		op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, op.opClient, "test:pod", op.now, applier)
 	}
 
 	op.RunInformers(ctx)
diff --git a/pkg/lib/controller-runtime/client/ssa_test.go b/pkg/lib/controller-runtime/client/ssa_test.go
new file mode 100644
index 000000000..27ba000cf
--- /dev/null
+++ b/pkg/lib/controller-runtime/client/ssa_test.go
@@ -0,0 +1,165 @@
+package client
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+func TestSetDefaultGroupVersionKind(t *testing.T) {
+	tests := []struct {
+		name          string
+		schemebuilder runtime.SchemeBuilder
+		obj           Object
+		want          schema.GroupVersionKind
+	}{
+		{
+			name:          "DefaultGVK",
+			schemebuilder: corev1.SchemeBuilder,
+			obj:           &corev1.ServiceAccount{},
+			want: schema.GroupVersionKind{
+				Group:   "",
+				Version: "v1",
+				Kind:    "ServiceAccount",
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := runtime.NewScheme()
+			err := tt.schemebuilder.AddToScheme(s)
+			require.NoError(t, err)
+
+			SetDefaultGroupVersionKind(tt.obj, s)
+			require.EqualValues(t, tt.want, tt.obj.GetObjectKind().GroupVersionKind())
+		})
+	}
+}
+
+func TestServerSideApply(t *testing.T) {
+	tests := []struct {
+		name          string
+		obj           Object
+		schemebuilder *runtime.SchemeBuilder
+		changeFunc    interface{}
+		want          Object
+	}{
+		{
+			name: "ServerSideMetadataApply",
+			obj: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "testpod",
+					Namespace: "testns",
+					Labels: map[string]string{
+						"testlabel": "oldvalue",
+					},
+				},
+			},
+			schemebuilder: &corev1.SchemeBuilder,
+			changeFunc: func(p *corev1.Pod) error {
+				p.Labels["testlabel"] = "newvalue"
+				return nil
+			},
+			want: &corev1.Pod{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "Pod",
+					APIVersion: "v1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "testpod",
+					Namespace: "testns",
+					Labels: map[string]string{
+						"testlabel": "newvalue",
+					},
+				},
+			},
+		},
+		{
+			name: "ServerSideStatusApply",
+			obj: &corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "testpod",
+					Namespace: "testns",
+				},
+				Status: corev1.PodStatus{Message: "new"},
+			},
+			schemebuilder: &corev1.SchemeBuilder,
+			changeFunc: func(p *corev1.Pod) error {
+				p.Status.Message = "new"
+				return nil
+			},
+			want: &corev1.Pod{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "Pod",
+					APIVersion: "v1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "testpod",
+					Namespace: "testns",
+				},
+				Status: corev1.PodStatus{Message: "new"},
+			},
+		},
+		{
+			name: "ServerSideUnstructuredApply",
+			obj: &unstructured.Unstructured{
+				Object: map[string]interface{}{
+					"kind":       "Pod",
+					"apiVersion": "v1",
+					"metadata": map[string]interface{}{
+						"name":      "testpod",
+						"namespace": "testns",
+					},
+					"status": map[string]interface{}{
+						"message": "old",
+					},
+				},
+			},
+			schemebuilder: &corev1.SchemeBuilder,
+			changeFunc: func(u *unstructured.Unstructured) error {
+				return unstructured.SetNestedField(u.Object, "new", "status", "message")
+			},
+			want: &corev1.Pod{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "Pod",
+					APIVersion: "v1",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "testpod",
+					Namespace: "testns",
+				},
+				Status: corev1.PodStatus{Message: "new"},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := runtime.NewScheme()
+			require.NoError(t, tt.schemebuilder.AddToScheme(s))
+
+			applier := NewFakeApplier(s, "testowner", tt.obj)
+			require.NoError(t, applier.Apply(context.TODO(), tt.obj, tt.changeFunc)())
+
+			newObj := corev1.Pod{}
+			require.NoError(t, applier.client.Get(context.TODO(), NamespacedName(t, tt.obj), &newObj))
+
+			newObj.ResourceVersion = ""
+			require.EqualValues(t, tt.want, &newObj)
+		})
+	}
+}
+
+// NamespacedName returns the namespaced name of the given object and panics if it can't access the object's meta.
+func NamespacedName(t *testing.T, obj runtime.Object) types.NamespacedName {
+	m, err := meta.Accessor(obj)
+	require.NoError(t, err)
+	return types.NamespacedName{Namespace: m.GetNamespace(), Name: m.GetName()}
+}
diff --git a/test/e2e/util_test.go b/test/e2e/util_test.go
index 3254f4037..01730810e 100644
--- a/test/e2e/util_test.go
+++ b/test/e2e/util_test.go
@@ -16,6 +16,7 @@ import (
 	"google.golang.org/grpc"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 	extScheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
 	"k8s.io/apimachinery/pkg/api/equality"
@@ -30,17 +31,22 @@ import (
 	"k8s.io/apiserver/pkg/storage/names"
 	"k8s.io/client-go/dynamic"
 	k8sscheme "k8s.io/client-go/kubernetes/scheme"
+	kscheme "k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/rest"
 
+	operatorsv1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
+	controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client"
 	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
 	pmclient "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/client"
 	pmversioned "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/client/clientset/versioned"
 )
 
+
+
 const (
 	pollInterval = 1 * time.Second
 	pollDuration = 5 * time.Minute
@@ -624,4 +630,26 @@ func Local(client operatorclient.ClientInterface) (bool, error) {
 	}
 
 	return true, nil
-}
\ No newline at end of file
+}
+
+func newSSAClient(t *testing.T, config *rest.Config) *controllerclient.ServerSideApplier {
+	scheme := runtime.NewScheme()
+	localSchemeBuilder := runtime.NewSchemeBuilder(
+		apiextensionsv1.AddToScheme,
+		kscheme.AddToScheme,
+		v1alpha1.AddToScheme,
+		operatorsv1.AddToScheme,
+	)
+
+	err := localSchemeBuilder.AddToScheme(scheme)
+	require.NoError(t, err)
+
+	ssaClient, err := controllerclient.NewForConfig(config, scheme, "test.olm.registry")
+	require.NoError(t, err)
+	return ssaClient
+}
+
+func Apply(ssaClient *controllerclient.ServerSideApplier, obj controllerclient.Object, changeFunc interface{}) func() error {
+	return ssaClient.Apply(context.Background(), obj, changeFunc)
+}
+