github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/deploy/label/labels.go (about) 1 /* 2 Copyright 2019 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package label 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "time" 24 25 "k8s.io/apimachinery/pkg/api/meta" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/types" 28 patch "k8s.io/apimachinery/pkg/util/strategicpatch" 29 "k8s.io/client-go/discovery" 30 "k8s.io/client-go/dynamic" 31 32 deploy "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/types" 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/util" 34 kubernetesclient "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/client" 35 kubectx "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/context" 36 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log" 37 ) 38 39 // retry 3 times to give the object time to propagate to the API server 40 const ( 41 tries = 3 42 sleeptime = 300 * time.Millisecond 43 ) 44 45 // Apply applies all provided labels to the created Kubernetes resources 46 func Apply(ctx context.Context, labels map[string]string, results []deploy.Artifact, kubeContext string) error { 47 if len(labels) == 0 { 48 return nil 49 } 50 51 // use the kubectl client to update all k8s objects with a skaffold watermark 52 dynClient, err := kubernetesclient.DynamicClient(kubeContext) 53 if err != nil { 54 return fmt.Errorf("error getting Kubernetes dynamic client: %w", err) 55 } 56 57 client, err := kubernetesclient.Client(kubeContext) 58 if err != nil { 59 return fmt.Errorf("error getting Kubernetes client: %w", err) 60 } 61 62 for _, res := range results { 63 err = nil 64 for i := 0; i < tries; i++ { 65 if err = updateRuntimeObject(ctx, dynClient, client.Discovery(), labels, res, kubeContext); err == nil { 66 break 67 } 68 time.Sleep(sleeptime) 69 } 70 if err != nil { 71 log.Entry(ctx).Warnf("error adding label to runtime object: %s", err.Error()) 72 } 73 } 74 75 return nil 76 } 77 78 func addLabels(labels map[string]string, accessor metav1.Object) { 79 kv := make(map[string]string) 80 81 copyMap(kv, labels) 82 copyMap(kv, accessor.GetLabels()) 83 84 accessor.SetLabels(kv) 85 } 86 87 func updateRuntimeObject(ctx context.Context, client dynamic.Interface, disco discovery.DiscoveryInterface, labels map[string]string, res deploy.Artifact, kubeContext string) error { 88 originalJSON, _ := json.Marshal(res.Obj) 89 modifiedObj := res.Obj.DeepCopyObject() 90 accessor, err := meta.Accessor(modifiedObj) 91 if err != nil { 92 return fmt.Errorf("getting metadata accessor: %w", err) 93 } 94 name := accessor.GetName() 95 96 addLabels(labels, accessor) 97 98 modifiedJSON, _ := json.Marshal(modifiedObj) 99 p, _ := patch.CreateTwoWayMergePatch(originalJSON, modifiedJSON, modifiedObj) 100 101 namespaced, gvr, err := util.GroupVersionResource(disco, modifiedObj.GetObjectKind().GroupVersionKind()) 102 if err != nil { 103 return fmt.Errorf("getting group version resource from obj: %w", err) 104 } 105 106 if namespaced { 107 var namespace string 108 if accessor.GetNamespace() != "" { 109 namespace = accessor.GetNamespace() 110 } else { 111 namespace = res.Namespace 112 } 113 114 ns, err := resolveNamespace(namespace, kubeContext) 115 if err != nil { 116 return fmt.Errorf("resolving namespace: %w", err) 117 } 118 119 log.Entry(ctx).Debug("Patching", name, "in namespace", ns) 120 if _, err := client.Resource(gvr).Namespace(ns).Patch(ctx, name, types.StrategicMergePatchType, p, metav1.PatchOptions{}); err != nil { 121 return fmt.Errorf("patching resource %s/%q: %w", ns, name, err) 122 } 123 } else { 124 log.Entry(ctx).Debug("Patching", name) 125 if _, err := client.Resource(gvr).Patch(ctx, name, types.StrategicMergePatchType, p, metav1.PatchOptions{}); err != nil { 126 return fmt.Errorf("patching resource %q: %w", name, err) 127 } 128 } 129 130 return nil 131 } 132 133 func resolveNamespace(ns, kubeContext string) (string, error) { 134 if ns != "" { 135 return ns, nil 136 } 137 cfg, err := kubectx.CurrentConfig() 138 if err != nil { 139 return "", fmt.Errorf("getting kubeconfig: %w", err) 140 } 141 142 current, present := cfg.Contexts[kubeContext] 143 if present && current.Namespace != "" { 144 return current.Namespace, nil 145 } 146 return "default", nil 147 } 148 149 func copyMap(dest, from map[string]string) { 150 for k, v := range from { 151 dest[k] = v 152 } 153 }