mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	These packages deserve to live at the top level of `k8s.io/cli-runtime`, as they are potentially of wider use than genericclioptions.
		
			
				
	
	
		
			188 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			188 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
| 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 set
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"strings"
 | |
| 
 | |
| 	"k8s.io/api/core/v1"
 | |
| 	"k8s.io/apimachinery/pkg/api/errors"
 | |
| 	"k8s.io/apimachinery/pkg/runtime"
 | |
| 	"k8s.io/apimachinery/pkg/util/sets"
 | |
| 	"k8s.io/apimachinery/pkg/util/strategicpatch"
 | |
| 	"k8s.io/cli-runtime/pkg/resource"
 | |
| 	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
 | |
| )
 | |
| 
 | |
| // selectContainers allows one or more containers to be matched against a string or wildcard
 | |
| func selectContainers(containers []v1.Container, spec string) ([]*v1.Container, []*v1.Container) {
 | |
| 	out := []*v1.Container{}
 | |
| 	skipped := []*v1.Container{}
 | |
| 	for i, c := range containers {
 | |
| 		if selectString(c.Name, spec) {
 | |
| 			out = append(out, &containers[i])
 | |
| 		} else {
 | |
| 			skipped = append(skipped, &containers[i])
 | |
| 		}
 | |
| 	}
 | |
| 	return out, skipped
 | |
| }
 | |
| 
 | |
| // handlePodUpdateError prints a more useful error to the end user when mutating a pod.
 | |
| func handlePodUpdateError(out io.Writer, err error, resource string) {
 | |
| 	if statusError, ok := err.(*errors.StatusError); ok && errors.IsInvalid(err) {
 | |
| 		errorDetails := statusError.Status().Details
 | |
| 		if errorDetails.Kind == "Pod" {
 | |
| 			all, match := true, false
 | |
| 			for _, cause := range errorDetails.Causes {
 | |
| 				if cause.Field == "spec" && strings.Contains(cause.Message, "may not update fields other than") {
 | |
| 					fmt.Fprintf(out, "error: may not update %s in pod %q directly\n", resource, errorDetails.Name)
 | |
| 					match = true
 | |
| 				} else {
 | |
| 					all = false
 | |
| 				}
 | |
| 			}
 | |
| 			if all && match {
 | |
| 				return
 | |
| 			}
 | |
| 		} else {
 | |
| 			if ok := cmdutil.PrintErrorWithCauses(err, out); ok {
 | |
| 				return
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fmt.Fprintf(out, "error: %v\n", err)
 | |
| }
 | |
| 
 | |
| // selectString returns true if the provided string matches spec, where spec is a string with
 | |
| // a non-greedy '*' wildcard operator.
 | |
| // TODO: turn into a regex and handle greedy matches and backtracking.
 | |
| func selectString(s, spec string) bool {
 | |
| 	if spec == "*" {
 | |
| 		return true
 | |
| 	}
 | |
| 	if !strings.Contains(spec, "*") {
 | |
| 		return s == spec
 | |
| 	}
 | |
| 
 | |
| 	pos := 0
 | |
| 	match := true
 | |
| 	parts := strings.Split(spec, "*")
 | |
| 	for i, part := range parts {
 | |
| 		if len(part) == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 		next := strings.Index(s[pos:], part)
 | |
| 		switch {
 | |
| 		// next part not in string
 | |
| 		case next < pos:
 | |
| 			fallthrough
 | |
| 		// first part does not match start of string
 | |
| 		case i == 0 && pos != 0:
 | |
| 			fallthrough
 | |
| 		// last part does not exactly match remaining part of string
 | |
| 		case i == (len(parts)-1) && len(s) != (len(part)+next):
 | |
| 			match = false
 | |
| 			break
 | |
| 		default:
 | |
| 			pos = next
 | |
| 		}
 | |
| 	}
 | |
| 	return match
 | |
| }
 | |
| 
 | |
| // Patch represents the result of a mutation to an object.
 | |
| type Patch struct {
 | |
| 	Info *resource.Info
 | |
| 	Err  error
 | |
| 
 | |
| 	Before []byte
 | |
| 	After  []byte
 | |
| 	Patch  []byte
 | |
| }
 | |
| 
 | |
| // PatchFn is a function type that accepts an info object and returns a byte slice.
 | |
| // Implementations of PatchFn should update the object and return it encoded.
 | |
| type PatchFn func(runtime.Object) ([]byte, error)
 | |
| 
 | |
| // CalculatePatch calls the mutation function on the provided info object, and generates a strategic merge patch for
 | |
| // the changes in the object. Encoder must be able to encode the info into the appropriate destination type.
 | |
| // This function returns whether the mutation function made any change in the original object.
 | |
| func CalculatePatch(patch *Patch, encoder runtime.Encoder, mutateFn PatchFn) bool {
 | |
| 	patch.Before, patch.Err = runtime.Encode(encoder, patch.Info.Object)
 | |
| 	patch.After, patch.Err = mutateFn(patch.Info.Object)
 | |
| 	if patch.Err != nil {
 | |
| 		return true
 | |
| 	}
 | |
| 	if patch.After == nil {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	patch.Patch, patch.Err = strategicpatch.CreateTwoWayMergePatch(patch.Before, patch.After, patch.Info.Object)
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // CalculatePatches calculates patches on each provided info object. If the provided mutateFn
 | |
| // makes no change in an object, the object is not included in the final list of patches.
 | |
| func CalculatePatches(infos []*resource.Info, encoder runtime.Encoder, mutateFn PatchFn) []*Patch {
 | |
| 	var patches []*Patch
 | |
| 	for _, info := range infos {
 | |
| 		patch := &Patch{Info: info}
 | |
| 		if CalculatePatch(patch, encoder, mutateFn) {
 | |
| 			patches = append(patches, patch)
 | |
| 		}
 | |
| 	}
 | |
| 	return patches
 | |
| }
 | |
| 
 | |
| func findEnv(env []v1.EnvVar, name string) (v1.EnvVar, bool) {
 | |
| 	for _, e := range env {
 | |
| 		if e.Name == name {
 | |
| 			return e, true
 | |
| 		}
 | |
| 	}
 | |
| 	return v1.EnvVar{}, false
 | |
| }
 | |
| 
 | |
| func updateEnv(existing []v1.EnvVar, env []v1.EnvVar, remove []string) []v1.EnvVar {
 | |
| 	out := []v1.EnvVar{}
 | |
| 	covered := sets.NewString(remove...)
 | |
| 	for _, e := range existing {
 | |
| 		if covered.Has(e.Name) {
 | |
| 			continue
 | |
| 		}
 | |
| 		newer, ok := findEnv(env, e.Name)
 | |
| 		if ok {
 | |
| 			covered.Insert(e.Name)
 | |
| 			out = append(out, newer)
 | |
| 			continue
 | |
| 		}
 | |
| 		out = append(out, e)
 | |
| 	}
 | |
| 	for _, e := range env {
 | |
| 		if covered.Has(e.Name) {
 | |
| 			continue
 | |
| 		}
 | |
| 		covered.Insert(e.Name)
 | |
| 		out = append(out, e)
 | |
| 	}
 | |
| 	return out
 | |
| }
 |