From b4ed1de6b8e113ab7e0bebd48d1dd809677e280a Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Wed, 17 Oct 2018 12:53:10 -0700 Subject: [PATCH] ref(*): kubernetes v1.12 support Signed-off-by: Adam Reese --- Gopkg.lock | 103 +++++++++++--------- Gopkg.toml | 20 +++- cmd/helm/helm.go | 2 +- pkg/kube/client.go | 106 ++++++++++----------- pkg/kube/client_test.go | 40 ++++---- pkg/kube/config.go | 4 +- pkg/kube/converter.go | 15 ++- pkg/kube/factory.go | 38 ++++++++ pkg/kube/log.go | 2 +- pkg/kube/result.go | 2 +- pkg/kube/result_test.go | 2 +- pkg/kube/wait.go | 34 +++++-- pkg/releasetesting/environment.go | 4 +- pkg/releasetesting/test_suite.go | 10 +- pkg/releasetesting/test_suite_test.go | 8 +- pkg/tiller/environment/environment.go | 10 +- pkg/tiller/environment/environment_test.go | 10 +- pkg/tiller/release_server_test.go | 8 +- 18 files changed, 245 insertions(+), 173 deletions(-) create mode 100644 pkg/kube/factory.go diff --git a/Gopkg.lock b/Gopkg.lock index c330cf6c8..cd5e78f7f 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -393,11 +393,12 @@ revision = "3959339b333561bf62a38b424fd41517c2c90f40" [[projects]] - digest = "1:06ec9147400aabb0d6960dd8557638603b5f320cd4cb8a3eceaae407e782849a" + digest = "1:8eb1de8112c9924d59bf1d3e5c26f5eaa2bfc2a5fcbb92dc1c2e4546d695f277" name = "github.com/imdario/mergo" packages = ["."] pruneopts = "UT" - revision = "6633656539c1639d9d78127b7d47c622b5d7b6dc" + revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4" + version = "v0.3.6" [[projects]] digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" @@ -689,7 +690,7 @@ version = "v0.9.0" [[projects]] - digest = "1:c45031ba03b85fc3b219c46b540996b793d1c5244ae4d7046314b8d09526c2a5" + digest = "1:517fc596d15da8456fefaa85bcff18dd2068ade58f1e108997c2456ff0e83d3d" name = "gopkg.in/square/go-jose.v2" packages = [ ".", @@ -698,8 +699,7 @@ "jwt", ] pruneopts = "UT" - revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1" - version = "v2.1.3" + revision = "89060dee6a84df9a4dae49f676f0c755037834f1" [[projects]] digest = "1:c27797c5f42d349e2a604510822df7d037415aae58bf1e6fd35624eda757c0aa" @@ -709,8 +709,8 @@ revision = "53feefa2559fb8dfa8d81baad31be332c97d6c77" [[projects]] - branch = "release-1.11" - digest = "1:fcc8693dcd553f6a9dfe94487fa66443a166405e4c4cce87d50bbad58c0812ce" + branch = "release-1.12" + digest = "1:16e3493f1ebd6e2c9bf2f05a2c0f0f23bd8bd346dfa27bfc5ccd29d2b77f900c" name = "k8s.io/api" packages = [ "admission/v1beta1", @@ -725,10 +725,12 @@ "authorization/v1beta1", "autoscaling/v1", "autoscaling/v2beta1", + "autoscaling/v2beta2", "batch/v1", "batch/v1beta1", "batch/v2alpha1", "certificates/v1beta1", + "coordination/v1beta1", "core/v1", "events/v1beta1", "extensions/v1beta1", @@ -746,7 +748,7 @@ "storage/v1beta1", ] pruneopts = "UT" - revision = "61b11ee6533278ae17d77fd36825d0b47ec3a293" + revision = "475331a8afff5587f47d0470a93f79c60c573c03" [[projects]] digest = "1:b46a162d7c7e9117ae2dd9a73ee4dc2181ad9ea9d505fd7c5eb63c96211dc9dd" @@ -756,8 +758,8 @@ revision = "898b0eda132e1aeac43a459785144ee4bf9b0a2e" [[projects]] - branch = "release-1.11" - digest = "1:5fb786469c205f315aea2e894c4bb4532570d5d7f63e1024abd274034c19547e" + branch = "release-1.12" + digest = "1:51d5c9bf991053e2c265cf2203c843dbddfa78fed4d8b22b9072b50561accd2b" name = "k8s.io/apimachinery" packages = [ "pkg/api/equality", @@ -766,6 +768,7 @@ "pkg/api/meta/testrestmapper", "pkg/api/resource", "pkg/api/validation", + "pkg/api/validation/path", "pkg/apis/meta/internalversion", "pkg/apis/meta/v1", "pkg/apis/meta/v1/unstructured", @@ -797,6 +800,7 @@ "pkg/util/intstr", "pkg/util/json", "pkg/util/mergepatch", + "pkg/util/naming", "pkg/util/net", "pkg/util/rand", "pkg/util/remotecommand", @@ -814,11 +818,11 @@ "third_party/forked/golang/reflect", ] pruneopts = "UT" - revision = "488889b0007f63ffee90b66a34a2deca9ec58774" + revision = "6dd46049f39503a1fc8d65de4bd566829e95faff" [[projects]] - branch = "release-1.10" - digest = "1:dde6031988d993fc4f0a4d13eded2b665d6c1dbfbea27e700a078a388d4a2593" + branch = "release-1.12" + digest = "1:e38fd46b96594c848ae465219e948e3ca1d3b595fc136d63b5022e625d8436bb" name = "k8s.io/apiserver" packages = [ "pkg/apis/audit", @@ -830,10 +834,22 @@ "pkg/util/feature", ] pruneopts = "UT" - revision = "ea53f8588c655568158b4ff53f5ec6fa4ebfc332" + revision = "a4ed22a224c0a5f970851abac59d313a6ac803c5" [[projects]] - digest = "1:5acb90c7f7c2070b74fb6d693f0ce15909039ecf65d8a663591caaddf5842ecd" + branch = "master" + digest = "1:9fefce8370f58ca7701e82309446bd72ca765a1c5da83207857cebbc1bd9aeb2" + name = "k8s.io/cli-runtime" + packages = [ + "pkg/genericclioptions", + "pkg/genericclioptions/printers", + "pkg/genericclioptions/resource", + ] + pruneopts = "UT" + revision = "7915b4409361b23932e7af013a2ec31d5753e001" + +[[projects]] + digest = "1:fb69e389e81d571d59b7a22ac06354e4cfec2a1d693362117b330977cad8d69a" name = "k8s.io/client-go" packages = [ "discovery", @@ -865,6 +881,8 @@ "kubernetes/typed/autoscaling/v1/fake", "kubernetes/typed/autoscaling/v2beta1", "kubernetes/typed/autoscaling/v2beta1/fake", + "kubernetes/typed/autoscaling/v2beta2", + "kubernetes/typed/autoscaling/v2beta2/fake", "kubernetes/typed/batch/v1", "kubernetes/typed/batch/v1/fake", "kubernetes/typed/batch/v1beta1", @@ -873,6 +891,8 @@ "kubernetes/typed/batch/v2alpha1/fake", "kubernetes/typed/certificates/v1beta1", "kubernetes/typed/certificates/v1beta1/fake", + "kubernetes/typed/coordination/v1beta1", + "kubernetes/typed/coordination/v1beta1/fake", "kubernetes/typed/core/v1", "kubernetes/typed/core/v1/fake", "kubernetes/typed/events/v1beta1", @@ -901,8 +921,6 @@ "kubernetes/typed/storage/v1alpha1/fake", "kubernetes/typed/storage/v1beta1", "kubernetes/typed/storage/v1beta1/fake", - "listers/apps/v1", - "listers/core/v1", "pkg/apis/clientauthentication", "pkg/apis/clientauthentication/v1alpha1", "pkg/apis/clientauthentication/v1beta1", @@ -938,6 +956,7 @@ "tools/record", "tools/reference", "tools/remotecommand", + "tools/watch", "transport", "transport/spdy", "util/buffer", @@ -951,8 +970,8 @@ "util/retry", ] pruneopts = "UT" - revision = "1f13a808da65775f22cbf47862c4e5898d8f4ca1" - version = "kubernetes-1.11.2" + revision = "cb4883f3dea0a8d72fc4af710798a980992a773d" + version = "kubernetes-1.12.1" [[projects]] digest = "1:8a5fb6a585e27c0339096c0db745795940a7e72a7925e7c4cf40b76bd113d382" @@ -966,8 +985,8 @@ revision = "50ae88d24ede7b8bad68e23c805b5d3da5c8abaf" [[projects]] - branch = "release-1.11" - digest = "1:0b8f7f69212ed74043de94c089008d6c3d58c53e4d08a13abec73419339d4180" + branch = "release-1.12" + digest = "1:fa66cfb06184d37bcf1fa085a91ba8f5c9d3a0c440f72c7bf898c3162a848617" name = "k8s.io/kubernetes" packages = [ "pkg/api/events", @@ -976,11 +995,7 @@ "pkg/api/ref", "pkg/api/resource", "pkg/api/service", - "pkg/api/testapi", "pkg/api/v1/pod", - "pkg/apis/admission", - "pkg/apis/admission/install", - "pkg/apis/admission/v1beta1", "pkg/apis/admissionregistration", "pkg/apis/admissionregistration/install", "pkg/apis/admissionregistration/v1alpha1", @@ -1002,6 +1017,7 @@ "pkg/apis/autoscaling/install", "pkg/apis/autoscaling/v1", "pkg/apis/autoscaling/v2beta1", + "pkg/apis/autoscaling/v2beta2", "pkg/apis/batch", "pkg/apis/batch/install", "pkg/apis/batch/v1", @@ -1010,9 +1026,9 @@ "pkg/apis/certificates", "pkg/apis/certificates/install", "pkg/apis/certificates/v1beta1", - "pkg/apis/componentconfig", - "pkg/apis/componentconfig/install", - "pkg/apis/componentconfig/v1alpha1", + "pkg/apis/coordination", + "pkg/apis/coordination/install", + "pkg/apis/coordination/v1beta1", "pkg/apis/core", "pkg/apis/core/helper", "pkg/apis/core/helper/qos", @@ -1027,9 +1043,6 @@ "pkg/apis/extensions", "pkg/apis/extensions/install", "pkg/apis/extensions/v1beta1", - "pkg/apis/imagepolicy", - "pkg/apis/imagepolicy/install", - "pkg/apis/imagepolicy/v1alpha1", "pkg/apis/networking", "pkg/apis/networking/install", "pkg/apis/networking/v1", @@ -1064,6 +1077,7 @@ "pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion", "pkg/client/clientset_generated/internalclientset/typed/batch/internalversion", "pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion", + "pkg/client/clientset_generated/internalclientset/typed/coordination/internalversion", "pkg/client/clientset_generated/internalclientset/typed/core/internalversion", "pkg/client/clientset_generated/internalclientset/typed/events/internalversion", "pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion", @@ -1088,9 +1102,6 @@ "pkg/kubectl/cmd/util/openapi", "pkg/kubectl/cmd/util/openapi/testing", "pkg/kubectl/cmd/util/openapi/validation", - "pkg/kubectl/genericclioptions", - "pkg/kubectl/genericclioptions/printers", - "pkg/kubectl/genericclioptions/resource", "pkg/kubectl/scheme", "pkg/kubectl/util", "pkg/kubectl/util/hash", @@ -1118,20 +1129,23 @@ "pkg/util/net/sets", "pkg/util/node", "pkg/util/parsers", - "pkg/util/pointer", "pkg/util/slice", "pkg/util/taints", "pkg/version", ] pruneopts = "UT" - revision = "6c8b476f24edb0abfb143e87238045d1d9aa73e6" + revision = "8ea5d6e20648a2bdf056105a75e7307c9e15c3f2" [[projects]] - digest = "1:5271b4ee2724d8c2ad7df650a5f9db46d01ce558769469713feba0e3e6079292" + branch = "master" + digest = "1:b587a79602928eab5971377f9fddc392cd047202ac4d6aae8845e10bd8634d78" name = "k8s.io/utils" - packages = ["exec"] + packages = [ + "exec", + "pointer", + ] pruneopts = "UT" - revision = "aedf551cdb8b0119df3a19c65fde413a13b34997" + revision = "f024bbd3a09501e18d1973b22be7188c5c005014" [[projects]] digest = "1:96f9b7c99c55e6063371088376d57d398f42888dedd08ab5d35065aba11e3965" @@ -1187,25 +1201,24 @@ "k8s.io/apimachinery/pkg/util/wait", "k8s.io/apimachinery/pkg/version", "k8s.io/apimachinery/pkg/watch", + "k8s.io/cli-runtime/pkg/genericclioptions", + "k8s.io/cli-runtime/pkg/genericclioptions/resource", "k8s.io/client-go/discovery", "k8s.io/client-go/kubernetes", "k8s.io/client-go/kubernetes/fake", + "k8s.io/client-go/kubernetes/scheme", "k8s.io/client-go/kubernetes/typed/core/v1", "k8s.io/client-go/plugin/pkg/client/auth", "k8s.io/client-go/rest/fake", + "k8s.io/client-go/tools/clientcmd", + "k8s.io/client-go/tools/watch", "k8s.io/client-go/util/homedir", "k8s.io/kubernetes/pkg/api/legacyscheme", - "k8s.io/kubernetes/pkg/api/testapi", - "k8s.io/kubernetes/pkg/api/v1/pod", "k8s.io/kubernetes/pkg/apis/batch", - "k8s.io/kubernetes/pkg/apis/core", - "k8s.io/kubernetes/pkg/apis/core/v1/helper", "k8s.io/kubernetes/pkg/controller/deployment/util", "k8s.io/kubernetes/pkg/kubectl/cmd/get", "k8s.io/kubernetes/pkg/kubectl/cmd/testing", "k8s.io/kubernetes/pkg/kubectl/cmd/util", - "k8s.io/kubernetes/pkg/kubectl/genericclioptions", - "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource", "k8s.io/kubernetes/pkg/kubectl/scheme", "k8s.io/kubernetes/pkg/kubectl/validation", ] diff --git a/Gopkg.toml b/Gopkg.toml index effd828fa..3e558b9ba 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -33,19 +33,23 @@ [[constraint]] name = "k8s.io/api" - branch = "release-1.11" + branch = "release-1.12" [[constraint]] name = "k8s.io/apimachinery" - branch = "release-1.11" + branch = "release-1.12" [[constraint]] - version = "kubernetes-1.11.2" + version = "kubernetes-1.12.1" name = "k8s.io/client-go" [[constraint]] name = "k8s.io/kubernetes" - branch = "release-1.11" + branch = "release-1.12" + +[[override]] + name = "k8s.io/apiserver" + branch = "release-1.12" [[override]] name = "github.com/json-iterator/go" @@ -55,6 +59,14 @@ name = "github.com/Azure/go-autorest" revision = "1ff28809256a84bb6966640ff3d0371af82ccba4" +[[override]] + name = "github.com/imdario/mergo" + version = "v0.3.5" + +[[override]] + name = "gopkg.in/square/go-jose.v2" + revision = "89060dee6a84df9a4dae49f676f0c755037834f1" + [prune] go-tests = true unused-packages = true diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index fd3e39302..7471dbaf8 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -23,8 +23,8 @@ import ( "sync" // Import to initialize client auth plugins. + "k8s.io/cli-runtime/pkg/genericclioptions" _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/helm/pkg/helm" "k8s.io/helm/pkg/helm/environment" diff --git a/pkg/kube/client.go b/pkg/kube/client.go index d8e0bda4d..3fe2aecf3 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -18,6 +18,7 @@ package kube // import "k8s.io/helm/pkg/kube" import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -43,28 +44,26 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/watch" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericclioptions/resource" + "k8s.io/client-go/kubernetes" + watchtools "k8s.io/client-go/tools/watch" "k8s.io/kubernetes/pkg/api/legacyscheme" batchinternal "k8s.io/kubernetes/pkg/apis/batch" - "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/kubectl/cmd/get" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" - "k8s.io/kubernetes/pkg/kubectl/validation" ) -const ( - // MissingGetHeader is added to Get's output when a resource is not found. - MissingGetHeader = "==> MISSING\nKIND\t\tNAME\n" -) +// MissingGetHeader is added to Get's output when a resource is not found. +const MissingGetHeader = "==> MISSING\nKIND\t\tNAME\n" // ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. var ErrNoObjectsVisited = goerrors.New("no objects visited") // Client represents a client capable of communicating with the Kubernetes API. type Client struct { - cmdutil.Factory - Log func(string, ...interface{}) + Factory Factory + Log func(string, ...interface{}) } // New creates a new Client. @@ -78,6 +77,10 @@ func New(getter genericclioptions.RESTClientGetter) *Client { } } +func (c *Client) KubernetesClientSet() (*kubernetes.Clientset, error) { + return c.Factory.KubernetesClientSet() +} + var nopLogger = func(_ string, _ ...interface{}) {} // ResourceActorFunc performs an action on a single resource. @@ -103,27 +106,24 @@ func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shoul } func (c *Client) namespace() string { - if ns, _, err := c.ToRawKubeConfigLoader().Namespace(); err == nil { + if ns, _, err := c.Factory.ToRawKubeConfigLoader().Namespace(); err == nil { return ns } return v1.NamespaceDefault } -func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result { - return c.NewBuilder(). +// newBuilder returns a new resource builder for structured api objects. +func (c *Client) newBuilder() *resource.Builder { + return c.Factory.NewBuilder(). ContinueOnError(). - WithScheme(legacyscheme.Scheme). - Schema(c.validator()). NamespaceParam(c.namespace()). DefaultNamespace(). RequireNamespace(). - Stream(reader, ""). - Flatten(). - Do() + Flatten() } -func (c *Client) validator() validation.Schema { - schema, err := c.Validator(true) +func (c *Client) validator() resource.ContentValidator { + schema, err := c.Factory.Validator(true) if err != nil { c.Log("warning: failed to load schema: %s", err) } @@ -134,14 +134,9 @@ func (c *Client) validator() validation.Schema { func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) { var result Result - result, err := c.NewBuilder(). + result, err := c.newBuilder(). Unstructured(). - ContinueOnError(). - NamespaceParam(c.namespace()). - DefaultNamespace(). - RequireNamespace(). Stream(reader, ""). - Flatten(). Do().Infos() return result, scrubValidationError(err) } @@ -149,7 +144,12 @@ func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, // Build validates for Kubernetes objects and returns resource Infos from a io.Reader. func (c *Client) Build(namespace string, reader io.Reader) (Result, error) { var result Result - result, err := c.newBuilder(namespace, reader).Infos() + result, err := c.newBuilder(). + WithScheme(legacyscheme.Scheme). + Schema(c.validator()). + Stream(reader, ""). + Do(). + Infos() return result, scrubValidationError(err) } @@ -165,7 +165,7 @@ func (c *Client) Get(namespace string, reader io.Reader) (string, error) { return "", err } - var objPods = make(map[string][]core.Pod) + var objPods = make(map[string][]v1.Pod) missing := []string{} err = perform(infos, func(info *resource.Info) error { @@ -369,7 +369,7 @@ func perform(infos Result, fn ResourceActorFunc) error { } func createResource(info *resource.Info) error { - obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) + obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object, nil) if err != nil { return err } @@ -439,7 +439,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, // send patch to server helper := resource.NewHelper(target.Client, target.Mapping) - obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch) + obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch, nil) if err != nil { kind := target.Mapping.GroupVersionKind.Kind log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err) @@ -480,12 +480,12 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, return nil } - client, err := c.ClientSet() + client, err := c.KubernetesClientSet() if err != nil { return err } - pods, err := client.Core().Pods(target.Namespace).List(metav1.ListOptions{ + pods, err := client.CoreV1().Pods(target.Namespace).List(metav1.ListOptions{ FieldSelector: fields.Everything().String(), LabelSelector: labels.Set(selector).AsSelector().String(), }) @@ -498,7 +498,7 @@ func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, c.Log("Restarting pod: %v/%v", pod.Namespace, pod.Name) // Delete each pod for get them restarted with changed spec. - if err := client.Core().Pods(pod.Namespace).Delete(pod.Name, metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil { + if err := client.CoreV1().Pods(pod.Namespace).Delete(pod.Name, metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil { return err } } @@ -562,7 +562,9 @@ func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) err // In the future, we might want to add some special logic for types // like Ingress, Volume, etc. - _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { + ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout) + defer cancel() + _, err = watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) { switch e.Type { case watch.Added, watch.Modified: // For things like a secret or a config map, this is the best indicator @@ -598,9 +600,9 @@ func (c *Client) waitForJob(e watch.Event, name string) (bool, error) { } for _, c := range o.Status.Conditions { - if c.Type == batchinternal.JobComplete && c.Status == core.ConditionTrue { + if c.Type == batchinternal.JobComplete && c.Status == "True" { return true, nil - } else if c.Type == batchinternal.JobFailed && c.Status == core.ConditionTrue { + } else if c.Type == batchinternal.JobFailed && c.Status == "True" { return true, goerrors.Errorf("job failed: %s", c.Reason) } } @@ -624,26 +626,26 @@ func scrubValidationError(err error) error { // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase // and returns said phase (PodSucceeded or PodFailed qualify). -func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { +func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (v1.PodPhase, error) { infos, err := c.Build(namespace, reader) if err != nil { - return core.PodUnknown, err + return v1.PodUnknown, err } info := infos[0] kind := info.Mapping.GroupVersionKind.Kind if kind != "Pod" { - return core.PodUnknown, goerrors.Errorf("%s is not a Pod", info.Name) + return v1.PodUnknown, goerrors.Errorf("%s is not a Pod", info.Name) } if err := c.watchPodUntilComplete(timeout, info); err != nil { - return core.PodUnknown, err + return v1.PodUnknown, err } if err := info.Get(); err != nil { - return core.PodUnknown, err + return v1.PodUnknown, err } - status := info.Object.(*core.Pod).Status.Phase + status := info.Object.(*v1.Pod).Status.Phase return status, nil } @@ -655,15 +657,17 @@ func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Inf } c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout) - _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { + ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout) + defer cancel() + _, err = watchtools.UntilWithoutRetry(ctx, w, func(e watch.Event) (bool, error) { switch e.Type { case watch.Deleted: return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "") } switch t := e.Object.(type) { - case *core.Pod: + case *v1.Pod: switch t.Status.Phase { - case core.PodFailed, core.PodSucceeded: + case v1.PodFailed, v1.PodSucceeded: return true, nil } } @@ -675,7 +679,7 @@ func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Inf //get a kubernetes resources' relation pods // kubernetes resource used select labels to relate pods -func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]core.Pod) (map[string][]core.Pod, error) { +func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]v1.Pod) (map[string][]v1.Pod, error) { if info == nil { return objPods, nil } @@ -695,9 +699,9 @@ func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][] return objPods, nil } - client, _ := c.ClientSet() + client, _ := c.KubernetesClientSet() - pods, err := client.Core().Pods(info.Namespace).List(metav1.ListOptions{ + pods, err := client.CoreV1().Pods(info.Namespace).List(metav1.ListOptions{ FieldSelector: fields.Everything().String(), LabelSelector: labels.Set(selector).AsSelector().String(), }) @@ -722,7 +726,7 @@ func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][] return objPods, nil } -func isFoundPod(podItem []core.Pod, pod core.Pod) bool { +func isFoundPod(podItem []v1.Pod, pod v1.Pod) bool { for _, value := range podItem { if (value.Namespace == pod.Namespace) && (value.Name == pod.Name) { return true @@ -730,7 +734,3 @@ func isFoundPod(podItem []core.Pod, pod core.Pod) bool { } return false } - -func asVersioned(info *resource.Info) runtime.Object { - return cmdutil.AsDefaultVersionedOrOriginal(info.Object, info.Mapping) -} diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index acfcd0e35..84dc6fc6e 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -24,52 +24,51 @@ import ( "strings" "testing" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/genericclioptions/resource" "k8s.io/client-go/rest/fake" - "k8s.io/kubernetes/pkg/api/legacyscheme" - "k8s.io/kubernetes/pkg/api/testapi" - "k8s.io/kubernetes/pkg/apis/core" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" "k8s.io/kubernetes/pkg/kubectl/scheme" ) var unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer +var codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) -func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { +func objBody(obj runtime.Object) io.ReadCloser { return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) } -func newPod(name string) core.Pod { - return newPodWithStatus(name, core.PodStatus{}, "") +func newPod(name string) v1.Pod { + return newPodWithStatus(name, v1.PodStatus{}, "") } -func newPodWithStatus(name string, status core.PodStatus, namespace string) core.Pod { - ns := core.NamespaceDefault +func newPodWithStatus(name string, status v1.PodStatus, namespace string) v1.Pod { + ns := v1.NamespaceDefault if namespace != "" { ns = namespace } - return core.Pod{ + return v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: ns, SelfLink: "/api/v1/namespaces/default/pods/" + name, }, - Spec: core.PodSpec{ - Containers: []core.Container{{ + Spec: v1.PodSpec{ + Containers: []v1.Container{{ Name: "app:v4", Image: "abc/app:v4", - Ports: []core.ContainerPort{{Name: "http", ContainerPort: 80}}, + Ports: []v1.ContainerPort{{Name: "http", ContainerPort: 80}}, }}, }, Status: status, } } -func newPodList(names ...string) core.PodList { - var list core.PodList +func newPodList(names ...string) v1.PodList { + var list v1.PodList for _, name := range names { list.Items = append(list.Items, newPod(name)) } @@ -89,7 +88,7 @@ func notFoundBody() *metav1.Status { func newResponse(code int, obj runtime.Object) (*http.Response, error) { header := http.Header{} header.Set("Content-Type", runtime.ContentTypeJSON) - body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), obj)))) + body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) return &http.Response{StatusCode: code, Header: header, Body: body}, nil } @@ -108,12 +107,12 @@ func TestUpdate(t *testing.T) { listA := newPodList("starfish", "otter", "squid") listB := newPodList("starfish", "otter", "dolphin") listC := newPodList("starfish", "otter", "dolphin") - listB.Items[0].Spec.Containers[0].Ports = []core.ContainerPort{{Name: "https", ContainerPort: 443}} - listC.Items[0].Spec.Containers[0].Ports = []core.ContainerPort{{Name: "https", ContainerPort: 443}} + listB.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}} + listC.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}} var actions []string - tf := cmdtesting.NewTestFactory() + tf := cmdtesting.NewTestFactory().WithNamespace("default") defer tf.Cleanup() tf.UnstructuredClient = &fake.RESTClient{ NegotiatedSerializer: unstructuredSerializer, @@ -154,8 +153,7 @@ func TestUpdate(t *testing.T) { Factory: tf, Log: nopLogger, } - codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) - if err := c.Update(core.NamespaceDefault, objBody(codec, &listA), objBody(codec, &listB), false, false, 0, false); err != nil { + if err := c.Update(v1.NamespaceDefault, objBody(&listA), objBody(&listB), false, false, 0, false); err != nil { t.Fatal(err) } // TODO: Find a way to test methods that use Client Set diff --git a/pkg/kube/config.go b/pkg/kube/config.go index 602ec8c6d..2e430d5ac 100644 --- a/pkg/kube/config.go +++ b/pkg/kube/config.go @@ -16,9 +16,7 @@ limitations under the License. package kube // import "k8s.io/helm/pkg/kube" -import ( - "k8s.io/kubernetes/pkg/kubectl/genericclioptions" -) +import "k8s.io/cli-runtime/pkg/genericclioptions" // GetConfig returns a Kubernetes client config. func GetConfig(kubeconfig, context, namespace string) *genericclioptions.ConfigFlags { diff --git a/pkg/kube/converter.go b/pkg/kube/converter.go index 32661f645..b056129e9 100644 --- a/pkg/kube/converter.go +++ b/pkg/kube/converter.go @@ -17,24 +17,21 @@ limitations under the License. package kube // import "k8s.io/helm/pkg/kube" import ( - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/cli-runtime/pkg/genericclioptions/resource" "k8s.io/kubernetes/pkg/api/legacyscheme" ) -// AsDefaultVersionedOrOriginal returns the object as a Go object in the external form if possible (matching the -// group version kind of the mapping if provided, a best guess based on serialization if not provided, or obj if it cannot be converted. -// TODO update call sites to specify the scheme they want on their builder. -func AsDefaultVersionedOrOriginal(obj runtime.Object, mapping *meta.RESTMapping) runtime.Object { +func asVersioned(info *resource.Info) runtime.Object { converter := runtime.ObjectConvertor(legacyscheme.Scheme) groupVersioner := runtime.GroupVersioner(schema.GroupVersions(legacyscheme.Scheme.PrioritizedVersionsAllGroups())) - if mapping != nil { - groupVersioner = mapping.GroupVersionKind.GroupVersion() + if info.Mapping != nil { + groupVersioner = info.Mapping.GroupVersionKind.GroupVersion() } - if obj, err := converter.ConvertToVersion(obj, groupVersioner); err == nil { + if obj, err := converter.ConvertToVersion(info.Object, groupVersioner); err == nil { return obj } - return obj + return info.Object } diff --git a/pkg/kube/factory.go b/pkg/kube/factory.go new file mode 100644 index 000000000..26dad8379 --- /dev/null +++ b/pkg/kube/factory.go @@ -0,0 +1,38 @@ +/* +Copyright The Helm 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 kube // import "k8s.io/helm/pkg/kube" + +import ( + "k8s.io/cli-runtime/pkg/genericclioptions/resource" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/kubernetes/pkg/kubectl/validation" +) + +// Factory provides abstractions that allow the Kubectl command to be extended across multiple types +// of resources and different API sets. +type Factory interface { + // ToRawKubeConfigLoader return kubeconfig loader as-is + ToRawKubeConfigLoader() clientcmd.ClientConfig + // KubernetesClientSet gives you back an external clientset + KubernetesClientSet() (*kubernetes.Clientset, error) + // NewBuilder returns an object that assists in loading objects from both disk and the server + // and which implements the common patterns for CLI interactions with generic resources. + NewBuilder() *resource.Builder + // Returns a schema that can validate objects stored on disk. + Validator(validate bool) (validation.Schema, error) +} diff --git a/pkg/kube/log.go b/pkg/kube/log.go index fc3683b1d..24dafc9e0 100644 --- a/pkg/kube/log.go +++ b/pkg/kube/log.go @@ -24,7 +24,7 @@ import ( func init() { if level := os.Getenv("KUBE_LOG_LEVEL"); level != "" { - flag.Set("vmodule", fmt.Sprintf("loader=%s,round_trippers=%s,request=%s", level, level, level)) + flag.Set("vmodule", fmt.Sprintf("loader=%[1]s,round_trippers=%[1]s,request=%[1]s", level)) flag.Set("logtostderr", "true") } } diff --git a/pkg/kube/result.go b/pkg/kube/result.go index 0c6289e49..cc222a66f 100644 --- a/pkg/kube/result.go +++ b/pkg/kube/result.go @@ -16,7 +16,7 @@ limitations under the License. package kube // import "k8s.io/helm/pkg/kube" -import "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" +import "k8s.io/cli-runtime/pkg/genericclioptions/resource" // Result provides convenience methods for comparing collections of Infos. type Result []*resource.Info diff --git a/pkg/kube/result_test.go b/pkg/kube/result_test.go index 503473c05..c4cf989b8 100644 --- a/pkg/kube/result_test.go +++ b/pkg/kube/result_test.go @@ -21,7 +21,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" + "k8s.io/cli-runtime/pkg/genericclioptions/resource" ) func TestResult(t *testing.T) { diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index 960409df9..ebd12b4b1 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -29,8 +29,6 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" - podutil "k8s.io/kubernetes/pkg/api/v1/pod" - "k8s.io/kubernetes/pkg/apis/core/v1/helper" deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" ) @@ -50,11 +48,13 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { return err } return wait.Poll(2*time.Second, timeout, func() (bool, error) { - pods := []v1.Pod{} - services := []v1.Service{} - pvc := []v1.PersistentVolumeClaim{} - deployments := []deployment{} - for _, v := range created { + var ( + pods []v1.Pod + services []v1.Service + pvc []v1.PersistentVolumeClaim + deployments []deployment + ) + for _, v := range created[:0] { switch value := asVersioned(v).(type) { case *v1.ReplicationController: list, err := getPods(kcs, value.Namespace, value.Spec.Selector) @@ -203,7 +203,7 @@ func (c *Client) waitForResources(timeout time.Duration, created Result) error { func (c *Client) podsReady(pods []v1.Pod) bool { for _, pod := range pods { - if !podutil.IsPodReady(&pod) { + if !IsPodReady(&pod) { c.Log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName()) return false } @@ -211,6 +211,16 @@ func (c *Client) podsReady(pods []v1.Pod) bool { return true } +// IsPodReady returns true if a pod is ready; false otherwise. +func IsPodReady(pod *v1.Pod) bool { + for _, c := range pod.Status.Conditions { + if c.Type == v1.PodReady && c.Status == v1.ConditionTrue { + return true + } + } + return false +} + func (c *Client) servicesReady(svc []v1.Service) bool { for _, s := range svc { // ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set) @@ -219,7 +229,7 @@ func (c *Client) servicesReady(svc []v1.Service) bool { } // Make sure the service is not explicitly set to "None" before checking the IP - if s.Spec.ClusterIP != v1.ClusterIPNone && !helper.IsServiceIPSet(&s) { + if s.Spec.ClusterIP != v1.ClusterIPNone && !IsServiceIPSet(&s) { c.Log("Service is not ready: %s/%s", s.GetNamespace(), s.GetName()) return false } @@ -232,6 +242,12 @@ func (c *Client) servicesReady(svc []v1.Service) bool { return true } +// this function aims to check if the service's ClusterIP is set or not +// the objective is not to perform validation here +func IsServiceIPSet(service *v1.Service) bool { + return service.Spec.ClusterIP != v1.ClusterIPNone && service.Spec.ClusterIP != "" +} + func (c *Client) volumesReady(vols []v1.PersistentVolumeClaim) bool { for _, v := range vols { if v.Status.Phase != v1.ClaimBound { diff --git a/pkg/releasetesting/environment.go b/pkg/releasetesting/environment.go index 1899f672d..fcb52e84d 100644 --- a/pkg/releasetesting/environment.go +++ b/pkg/releasetesting/environment.go @@ -22,7 +22,7 @@ import ( "log" "time" - "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/api/core/v1" "k8s.io/helm/pkg/hapi" "k8s.io/helm/pkg/hapi/release" @@ -48,7 +48,7 @@ func (env *Environment) createTestPod(test *test) error { return nil } -func (env *Environment) getTestPodStatus(test *test) (core.PodPhase, error) { +func (env *Environment) getTestPodStatus(test *test) (v1.PodPhase, error) { b := bytes.NewBufferString(test.manifest) status, err := env.KubeClient.WaitAndGetCompletedPodPhase(env.Namespace, b, time.Duration(env.Timeout)*time.Second) if err != nil { diff --git a/pkg/releasetesting/test_suite.go b/pkg/releasetesting/test_suite.go index 55206af4a..590f01370 100644 --- a/pkg/releasetesting/test_suite.go +++ b/pkg/releasetesting/test_suite.go @@ -22,7 +22,7 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" - "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/api/core/v1" "k8s.io/helm/pkg/hapi/release" "k8s.io/helm/pkg/hooks" @@ -82,7 +82,7 @@ func (ts *TestSuite) Run(env *Environment) error { } resourceCleanExit := true - status := core.PodUnknown + status := v1.PodUnknown if resourceCreated { status, err = env.getTestPodStatus(test) if err != nil { @@ -111,15 +111,15 @@ func (ts *TestSuite) Run(env *Environment) error { return nil } -func (t *test) assignTestResult(podStatus core.PodPhase) error { +func (t *test) assignTestResult(podStatus v1.PodPhase) error { switch podStatus { - case core.PodSucceeded: + case v1.PodSucceeded: if t.expectedSuccess { t.result.Status = release.TestRunSuccess } else { t.result.Status = release.TestRunFailure } - case core.PodFailed: + case v1.PodFailed: if !t.expectedSuccess { t.result.Status = release.TestRunSuccess } else { diff --git a/pkg/releasetesting/test_suite_test.go b/pkg/releasetesting/test_suite_test.go index 04e5cea89..4e8d152a6 100644 --- a/pkg/releasetesting/test_suite_test.go +++ b/pkg/releasetesting/test_suite_test.go @@ -21,7 +21,7 @@ import ( "testing" "time" - "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/api/core/v1" "k8s.io/helm/pkg/hapi" "k8s.io/helm/pkg/hapi/release" @@ -250,11 +250,11 @@ type mockKubeClient struct { err error } -func (c *mockKubeClient) WaitAndGetCompletedPodPhase(_ string, _ io.Reader, _ time.Duration) (core.PodPhase, error) { +func (c *mockKubeClient) WaitAndGetCompletedPodPhase(_ string, _ io.Reader, _ time.Duration) (v1.PodPhase, error) { if c.podFail { - return core.PodFailed, nil + return v1.PodFailed, nil } - return core.PodSucceeded, nil + return v1.PodSucceeded, nil } func (c *mockKubeClient) Get(_ string, _ io.Reader) (string, error) { return "", nil diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index 39d881a9c..8a166fe23 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -26,8 +26,8 @@ import ( "io" "time" - "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" + "k8s.io/api/core/v1" + "k8s.io/cli-runtime/pkg/genericclioptions/resource" "k8s.io/helm/pkg/kube" ) @@ -82,7 +82,7 @@ type KubeClient interface { // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase // and returns said phase (PodSucceeded or PodFailed qualify). - WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) + WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (v1.PodPhase, error) } // PrintingKubeClient implements KubeClient, but simply prints the reader to @@ -134,7 +134,7 @@ func (p *PrintingKubeClient) BuildUnstructured(ns string, reader io.Reader) (kub } // WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase. -func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { +func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (v1.PodPhase, error) { _, err := io.Copy(p.Out, reader) - return core.PodUnknown, err + return v1.PodUnknown, err } diff --git a/pkg/tiller/environment/environment_test.go b/pkg/tiller/environment/environment_test.go index a98ceb180..fe458479f 100644 --- a/pkg/tiller/environment/environment_test.go +++ b/pkg/tiller/environment/environment_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" + "k8s.io/api/core/v1" + "k8s.io/cli-runtime/pkg/genericclioptions/resource" "k8s.io/helm/pkg/kube" ) @@ -51,11 +51,11 @@ func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) func (k *mockKubeClient) BuildUnstructured(ns string, reader io.Reader) (kube.Result, error) { return []*resource.Info{}, nil } -func (k *mockKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { - return core.PodUnknown, nil +func (k *mockKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (v1.PodPhase, error) { + return v1.PodUnknown, nil } -func (k *mockKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { +func (k *mockKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (v1.PodPhase, error) { return "", nil } diff --git a/pkg/tiller/release_server_test.go b/pkg/tiller/release_server_test.go index 70a2dc31c..3115341b6 100644 --- a/pkg/tiller/release_server_test.go +++ b/pkg/tiller/release_server_test.go @@ -28,9 +28,9 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" + "k8s.io/api/core/v1" + "k8s.io/cli-runtime/pkg/genericclioptions/resource" "k8s.io/client-go/kubernetes/fake" - "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" "k8s.io/helm/pkg/chart" "k8s.io/helm/pkg/engine" @@ -492,8 +492,8 @@ func (kc *mockHooksKubeClient) Build(_ string, _ io.Reader) (kube.Result, error) func (kc *mockHooksKubeClient) BuildUnstructured(_ string, _ io.Reader) (kube.Result, error) { return []*resource.Info{}, nil } -func (kc *mockHooksKubeClient) WaitAndGetCompletedPodPhase(_ string, _ io.Reader, _ time.Duration) (core.PodPhase, error) { - return core.PodUnknown, nil +func (kc *mockHooksKubeClient) WaitAndGetCompletedPodPhase(_ string, _ io.Reader, _ time.Duration) (v1.PodPhase, error) { + return v1.PodUnknown, nil } func deletePolicyStub(kubeClient *mockHooksKubeClient) *ReleaseServer {