github.com/oam-dev/kubevela@v1.9.11/pkg/utils/k8s.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 utils 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "os" 24 "regexp" 25 "strings" 26 "text/template" 27 "time" 28 29 "github.com/tidwall/gjson" 30 "k8s.io/apimachinery/pkg/fields" 31 "k8s.io/apimachinery/pkg/runtime" 32 33 "github.com/fatih/color" 34 "github.com/kubevela/pkg/multicluster" 35 "github.com/pkg/errors" 36 "github.com/wercker/stern/stern" 37 authv1 "k8s.io/api/authentication/v1" 38 corev1 "k8s.io/api/core/v1" 39 apierrors "k8s.io/apimachinery/pkg/api/errors" 40 "k8s.io/apimachinery/pkg/api/meta" 41 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 42 "k8s.io/apimachinery/pkg/labels" 43 "k8s.io/apimachinery/pkg/runtime/schema" 44 "k8s.io/apimachinery/pkg/selection" 45 k8stypes "k8s.io/apimachinery/pkg/types" 46 "k8s.io/client-go/kubernetes" 47 "k8s.io/client-go/rest" 48 "sigs.k8s.io/controller-runtime/pkg/client" 49 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 50 51 "github.com/oam-dev/kubevela/pkg/oam/util" 52 velaerr "github.com/oam-dev/kubevela/pkg/utils/errors" 53 querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types" 54 ) 55 56 // MutateOption defines the function pattern for mutate 57 type MutateOption func(object metav1.Object) error 58 59 // MergeOverrideLabels will merge the existing labels and override by the labels passed in 60 func MergeOverrideLabels(labels map[string]string) MutateOption { 61 return func(object metav1.Object) error { 62 util.AddLabels(object, labels) 63 return nil 64 } 65 } 66 67 // MergeOverrideAnnotations will merge the existing annotations and override by the annotations passed in 68 func MergeOverrideAnnotations(annotations map[string]string) MutateOption { 69 return func(object metav1.Object) error { 70 util.AddAnnotations(object, annotations) 71 return nil 72 } 73 } 74 75 // MergeNoConflictLabels will merge the existing labels with the labels passed in, it will report conflicts if exists 76 func MergeNoConflictLabels(labels map[string]string) MutateOption { 77 return func(object metav1.Object) error { 78 existingLabels := object.GetLabels() 79 // check and fill the labels 80 for k, v := range labels { 81 ev, ok := existingLabels[k] 82 if ok && ev != "" && ev != v { 83 return fmt.Errorf("%s for object %s, key: %s, conflicts value: %s <-> %s", velaerr.LabelConflict, object.GetName(), k, ev, v) 84 } 85 existingLabels[k] = v 86 } 87 object.SetLabels(existingLabels) 88 return nil 89 } 90 } 91 92 // CreateOrUpdateNamespace will create a namespace if not exist, it will also update a namespace if exists 93 // It will report an error if the labels conflict while it will override the annotations 94 func CreateOrUpdateNamespace(ctx context.Context, kubeClient client.Client, name string, options ...MutateOption) error { 95 ns, err := GetNamespace(ctx, kubeClient, name) 96 switch { 97 case err == nil: 98 return PatchNamespace(ctx, kubeClient, ns, options...) 99 case apierrors.IsNotFound(err): 100 return CreateNamespace(ctx, kubeClient, name, options...) 101 default: 102 return err 103 } 104 } 105 106 // PatchNamespace will patch a namespace 107 func PatchNamespace(ctx context.Context, kubeClient client.Client, ns *corev1.Namespace, options ...MutateOption) error { 108 original := ns.DeepCopy() 109 for _, op := range options { 110 if err := op(ns); err != nil { 111 return err 112 } 113 } 114 return kubeClient.Patch(ctx, ns, client.MergeFrom(original)) 115 } 116 117 // CreateNamespace will create a namespace with mutate option 118 func CreateNamespace(ctx context.Context, kubeClient client.Client, name string, options ...MutateOption) error { 119 obj := &corev1.Namespace{ 120 ObjectMeta: metav1.ObjectMeta{ 121 Name: name, 122 }, 123 Spec: corev1.NamespaceSpec{}, 124 } 125 for _, op := range options { 126 if err := op(obj); err != nil { 127 return err 128 } 129 } 130 return kubeClient.Create(ctx, obj) 131 } 132 133 // GetNamespace will return a namespace with mutate option 134 func GetNamespace(ctx context.Context, kubeClient client.Client, name string) (*corev1.Namespace, error) { 135 obj := &corev1.Namespace{} 136 err := kubeClient.Get(ctx, client.ObjectKey{Name: name}, obj) 137 if err != nil { 138 return nil, err 139 } 140 return obj, nil 141 } 142 143 // UpdateNamespace will update a namespace with mutate option 144 func UpdateNamespace(ctx context.Context, kubeClient client.Client, name string, options ...MutateOption) error { 145 var namespace corev1.Namespace 146 err := kubeClient.Get(ctx, k8stypes.NamespacedName{Name: name}, &namespace) 147 if err != nil { 148 return err 149 } 150 for _, op := range options { 151 if err = op(&namespace); err != nil { 152 return err 153 } 154 } 155 return kubeClient.Update(ctx, &namespace) 156 } 157 158 // GetServiceAccountSubjectFromConfig extract ServiceAccountName subject from token 159 func GetServiceAccountSubjectFromConfig(cfg *rest.Config) string { 160 sub, _ := GetTokenSubject(cfg.BearerToken) 161 return sub 162 } 163 164 // GetCertificateCommonNameAndOrganizationsFromConfig extract CommonName and Organizations from Certificate 165 func GetCertificateCommonNameAndOrganizationsFromConfig(cfg *rest.Config) (string, []string) { 166 cert := cfg.CertData 167 if len(cert) == 0 && cfg.CertFile != "" { 168 cert, _ = os.ReadFile(cfg.CertFile) 169 } 170 name, _ := GetCertificateSubject(cert) 171 if name == nil { 172 return "", nil 173 } 174 return name.CommonName, name.Organization 175 } 176 177 // GetUserInfoFromConfig extract UserInfo from KubeConfig 178 func GetUserInfoFromConfig(cfg *rest.Config) *authv1.UserInfo { 179 if sub := GetServiceAccountSubjectFromConfig(cfg); sub != "" { 180 return &authv1.UserInfo{Username: sub} 181 } 182 if cn, orgs := GetCertificateCommonNameAndOrganizationsFromConfig(cfg); cn != "" { 183 return &authv1.UserInfo{Username: cn, Groups: orgs} 184 } 185 return nil 186 } 187 188 // CreateOrUpdate create or update a kubernetes object 189 func CreateOrUpdate(ctx context.Context, cli client.Client, obj client.Object) (controllerutil.OperationResult, error) { 190 bs, err := json.Marshal(obj) 191 if err != nil { 192 return controllerutil.OperationResultNone, err 193 } 194 return controllerutil.CreateOrUpdate(ctx, cli, obj, func() error { 195 createTimestamp := obj.GetCreationTimestamp() 196 resourceVersion := obj.GetResourceVersion() 197 deletionTimestamp := obj.GetDeletionTimestamp() 198 generation := obj.GetGeneration() 199 managedFields := obj.GetManagedFields() 200 if e := json.Unmarshal(bs, obj); err != nil { 201 return e 202 } 203 obj.SetCreationTimestamp(createTimestamp) 204 obj.SetResourceVersion(resourceVersion) 205 obj.SetDeletionTimestamp(deletionTimestamp) 206 obj.SetGeneration(generation) 207 obj.SetManagedFields(managedFields) 208 return nil 209 }) 210 } 211 212 // EscapeResourceNameToLabelValue parse characters in resource name to label valid name 213 func EscapeResourceNameToLabelValue(resourceName string) string { 214 return strings.ReplaceAll(resourceName, ":", "_") 215 } 216 217 // IsClusterScope check if the gvk is cluster scoped 218 func IsClusterScope(gvk schema.GroupVersionKind, mapper meta.RESTMapper) (bool, error) { 219 mappings, err := mapper.RESTMappings(gvk.GroupKind(), gvk.Version) 220 isClusterScope := len(mappings) > 0 && mappings[0].Scope.Name() == meta.RESTScopeNameRoot 221 return isClusterScope, err 222 } 223 224 // GetPodsLogs get logs from pods 225 func GetPodsLogs(ctx context.Context, config *rest.Config, containerName string, selectPods []*querytypes.PodBase, tmpl string, logC chan<- string, tailLines *int64) error { 226 if err := verifyPods(selectPods); err != nil { 227 return err 228 } 229 podRegex := getPodRegex(selectPods) 230 pods, err := regexp.Compile(podRegex) 231 if err != nil { 232 return fmt.Errorf("fail to compile '%s' for logs query", podRegex) 233 } 234 container := regexp.MustCompile(".*") 235 if containerName != "" { 236 container = regexp.MustCompile(containerName + ".*") 237 } 238 // These pods are from the same namespace, so we can use the first one to get the namespace 239 namespace := selectPods[0].Metadata.Namespace 240 selector := labels.NewSelector() 241 // Only use the labels to select pod if query one pod's log. It is only used when query vela-core log 242 if len(selectPods) == 1 { 243 for k, v := range selectPods[0].Metadata.Labels { 244 req, _ := labels.NewRequirement(k, selection.Equals, []string{v}) 245 if req != nil { 246 selector = selector.Add(*req) 247 } 248 } 249 } 250 config.Wrap(multicluster.NewTransportWrapper()) 251 clientSet, err := kubernetes.NewForConfig(config) 252 if err != nil { 253 return err 254 } 255 added, removed, err := stern.Watch(ctx, 256 clientSet.CoreV1().Pods(namespace), 257 pods, 258 container, 259 nil, 260 []stern.ContainerState{stern.RUNNING, stern.TERMINATED}, 261 selector, 262 ) 263 if err != nil { 264 return err 265 } 266 tails := make(map[string]*stern.Tail) 267 268 funs := map[string]interface{}{ 269 "json": func(in interface{}) (string, error) { 270 b, err := json.Marshal(in) 271 if err != nil { 272 return "", err 273 } 274 return string(b), nil 275 }, 276 "color": func(color color.Color, text string) string { 277 return color.SprintFunc()(text) 278 }, 279 } 280 template, err := template.New("log").Funcs(funs).Parse(tmpl) 281 if err != nil { 282 return errors.Wrap(err, "unable to parse template") 283 } 284 285 go func() { 286 for p := range added { 287 id := p.GetID() 288 if tails[id] != nil { 289 continue 290 } 291 // 48h 292 dur, _ := time.ParseDuration("48h") 293 tail := stern.NewTail(p.Namespace, p.Pod, p.Container, template, &stern.TailOptions{ 294 Timestamps: true, 295 SinceSeconds: int64(dur.Seconds()), 296 Exclude: nil, 297 Include: nil, 298 Namespace: false, 299 TailLines: tailLines, // default for all logs 300 }) 301 tails[id] = tail 302 303 tail.Start(ctx, clientSet.CoreV1().Pods(p.Namespace), logC) 304 } 305 }() 306 307 go func() { 308 for p := range removed { 309 id := p.GetID() 310 if tails[id] == nil { 311 continue 312 } 313 tails[id].Close() 314 delete(tails, id) 315 } 316 }() 317 318 <-ctx.Done() 319 close(logC) 320 return nil 321 } 322 323 func getPodRegex(pods []*querytypes.PodBase) string { 324 var podNames []string 325 for _, pod := range pods { 326 podNames = append(podNames, fmt.Sprintf("(%s.*)", pod.Metadata.Name)) 327 } 328 return strings.Join(podNames, "|") 329 } 330 331 func verifyPods(pods []*querytypes.PodBase) error { 332 if len(pods) == 0 { 333 return errors.New("no pods selected") 334 } 335 if len(pods) == 1 { 336 return nil 337 } 338 namespace := pods[0].Metadata.Namespace 339 for _, pod := range pods { 340 if pod.Metadata.Namespace != namespace { 341 return errors.New("cannot select pods from different namespaces") 342 } 343 } 344 return nil 345 } 346 347 // FilterObjectsByFieldSelector supports all field queries per type 348 func FilterObjectsByFieldSelector(objects []runtime.Object, fieldSelector fields.Selector) []runtime.Object { 349 filterFunc := createFieldFilter(fieldSelector) 350 // selected matched ones 351 var filtered []runtime.Object 352 for _, object := range objects { 353 selected := true 354 if !filterFunc(object) { 355 selected = false 356 } 357 358 if selected { 359 filtered = append(filtered, object) 360 } 361 } 362 return filtered 363 } 364 365 // FilterFunc return true if object contains field selector 366 type FilterFunc func(object runtime.Object) bool 367 368 // createFieldFilter return filterFunc 369 func createFieldFilter(selector fields.Selector) FilterFunc { 370 return func(object runtime.Object) bool { 371 return contains(&object, selector) 372 } 373 } 374 375 // implement a generic query filter to support multiple field selectors 376 func contains(object *runtime.Object, fieldSelector fields.Selector) bool { 377 // call the ParseSelector function of "k8s.io/apimachinery/pkg/fields/selector.go" to validate and parse the selector 378 for _, requirement := range fieldSelector.Requirements() { 379 var negative bool 380 // supports '=', '==' and '!='.(e.g. ?fieldSelector=key1=value1,key2=value2) 381 // fields.ParseSelector(FieldSelector) has handled the case where the operator is '==' and converted it to '=', 382 // so case selection.DoubleEquals can be ignored here. 383 switch requirement.Operator { 384 case selection.NotEquals: 385 negative = true 386 case selection.Equals: 387 negative = false 388 default: 389 return false 390 } 391 key := requirement.Field 392 value := requirement.Value 393 394 data, err := json.Marshal(object) 395 if err != nil { 396 return false 397 } 398 result := gjson.Get(string(data), key) 399 if (negative && fmt.Sprintf("%v", result.String()) != value) || 400 (!negative && fmt.Sprintf("%v", result.String()) == value) { 401 continue 402 } 403 return false 404 } 405 return true 406 }