From 9902cde76edc903813960f0f108445f9a22ffdaa Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Sat, 30 Jun 2018 22:22:05 -0700 Subject: [PATCH 1/6] deploy: use dynamic client to patch resources with labels --- pkg/skaffold/deploy/labels.go | 288 +++++++++--------------------- pkg/skaffold/kubernetes/client.go | 21 ++- pkg/skaffold/kubernetes/log.go | 1 + 3 files changed, 104 insertions(+), 206 deletions(-) diff --git a/pkg/skaffold/deploy/labels.go b/pkg/skaffold/deploy/labels.go index f27fd9d6621..21ef4d582b9 100644 --- a/pkg/skaffold/deploy/labels.go +++ b/pkg/skaffold/deploy/labels.go @@ -19,23 +19,25 @@ package deploy import ( "context" "encoding/json" + "fmt" "io" "time" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" + kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" + + "github.com/pkg/errors" "github.com/sirupsen/logrus" - appsv1 "k8s.io/api/apps/v1" - appsv1beta1 "k8s.io/api/apps/v1beta1" - appsv1beta2 "k8s.io/api/apps/v1beta2" - corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + "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/types" patch "k8s.io/apimachinery/pkg/util/strategicpatch" - clientgo "k8s.io/client-go/kubernetes" + + "k8s.io/client-go/discovery" + "k8s.io/client-go/dynamic" ) // Labeller can give key/value labels to set on deployed resources. @@ -80,176 +82,21 @@ func merge(sources ...Labeller) map[string]string { return merged } -type objectType int - -// List of API Objects supported by the Skaffold Labeler +// retry 3 times to give the object time to propagate to the API server const ( - _ = iota - corev1Pod - appsv1Deployment - appsv1Beta1Deployment - appsv1Beta2Deployment - extensionsv1Beta1Deployment - corev1Service - appv1StatefulSet - appsv1Beta1StatefulSet - appsv1Beta2StatefulSet - extensionsv1Beta1DaemonSet - appsv1ReplicaSet - appsv1Beta2ReplicaSet + tries = 3 + sleeptime = 300 * time.Millisecond ) -// patcher is responsible for applying a given patch to the provided object -type patcher func(clientgo.Interface, string, string, []byte) error - -// objectMeta is responsible for returning a generic runtime.Object's metadata -type objectMeta func(runtime.Object) (*metav1.ObjectMeta, bool) - -var patchers = map[objectType]patcher{ - corev1Pod: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.CoreV1().Pods(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - appsv1Deployment: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.AppsV1().Deployments(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - appsv1Beta1Deployment: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.AppsV1beta1().Deployments(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - appsv1Beta2Deployment: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.AppsV1beta2().Deployments(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - extensionsv1Beta1Deployment: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.ExtensionsV1beta1().Deployments(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - corev1Service: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.CoreV1().Services(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - appv1StatefulSet: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.AppsV1().StatefulSets(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - appsv1Beta1StatefulSet: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.AppsV1beta1().StatefulSets(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - appsv1Beta2StatefulSet: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.AppsV1beta2().StatefulSets(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - extensionsv1Beta1DaemonSet: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.ExtensionsV1beta1().DaemonSets(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - appsv1ReplicaSet: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.AppsV1().ReplicaSets(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, - appsv1Beta2ReplicaSet: func(client clientgo.Interface, ns string, name string, p []byte) error { - _, err := client.AppsV1beta2().ReplicaSets(ns).Patch(name, types.StrategicMergePatchType, p) - return err - }, -} - -var objectMetas = map[objectType]objectMeta{ - corev1Pod: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*corev1.Pod) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - appsv1Deployment: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*appsv1.Deployment) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - appsv1Beta1Deployment: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*appsv1beta1.Deployment) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - appsv1Beta2Deployment: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*appsv1beta2.Deployment) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - extensionsv1Beta1Deployment: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*extensionsv1beta1.Deployment) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - corev1Service: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*corev1.Service) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - appv1StatefulSet: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*appsv1.StatefulSet) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - appsv1Beta1StatefulSet: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*appsv1beta1.StatefulSet) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - appsv1Beta2StatefulSet: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*appsv1beta1.StatefulSet) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - extensionsv1Beta1DaemonSet: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*extensionsv1beta1.DaemonSet) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - appsv1ReplicaSet: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*appsv1.ReplicaSet) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, - appsv1Beta2ReplicaSet: func(r runtime.Object) (*metav1.ObjectMeta, bool) { - obj, ok := r.(*appsv1beta2.ReplicaSet) - if !ok { - return nil, ok - } - return &obj.ObjectMeta, ok - }, -} - -// retry 3 times to give the object time to propagate to the API server -const tries int = 3 -const sleeptime time.Duration = 300 * time.Millisecond - func labelDeployResults(labels map[string]string, results []Artifact) { // use the kubectl client to update all k8s objects with a skaffold watermark - client, err := kubernetes.Client() + dynClient, err := kubernetes.DynamicClient() + if err != nil { + logrus.Warnf("error retrieving kubernetes client: %s", err.Error()) + return + } + + client, err := kubernetes.GetClientset() if err != nil { logrus.Warnf("error retrieving kubernetes client: %s", err.Error()) return @@ -258,7 +105,7 @@ func labelDeployResults(labels map[string]string, results []Artifact) { for _, res := range results { err = nil for i := 0; i < tries; i++ { - if err = updateRuntimeObject(client, labels, res); err == nil { + if err = updateRuntimeObject(dynClient, client.Discovery(), labels, res); err == nil { break } time.Sleep(sleeptime) @@ -269,46 +116,81 @@ func labelDeployResults(labels map[string]string, results []Artifact) { } } -func addSkaffoldLabels(labels map[string]string, m *metav1.ObjectMeta) { - if m.Labels == nil { - m.Labels = map[string]string{} +func addLabels(labels map[string]string, accessor metav1.Object) { + for k, v := range constants.Labels.DefaultLabels { + labels[k] = v + } + + objLabels := accessor.GetLabels() + if objLabels == nil { + objLabels = make(map[string]string) + } + + for key, value := range labels { + objLabels[key] = value + } + accessor.SetLabels(objLabels) +} + +func updateRuntimeObject(client dynamic.Interface, disco discovery.DiscoveryInterface, labels map[string]string, res Artifact) error { + originalJSON, _ := json.Marshal(*res.Obj) + modifiedObj := (*res.Obj).DeepCopyObject() + accessor, err := meta.Accessor(modifiedObj) + if err != nil { + return errors.Wrap(err, "getting metadata accessor") + } + name := accessor.GetName() + namespace := accessor.GetNamespace() + addLabels(labels, accessor) + + modifiedJSON, _ := json.Marshal(modifiedObj) + p, _ := patch.CreateTwoWayMergePatch(originalJSON, modifiedJSON, modifiedObj) + gvr, err := groupVersionResource(disco, modifiedObj.GetObjectKind().GroupVersionKind()) + if err != nil { + return errors.Wrap(err, "getting group version resource from obj") + } + ns, err := resolveNamespace(namespace) + if err != nil { + return errors.Wrap(err, "resolving namespace") } - for k, v := range labels { - m.Labels[k] = v + if _, err := client.Resource(gvr).Namespace(ns).Patch(name, types.StrategicMergePatchType, p); err != nil { + return errors.Wrapf(err, "patching resource %s/%s", namespace, name) } + + return err } -func retrieveNamespace(ns string, m metav1.ObjectMeta) string { +func resolveNamespace(ns string) (string, error) { if ns != "" { - return ns + return ns, nil } - if m.Namespace != "" { - return m.Namespace + cfg, err := kubectx.CurrentConfig() + if err != nil { + return "", errors.Wrap(err, "getting kubeconfig") } - return "default" + + current, present := cfg.Contexts[cfg.CurrentContext] + if present { + return current.Namespace, nil + } + return "default", nil } -// TODO(nkubala): change this to use the client-go dynamic client or something equally clean -func updateRuntimeObject(client clientgo.Interface, labels map[string]string, res Artifact) error { - for k, v := range constants.Labels.DefaultLabels { - labels[k] = v +func groupVersionResource(disco discovery.DiscoveryInterface, gvk schema.GroupVersionKind) (schema.GroupVersionResource, error) { + resources, err := disco.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) + if err != nil { + return schema.GroupVersionResource{}, errors.Wrap(err, "getting server resources for group version") } - var err error - applied := false - var metadata *metav1.ObjectMeta - originalJSON, _ := json.Marshal(*res.Obj) - modifiedObj := (*res.Obj).DeepCopyObject() - for typeStr, m := range objectMetas { - if metadata, applied = m(modifiedObj); applied { - addSkaffoldLabels(labels, metadata) - modifiedJSON, _ := json.Marshal(modifiedObj) - p, _ := patch.CreateTwoWayMergePatch(originalJSON, modifiedJSON, modifiedObj) - err = patchers[typeStr](client, retrieveNamespace(res.Namespace, *metadata), metadata.GetName(), p) - break // we should only ever apply one patch, so stop here + + for _, r := range resources.APIResources { + if r.Kind == gvk.Kind { + return schema.GroupVersionResource{ + Group: gvk.Group, + Version: gvk.Version, + Resource: r.Name, + }, nil } } - if !applied { - logrus.Infof("unknown runtime.Object, skipping label") - } - return err + + return schema.GroupVersionResource{}, fmt.Errorf("Could not find resource for %s", gvk.String()) } diff --git a/pkg/skaffold/kubernetes/client.go b/pkg/skaffold/kubernetes/client.go index 7af24dde308..088b9025227 100644 --- a/pkg/skaffold/kubernetes/client.go +++ b/pkg/skaffold/kubernetes/client.go @@ -20,22 +20,37 @@ import ( "fmt" "github.com/pkg/errors" + "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" + restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + // Initialize all known client auth plugins _ "k8s.io/client-go/plugin/pkg/client/auth" ) func GetClientset() (kubernetes.Interface, error) { + config, err := getClientConfig() + if err != nil { + return nil, errors.Wrap(err, "getting client config for kubernetes client") + } + return kubernetes.NewForConfig(config) +} + +func getClientConfig() (*restclient.Config, error) { loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) clientConfig, err := kubeConfig.ClientConfig() if err != nil { return nil, fmt.Errorf("Error creating kubeConfig: %s", err) } - client, err := kubernetes.NewForConfig(clientConfig) + return clientConfig, nil +} + +func GetDynamicClient() (dynamic.Interface, error) { + config, err := getClientConfig() if err != nil { - return nil, errors.Wrap(err, "Error creating new client from kubeConfig.ClientConfig()") + return nil, errors.Wrap(err, "getting client config for dynamic client") } - return client, nil + return dynamic.NewForConfig(config) } diff --git a/pkg/skaffold/kubernetes/log.go b/pkg/skaffold/kubernetes/log.go index 77cf30b916c..d27a0f627fe 100644 --- a/pkg/skaffold/kubernetes/log.go +++ b/pkg/skaffold/kubernetes/log.go @@ -35,6 +35,7 @@ import ( // Client is for tests var Client = GetClientset +var DynamicClient = GetDynamicClient // LogAggregator aggregates the logs for all the deployed pods. type LogAggregator struct { From a183b5cbfa2d387a19f1a0c51219294ad8bfecae Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Sat, 30 Jun 2018 22:22:31 -0700 Subject: [PATCH 2/6] vendor: add k8s.io/client-go/dynamic --- .../k8s.io/apimachinery/pkg/api/meta/meta.go | 2 + vendor/k8s.io/client-go/dynamic/interface.go | 59 ++++ vendor/k8s.io/client-go/dynamic/scheme.go | 98 ++++++ vendor/k8s.io/client-go/dynamic/simple.go | 287 ++++++++++++++++++ 4 files changed, 446 insertions(+) create mode 100644 vendor/k8s.io/client-go/dynamic/interface.go create mode 100644 vendor/k8s.io/client-go/dynamic/scheme.go create mode 100644 vendor/k8s.io/client-go/dynamic/simple.go diff --git a/vendor/k8s.io/apimachinery/pkg/api/meta/meta.go b/vendor/k8s.io/apimachinery/pkg/api/meta/meta.go index 1c2a83cfacb..0062ae22466 100644 --- a/vendor/k8s.io/apimachinery/pkg/api/meta/meta.go +++ b/vendor/k8s.io/apimachinery/pkg/api/meta/meta.go @@ -20,6 +20,7 @@ import ( "fmt" "reflect" + "github.com/davecgh/go-spew/spew" "github.com/golang/glog" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -109,6 +110,7 @@ func Accessor(obj interface{}) (metav1.Object, error) { } return nil, errNotObject default: + spew.Dump(t) return nil, errNotObject } } diff --git a/vendor/k8s.io/client-go/dynamic/interface.go b/vendor/k8s.io/client-go/dynamic/interface.go new file mode 100644 index 00000000000..3f364f872a7 --- /dev/null +++ b/vendor/k8s.io/client-go/dynamic/interface.go @@ -0,0 +1,59 @@ +/* +Copyright 2016 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 dynamic + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" +) + +type Interface interface { + Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface +} + +type ResourceInterface interface { + Create(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) + Update(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) + UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) + Delete(name string, options *metav1.DeleteOptions, subresources ...string) error + DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error + Get(name string, options metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) + List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) + Watch(opts metav1.ListOptions) (watch.Interface, error) + Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) +} + +type NamespaceableResourceInterface interface { + Namespace(string) ResourceInterface + ResourceInterface +} + +// APIPathResolverFunc knows how to convert a groupVersion to its API path. The Kind field is optional. +// TODO find a better place to move this for existing callers +type APIPathResolverFunc func(kind schema.GroupVersionKind) string + +// LegacyAPIPathResolverFunc can resolve paths properly with the legacy API. +// TODO find a better place to move this for existing callers +func LegacyAPIPathResolverFunc(kind schema.GroupVersionKind) string { + if len(kind.Group) == 0 { + return "/api" + } + return "/apis" +} diff --git a/vendor/k8s.io/client-go/dynamic/scheme.go b/vendor/k8s.io/client-go/dynamic/scheme.go new file mode 100644 index 00000000000..c4aa081f91f --- /dev/null +++ b/vendor/k8s.io/client-go/dynamic/scheme.go @@ -0,0 +1,98 @@ +/* +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 dynamic + +import ( + 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/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/runtime/serializer/versioning" +) + +var watchScheme = runtime.NewScheme() +var basicScheme = runtime.NewScheme() +var deleteScheme = runtime.NewScheme() +var parameterScheme = runtime.NewScheme() +var deleteOptionsCodec = serializer.NewCodecFactory(deleteScheme) +var dynamicParameterCodec = runtime.NewParameterCodec(parameterScheme) + +var versionV1 = schema.GroupVersion{Version: "v1"} + +func init() { + metav1.AddToGroupVersion(watchScheme, versionV1) + metav1.AddToGroupVersion(basicScheme, versionV1) + metav1.AddToGroupVersion(parameterScheme, versionV1) + metav1.AddToGroupVersion(deleteScheme, versionV1) +} + +var watchJsonSerializerInfo = runtime.SerializerInfo{ + MediaType: "application/json", + EncodesAsText: true, + Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false), + PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, true), + StreamSerializer: &runtime.StreamSerializerInfo{ + EncodesAsText: true, + Serializer: json.NewSerializer(json.DefaultMetaFactory, watchScheme, watchScheme, false), + Framer: json.Framer, + }, +} + +// watchNegotiatedSerializer is used to read the wrapper of the watch stream +type watchNegotiatedSerializer struct{} + +var watchNegotiatedSerializerInstance = watchNegotiatedSerializer{} + +func (s watchNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo { + return []runtime.SerializerInfo{watchJsonSerializerInfo} +} + +func (s watchNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { + return versioning.NewDefaultingCodecForScheme(watchScheme, encoder, nil, gv, nil) +} + +func (s watchNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { + return versioning.NewDefaultingCodecForScheme(watchScheme, nil, decoder, nil, gv) +} + +// basicNegotiatedSerializer is used to handle discovery and error handling serialization +type basicNegotiatedSerializer struct{} + +func (s basicNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo { + return []runtime.SerializerInfo{ + { + MediaType: "application/json", + EncodesAsText: true, + Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false), + PrettySerializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, true), + StreamSerializer: &runtime.StreamSerializerInfo{ + EncodesAsText: true, + Serializer: json.NewSerializer(json.DefaultMetaFactory, basicScheme, basicScheme, false), + Framer: json.Framer, + }, + }, + } +} + +func (s basicNegotiatedSerializer) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { + return versioning.NewDefaultingCodecForScheme(watchScheme, encoder, nil, gv, nil) +} + +func (s basicNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { + return versioning.NewDefaultingCodecForScheme(watchScheme, nil, decoder, nil, gv) +} diff --git a/vendor/k8s.io/client-go/dynamic/simple.go b/vendor/k8s.io/client-go/dynamic/simple.go new file mode 100644 index 00000000000..88e9cc2b06b --- /dev/null +++ b/vendor/k8s.io/client-go/dynamic/simple.go @@ -0,0 +1,287 @@ +/* +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 dynamic + +import ( + "io" + + "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/runtime/serializer/streaming" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/rest" +) + +type dynamicClient struct { + client *rest.RESTClient +} + +var _ Interface = &dynamicClient{} + +func NewForConfig(inConfig *rest.Config) (Interface, error) { + config := rest.CopyConfig(inConfig) + // for serializing the options + config.GroupVersion = &schema.GroupVersion{} + config.APIPath = "/if-you-see-this-search-for-the-break" + config.AcceptContentTypes = "application/json" + config.ContentType = "application/json" + config.NegotiatedSerializer = basicNegotiatedSerializer{} // this gets used for discovery and error handling types + if config.UserAgent == "" { + config.UserAgent = rest.DefaultKubernetesUserAgent() + } + + restClient, err := rest.RESTClientFor(config) + if err != nil { + return nil, err + } + + return &dynamicClient{client: restClient}, nil +} + +type dynamicResourceClient struct { + client *dynamicClient + namespace string + resource schema.GroupVersionResource +} + +func (c *dynamicClient) Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface { + return &dynamicResourceClient{client: c, resource: resource} +} + +func (c *dynamicResourceClient) Namespace(ns string) ResourceInterface { + ret := *c + ret.namespace = ns + return &ret +} + +func (c *dynamicResourceClient) Create(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) { + outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) + if err != nil { + return nil, err + } + name := "" + if len(subresources) > 0 { + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + name = accessor.GetName() + } + + result := c.client.client.Post().AbsPath(append(c.makeURLSegments(name), subresources...)...).Body(outBytes).Do() + if err := result.Error(); err != nil { + return nil, err + } + + retBytes, err := result.Raw() + if err != nil { + return nil, err + } + uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) + if err != nil { + return nil, err + } + return uncastObj.(*unstructured.Unstructured), nil +} + +func (c *dynamicResourceClient) Update(obj *unstructured.Unstructured, subresources ...string) (*unstructured.Unstructured, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) + if err != nil { + return nil, err + } + + result := c.client.client.Put().AbsPath(append(c.makeURLSegments(accessor.GetName()), subresources...)...).Body(outBytes).Do() + if err := result.Error(); err != nil { + return nil, err + } + + retBytes, err := result.Raw() + if err != nil { + return nil, err + } + uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) + if err != nil { + return nil, err + } + return uncastObj.(*unstructured.Unstructured), nil +} + +func (c *dynamicResourceClient) UpdateStatus(obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { + accessor, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + + outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) + if err != nil { + return nil, err + } + + result := c.client.client.Put().AbsPath(append(c.makeURLSegments(accessor.GetName()), "status")...).Body(outBytes).Do() + if err := result.Error(); err != nil { + return nil, err + } + + retBytes, err := result.Raw() + if err != nil { + return nil, err + } + uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) + if err != nil { + return nil, err + } + return uncastObj.(*unstructured.Unstructured), nil +} + +func (c *dynamicResourceClient) Delete(name string, opts *metav1.DeleteOptions, subresources ...string) error { + if opts == nil { + opts = &metav1.DeleteOptions{} + } + deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts) + if err != nil { + return err + } + + result := c.client.client.Delete().AbsPath(append(c.makeURLSegments(name), subresources...)...).Body(deleteOptionsByte).Do() + return result.Error() +} + +func (c *dynamicResourceClient) DeleteCollection(opts *metav1.DeleteOptions, listOptions metav1.ListOptions) error { + if opts == nil { + opts = &metav1.DeleteOptions{} + } + deleteOptionsByte, err := runtime.Encode(deleteOptionsCodec.LegacyCodec(schema.GroupVersion{Version: "v1"}), opts) + if err != nil { + return err + } + + result := c.client.client.Delete().AbsPath(c.makeURLSegments("")...).Body(deleteOptionsByte).SpecificallyVersionedParams(&listOptions, dynamicParameterCodec, versionV1).Do() + return result.Error() +} + +func (c *dynamicResourceClient) Get(name string, opts metav1.GetOptions, subresources ...string) (*unstructured.Unstructured, error) { + result := c.client.client.Get().AbsPath(append(c.makeURLSegments(name), subresources...)...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do() + if err := result.Error(); err != nil { + return nil, err + } + retBytes, err := result.Raw() + if err != nil { + return nil, err + } + uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) + if err != nil { + return nil, err + } + return uncastObj.(*unstructured.Unstructured), nil +} + +func (c *dynamicResourceClient) List(opts metav1.ListOptions) (*unstructured.UnstructuredList, error) { + result := c.client.client.Get().AbsPath(c.makeURLSegments("")...).SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).Do() + if err := result.Error(); err != nil { + return nil, err + } + retBytes, err := result.Raw() + if err != nil { + return nil, err + } + uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) + if err != nil { + return nil, err + } + if list, ok := uncastObj.(*unstructured.UnstructuredList); ok { + return list, nil + } + + list, err := uncastObj.(*unstructured.Unstructured).ToList() + if err != nil { + return nil, err + } + return list, nil +} + +func (c *dynamicResourceClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { + internalGV := schema.GroupVersions{ + {Group: c.resource.Group, Version: runtime.APIVersionInternal}, + // always include the legacy group as a decoding target to handle non-error `Status` return types + {Group: "", Version: runtime.APIVersionInternal}, + } + s := &rest.Serializers{ + Encoder: watchNegotiatedSerializerInstance.EncoderForVersion(watchJsonSerializerInfo.Serializer, c.resource.GroupVersion()), + Decoder: watchNegotiatedSerializerInstance.DecoderToVersion(watchJsonSerializerInfo.Serializer, internalGV), + + RenegotiatedDecoder: func(contentType string, params map[string]string) (runtime.Decoder, error) { + return watchNegotiatedSerializerInstance.DecoderToVersion(watchJsonSerializerInfo.Serializer, internalGV), nil + }, + StreamingSerializer: watchJsonSerializerInfo.StreamSerializer.Serializer, + Framer: watchJsonSerializerInfo.StreamSerializer.Framer, + } + + wrappedDecoderFn := func(body io.ReadCloser) streaming.Decoder { + framer := s.Framer.NewFrameReader(body) + return streaming.NewDecoder(framer, s.StreamingSerializer) + } + + opts.Watch = true + return c.client.client.Get().AbsPath(c.makeURLSegments("")...). + SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1). + WatchWithSpecificDecoders(wrappedDecoderFn, unstructured.UnstructuredJSONScheme) +} + +func (c *dynamicResourceClient) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*unstructured.Unstructured, error) { + result := c.client.client.Patch(pt).AbsPath(append(c.makeURLSegments(name), subresources...)...).Body(data).Do() + if err := result.Error(); err != nil { + return nil, err + } + retBytes, err := result.Raw() + if err != nil { + return nil, err + } + uncastObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, retBytes) + if err != nil { + return nil, err + } + return uncastObj.(*unstructured.Unstructured), nil +} + +func (c *dynamicResourceClient) makeURLSegments(name string) []string { + url := []string{} + if len(c.resource.Group) == 0 { + url = append(url, "api") + } else { + url = append(url, "apis", c.resource.Group) + } + url = append(url, c.resource.Version) + + if len(c.namespace) > 0 { + url = append(url, "namespaces", c.namespace) + } + url = append(url, c.resource.Resource) + + if len(name) > 0 { + url = append(url, name) + } + + return url +} From 69c1adde5caf162343fc6a58691ec91623892dc0 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Sat, 30 Jun 2018 22:22:41 -0700 Subject: [PATCH 3/6] vendor: update gopkg.lock --- Gopkg.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gopkg.lock b/Gopkg.lock index 815d4fe9bba..05e9c32826c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -842,6 +842,7 @@ packages = [ "discovery", "discovery/fake", + "dynamic", "kubernetes", "kubernetes/fake", "kubernetes/scheme", @@ -948,6 +949,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "034783fc12bf1a413c89d14f83491cba58dd1481e1e989d535d7a6e5fbfb676c" + inputs-digest = "fb2d9bb478862a716d93eb7a0e3c8c449be8bed8e687f44b287dd2051129eb64" solver-name = "gps-cdcl" solver-version = 1 From 7f9623564758949f12d064a5fa8146ff2aaffa4a Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Sat, 30 Jun 2018 22:40:14 -0700 Subject: [PATCH 4/6] review feedback --- pkg/skaffold/deploy/labels.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/skaffold/deploy/labels.go b/pkg/skaffold/deploy/labels.go index 21ef4d582b9..a728b7498c1 100644 --- a/pkg/skaffold/deploy/labels.go +++ b/pkg/skaffold/deploy/labels.go @@ -157,7 +157,7 @@ func updateRuntimeObject(client dynamic.Interface, disco discovery.DiscoveryInte return errors.Wrapf(err, "patching resource %s/%s", namespace, name) } - return err + return nil } func resolveNamespace(ns string) (string, error) { From 2f119e3f132489421c1cabbd45c4b169843f309f Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Mon, 9 Jul 2018 10:49:59 -0700 Subject: [PATCH 5/6] labels: apply defaults first --- pkg/skaffold/deploy/labels.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/skaffold/deploy/labels.go b/pkg/skaffold/deploy/labels.go index a728b7498c1..d0049f87961 100644 --- a/pkg/skaffold/deploy/labels.go +++ b/pkg/skaffold/deploy/labels.go @@ -117,15 +117,15 @@ func labelDeployResults(labels map[string]string, results []Artifact) { } func addLabels(labels map[string]string, accessor metav1.Object) { - for k, v := range constants.Labels.DefaultLabels { - labels[k] = v - } - objLabels := accessor.GetLabels() if objLabels == nil { objLabels = make(map[string]string) } - + for k, v := range constants.Labels.DefaultLabels { + if _, ok := objLabels[k]; !ok { + objLabels[k] = v + } + } for key, value := range labels { objLabels[key] = value } @@ -170,7 +170,7 @@ func resolveNamespace(ns string) (string, error) { } current, present := cfg.Contexts[cfg.CurrentContext] - if present { + if present && current.Namespace != "" { return current.Namespace, nil } return "default", nil From 0259d8dd6c23dd0c4fdf92674b9fde1fff379899 Mon Sep 17 00:00:00 2001 From: Matt Rickard Date: Mon, 9 Jul 2018 13:57:46 -0700 Subject: [PATCH 6/6] update log message --- pkg/skaffold/deploy/labels.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/skaffold/deploy/labels.go b/pkg/skaffold/deploy/labels.go index d0049f87961..c832f45d034 100644 --- a/pkg/skaffold/deploy/labels.go +++ b/pkg/skaffold/deploy/labels.go @@ -92,7 +92,7 @@ func labelDeployResults(labels map[string]string, results []Artifact) { // use the kubectl client to update all k8s objects with a skaffold watermark dynClient, err := kubernetes.DynamicClient() if err != nil { - logrus.Warnf("error retrieving kubernetes client: %s", err.Error()) + logrus.Warnf("error retrieving kubernetes dynamic client: %s", err.Error()) return }