github.com/oam-dev/kubevela@v1.9.11/pkg/component/ref_objects.go (about) 1 /* 2 Copyright 2021 The KubeVela 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 component 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "strings" 24 25 "github.com/kubevela/pkg/util/slices" 26 "github.com/pkg/errors" 27 corev1 "k8s.io/api/core/v1" 28 apierrors "k8s.io/apimachinery/pkg/api/errors" 29 "k8s.io/apimachinery/pkg/api/meta" 30 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 31 "k8s.io/apimachinery/pkg/labels" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 utilfeature "k8s.io/apiserver/pkg/util/feature" 35 "sigs.k8s.io/controller-runtime/pkg/client" 36 37 velaclient "github.com/kubevela/pkg/controller/client" 38 39 "github.com/oam-dev/kubevela/apis/core.oam.dev/common" 40 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1" 41 "github.com/oam-dev/kubevela/pkg/features" 42 "github.com/oam-dev/kubevela/pkg/multicluster" 43 "github.com/oam-dev/kubevela/pkg/oam" 44 ) 45 46 const ( 47 // RefObjectsAvailableScopeGlobal ref-objects component can refer to arbitrary objects in any cluster 48 RefObjectsAvailableScopeGlobal = "global" 49 // RefObjectsAvailableScopeCluster ref-objects component can only refer to objects inside the hub cluster 50 RefObjectsAvailableScopeCluster = "cluster" 51 // RefObjectsAvailableScopeNamespace ref-objects component can only refer to objects inside the application namespace 52 RefObjectsAvailableScopeNamespace = "namespace" 53 ) 54 55 // RefObjectsAvailableScope indicates the available scope for objects to refer 56 var RefObjectsAvailableScope = RefObjectsAvailableScopeGlobal 57 58 // GetLabelSelectorFromRefObjectSelector extract labelSelector from `labelSelector` first. If empty, extract from `selector` 59 func GetLabelSelectorFromRefObjectSelector(selector v1alpha1.ObjectReferrer) map[string]string { 60 if selector.LabelSelector != nil { 61 return selector.LabelSelector 62 } 63 if utilfeature.DefaultMutableFeatureGate.Enabled(features.DeprecatedObjectLabelSelector) { 64 return selector.DeprecatedLabelSelector 65 } 66 return nil 67 } 68 69 // GetGroupVersionKindFromRefObjectSelector extract GroupVersionKind by Resource if provided, otherwise, extract from APIVersion and Kind directly 70 func GetGroupVersionKindFromRefObjectSelector(mapper meta.RESTMapper, selector v1alpha1.ObjectReferrer) (schema.GroupVersionKind, error) { 71 if selector.Resource != "" { 72 gvks, err := mapper.KindsFor(schema.GroupVersionResource{Group: selector.Group, Resource: selector.Resource}) 73 if err != nil { 74 return schema.GroupVersionKind{}, err 75 } 76 if len(gvks) == 0 { 77 return schema.GroupVersionKind{}, errors.Errorf("no kind found for resource %s", selector.Resource) 78 } 79 return gvks[0], nil 80 } 81 if utilfeature.DefaultMutableFeatureGate.Enabled(features.LegacyObjectTypeIdentifier) { 82 if selector.APIVersion != "" && selector.Kind != "" { 83 gv, err := schema.ParseGroupVersion(selector.APIVersion) 84 if err != nil { 85 return schema.GroupVersionKind{}, errors.Wrapf(err, "invalid APIVersion") 86 } 87 return gv.WithKind(selector.Kind), nil 88 } 89 return schema.GroupVersionKind{}, errors.Errorf("neither resource or apiVersion/kind is set for referring objects") 90 } 91 return schema.GroupVersionKind{}, errors.Errorf("resource is not set and legacy object type identifier is disabled for referring objects") 92 } 93 94 // ValidateRefObjectSelector validate if exclusive fields are set for the selector 95 func ValidateRefObjectSelector(selector v1alpha1.ObjectReferrer) error { 96 labelSelector := GetLabelSelectorFromRefObjectSelector(selector) 97 if labelSelector != nil && selector.Name != "" { 98 return errors.Errorf("invalid object selector for ref-objects, name and labelSelector cannot be both set") 99 } 100 return nil 101 } 102 103 // ClearRefObjectForDispatch reset the objects for dispatch 104 func ClearRefObjectForDispatch(un *unstructured.Unstructured) { 105 un.SetResourceVersion("") 106 un.SetGeneration(0) 107 un.SetOwnerReferences(nil) 108 un.SetDeletionTimestamp(nil) 109 un.SetManagedFields(nil) 110 un.SetUID("") 111 unstructured.RemoveNestedField(un.Object, "metadata", "creationTimestamp") 112 unstructured.RemoveNestedField(un.Object, "status") 113 // TODO(somefive): make the following logic more generalizable 114 if un.GetKind() == "Service" && un.GetAPIVersion() == "v1" { 115 if clusterIP, exist, _ := unstructured.NestedString(un.Object, "spec", "clusterIP"); exist && clusterIP != corev1.ClusterIPNone { 116 unstructured.RemoveNestedField(un.Object, "spec", "clusterIP") 117 unstructured.RemoveNestedField(un.Object, "spec", "clusterIPs") 118 } 119 } 120 } 121 122 // SelectRefObjectsForDispatch select objects by selector from kubernetes 123 func SelectRefObjectsForDispatch(ctx context.Context, cli client.Client, appNs string, compName string, selector v1alpha1.ObjectReferrer) (objs []*unstructured.Unstructured, err error) { 124 if err = ValidateRefObjectSelector(selector); err != nil { 125 return nil, err 126 } 127 labelSelector := GetLabelSelectorFromRefObjectSelector(selector) 128 ns := appNs 129 if selector.Namespace != "" { 130 if RefObjectsAvailableScope == RefObjectsAvailableScopeNamespace { 131 return nil, errors.Errorf("cannot refer to objects outside the application's namespace") 132 } 133 ns = selector.Namespace 134 } 135 if selector.Cluster != "" && selector.Cluster != multicluster.ClusterLocalName { 136 if RefObjectsAvailableScope != RefObjectsAvailableScopeGlobal { 137 return nil, errors.Errorf("cannot refer to objects outside control plane") 138 } 139 ctx = multicluster.ContextWithClusterName(ctx, selector.Cluster) 140 } 141 gvk, err := GetGroupVersionKindFromRefObjectSelector(cli.RESTMapper(), selector) 142 if err != nil { 143 return nil, err 144 } 145 isNamespaced, err := IsGroupVersionKindNamespaceScoped(cli.RESTMapper(), gvk) 146 if err != nil { 147 return nil, err 148 } 149 if selector.Name == "" && labelSelector != nil { 150 uns := &unstructured.UnstructuredList{} 151 uns.SetGroupVersionKind(gvk) 152 opts := []client.ListOption{client.MatchingLabels(labelSelector)} 153 if isNamespaced { 154 opts = append(opts, client.InNamespace(ns)) 155 } 156 if err = cli.List(ctx, uns, opts...); err != nil { 157 return nil, errors.Wrapf(err, "failed to load ref object %s with selector", gvk.Kind) 158 } 159 for _, _un := range uns.Items { 160 objs = append(objs, _un.DeepCopy()) 161 } 162 } else { 163 un := &unstructured.Unstructured{} 164 un.SetGroupVersionKind(gvk) 165 un.SetName(selector.Name) 166 if isNamespaced { 167 un.SetNamespace(ns) 168 } 169 if selector.Name == "" { 170 un.SetName(compName) 171 } 172 if err := cli.Get(ctx, client.ObjectKeyFromObject(un), un); err != nil { 173 return nil, errors.Wrapf(err, "failed to load ref object %s %s/%s", un.GetKind(), un.GetNamespace(), un.GetName()) 174 } 175 objs = append(objs, un) 176 } 177 for _, obj := range objs { 178 ClearRefObjectForDispatch(obj) 179 } 180 return objs, nil 181 } 182 183 // ReferredObjectsDelegatingClient delegate client get/list function by retrieving ref-objects from existing objects 184 func ReferredObjectsDelegatingClient(cli client.Client, objs []*unstructured.Unstructured) client.Client { 185 objs = slices.Filter(objs, func(obj *unstructured.Unstructured) bool { 186 return obj.GetAnnotations() == nil || obj.GetAnnotations()[oam.AnnotationResourceURL] == "" 187 }) 188 return velaclient.DelegatingHandlerClient{ 189 Client: cli, 190 Getter: func(ctx context.Context, key client.ObjectKey, obj client.Object, _ ...client.GetOption) error { 191 un, ok := obj.(*unstructured.Unstructured) 192 if !ok { 193 return errors.Errorf("ReferredObjectsDelegatingClient does not support non-unstructured type") 194 } 195 gvk := un.GroupVersionKind() 196 for _, _un := range objs { 197 if gvk == _un.GroupVersionKind() && key == client.ObjectKeyFromObject(_un) { 198 _un.DeepCopyInto(un) 199 return nil 200 } 201 } 202 return apierrors.NewNotFound(schema.GroupResource{Group: gvk.Group, Resource: gvk.Kind}, un.GetName()) 203 }, 204 Lister: func(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { 205 uns, ok := list.(*unstructured.UnstructuredList) 206 if !ok { 207 return errors.Errorf("ReferredObjectsDelegatingClient does not support non-unstructured type") 208 } 209 gvk := uns.GroupVersionKind() 210 gvk.Kind = strings.TrimSuffix(gvk.Kind, "List") 211 listOpts := &client.ListOptions{} 212 for _, opt := range opts { 213 opt.ApplyToList(listOpts) 214 } 215 for _, _un := range objs { 216 if gvk != _un.GroupVersionKind() { 217 continue 218 } 219 if listOpts.Namespace != "" && listOpts.Namespace != _un.GetNamespace() { 220 continue 221 } 222 if listOpts.LabelSelector != nil && !listOpts.LabelSelector.Matches(labels.Set(_un.GetLabels())) { 223 continue 224 } 225 uns.Items = append(uns.Items, *_un) 226 } 227 return nil 228 }, 229 } 230 } 231 232 // AppendUnstructuredObjects add new objects into object list if not exists 233 func AppendUnstructuredObjects(objs []*unstructured.Unstructured, newObjs ...*unstructured.Unstructured) []*unstructured.Unstructured { 234 for _, newObj := range newObjs { 235 idx := -1 236 for i, oldObj := range objs { 237 if oldObj.GroupVersionKind() == newObj.GroupVersionKind() && client.ObjectKeyFromObject(oldObj) == client.ObjectKeyFromObject(newObj) { 238 idx = i 239 break 240 } 241 } 242 if idx >= 0 { 243 objs[idx] = newObj 244 } else { 245 objs = append(objs, newObj) 246 } 247 } 248 return objs 249 } 250 251 // ConvertUnstructuredsToReferredObjects convert unstructured objects into ReferredObjects 252 func ConvertUnstructuredsToReferredObjects(uns []*unstructured.Unstructured) (refObjs []common.ReferredObject, err error) { 253 for _, obj := range uns { 254 bs, err := json.Marshal(obj) 255 if err != nil { 256 return nil, err 257 } 258 refObjs = append(refObjs, common.ReferredObject{RawExtension: runtime.RawExtension{Raw: bs}}) 259 } 260 return refObjs, nil 261 } 262 263 // IsGroupVersionKindNamespaceScoped check if the target GroupVersionKind is namespace scoped resource 264 func IsGroupVersionKindNamespaceScoped(mapper meta.RESTMapper, gvk schema.GroupVersionKind) (bool, error) { 265 mappings, err := mapper.RESTMappings(gvk.GroupKind(), gvk.Version) 266 if err != nil { 267 return false, err 268 } 269 if len(mappings) == 0 { 270 return false, fmt.Errorf("unable to fund the mappings for gvk %s", gvk) 271 } 272 return mappings[0].Scope.Name() == meta.RESTScopeNameNamespace, nil 273 }