mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Signed-off-by: XinYang <xinydev@gmail.com> Update cmd/kubeadm/app/cmd/join.go Co-authored-by: Lubomir I. Ivanov <neolit123@gmail.com>
		
			
				
	
	
		
			348 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			348 lines
		
	
	
		
			9.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2020 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 patches
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"regexp"
 | 
						|
	"strings"
 | 
						|
	"sync"
 | 
						|
 | 
						|
	"k8s.io/apimachinery/pkg/types"
 | 
						|
	"k8s.io/apimachinery/pkg/util/strategicpatch"
 | 
						|
	utilyaml "k8s.io/apimachinery/pkg/util/yaml"
 | 
						|
	"sigs.k8s.io/yaml"
 | 
						|
 | 
						|
	jsonpatch "github.com/evanphx/json-patch"
 | 
						|
	"github.com/pkg/errors"
 | 
						|
)
 | 
						|
 | 
						|
// PatchTarget defines a target to be patched, such as a control-plane static Pod.
 | 
						|
type PatchTarget struct {
 | 
						|
	// Name must be the name of a known target. In the case of Kubernetes objects
 | 
						|
	// this is likely to match the ObjectMeta.Name of a target.
 | 
						|
	Name string
 | 
						|
 | 
						|
	// StrategicMergePatchObject is only used for strategic merge patches.
 | 
						|
	// It represents the underlying object type that is patched - e.g. "v1.Pod"
 | 
						|
	StrategicMergePatchObject interface{}
 | 
						|
 | 
						|
	// Data must contain the bytes that will be patched.
 | 
						|
	Data []byte
 | 
						|
}
 | 
						|
 | 
						|
// PatchManager defines an object that can apply patches.
 | 
						|
type PatchManager struct {
 | 
						|
	patchSets    []*patchSet
 | 
						|
	knownTargets []string
 | 
						|
	output       io.Writer
 | 
						|
}
 | 
						|
 | 
						|
// patchSet defines a set of patches of a certain type that can patch a PatchTarget.
 | 
						|
type patchSet struct {
 | 
						|
	targetName string
 | 
						|
	patchType  types.PatchType
 | 
						|
	patches    []string
 | 
						|
}
 | 
						|
 | 
						|
// String() is used for unit-testing.
 | 
						|
func (ps *patchSet) String() string {
 | 
						|
	return fmt.Sprintf(
 | 
						|
		"{%q, %q, %#v}",
 | 
						|
		ps.targetName,
 | 
						|
		ps.patchType,
 | 
						|
		ps.patches,
 | 
						|
	)
 | 
						|
}
 | 
						|
 | 
						|
var (
 | 
						|
	pathLock  = &sync.RWMutex{}
 | 
						|
	pathCache = map[string]*PatchManager{}
 | 
						|
 | 
						|
	patchTypes = map[string]types.PatchType{
 | 
						|
		"json":      types.JSONPatchType,
 | 
						|
		"merge":     types.MergePatchType,
 | 
						|
		"strategic": types.StrategicMergePatchType,
 | 
						|
		"":          types.StrategicMergePatchType, // Default
 | 
						|
	}
 | 
						|
	patchTypeList    = []string{"json", "merge", "strategic"}
 | 
						|
	patchTypesJoined = strings.Join(patchTypeList, "|")
 | 
						|
	knownExtensions  = []string{"json", "yaml"}
 | 
						|
 | 
						|
	regExtension = regexp.MustCompile(`.+\.(` + strings.Join(knownExtensions, "|") + `)$`)
 | 
						|
)
 | 
						|
 | 
						|
// GetPatchManagerForPath creates a patch manager that can be used to apply patches to "knownTargets".
 | 
						|
// "path" should contain patches that can be used to patch the "knownTargets".
 | 
						|
// If "output" is non-nil, messages about actions performed by the manager would go on this io.Writer.
 | 
						|
func GetPatchManagerForPath(path string, knownTargets []string, output io.Writer) (*PatchManager, error) {
 | 
						|
	pathLock.RLock()
 | 
						|
	if pm, known := pathCache[path]; known {
 | 
						|
		pathLock.RUnlock()
 | 
						|
		return pm, nil
 | 
						|
	}
 | 
						|
	pathLock.RUnlock()
 | 
						|
 | 
						|
	if output == nil {
 | 
						|
		output = ioutil.Discard
 | 
						|
	}
 | 
						|
 | 
						|
	fmt.Fprintf(output, "[patches] Reading patches from path %q\n", path)
 | 
						|
 | 
						|
	// Get the files in the path.
 | 
						|
	patchSets, patchFiles, ignoredFiles, err := getPatchSetsFromPath(path, knownTargets, output)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if len(patchFiles) > 0 {
 | 
						|
		fmt.Fprintf(output, "[patches] Found the following patch files: %v\n", patchFiles)
 | 
						|
	}
 | 
						|
	if len(ignoredFiles) > 0 {
 | 
						|
		fmt.Fprintf(output, "[patches] Ignored the following files: %v\n", ignoredFiles)
 | 
						|
	}
 | 
						|
 | 
						|
	pm := &PatchManager{
 | 
						|
		patchSets:    patchSets,
 | 
						|
		knownTargets: knownTargets,
 | 
						|
		output:       output,
 | 
						|
	}
 | 
						|
	pathLock.Lock()
 | 
						|
	pathCache[path] = pm
 | 
						|
	pathLock.Unlock()
 | 
						|
 | 
						|
	return pm, nil
 | 
						|
}
 | 
						|
 | 
						|
// ApplyPatchesToTarget takes a patch target and patches its "Data" using the patches
 | 
						|
// stored in the patch manager. The resulted "Data" is always converted to JSON.
 | 
						|
func (pm *PatchManager) ApplyPatchesToTarget(patchTarget *PatchTarget) error {
 | 
						|
	var err error
 | 
						|
	var patchedData []byte
 | 
						|
 | 
						|
	var found bool
 | 
						|
	for _, pt := range pm.knownTargets {
 | 
						|
		if pt == patchTarget.Name {
 | 
						|
			found = true
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if !found {
 | 
						|
		return errors.Errorf("unknown patch target name %q, must be one of %v", patchTarget.Name, pm.knownTargets)
 | 
						|
	}
 | 
						|
 | 
						|
	// Always convert the target data to JSON.
 | 
						|
	patchedData, err = yaml.YAMLToJSON(patchTarget.Data)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// Iterate over the patchSets.
 | 
						|
	for _, patchSet := range pm.patchSets {
 | 
						|
		if patchSet.targetName != patchTarget.Name {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Iterate over the patches in the patchSets.
 | 
						|
		for _, patch := range patchSet.patches {
 | 
						|
			patchBytes := []byte(patch)
 | 
						|
 | 
						|
			// Patch based on the patch type.
 | 
						|
			switch patchSet.patchType {
 | 
						|
 | 
						|
			// JSON patch.
 | 
						|
			case types.JSONPatchType:
 | 
						|
				var patchObj jsonpatch.Patch
 | 
						|
				patchObj, err = jsonpatch.DecodePatch(patchBytes)
 | 
						|
				if err == nil {
 | 
						|
					patchedData, err = patchObj.Apply(patchedData)
 | 
						|
				}
 | 
						|
 | 
						|
			// Merge patch.
 | 
						|
			case types.MergePatchType:
 | 
						|
				patchedData, err = jsonpatch.MergePatch(patchedData, patchBytes)
 | 
						|
 | 
						|
			// Strategic merge patch.
 | 
						|
			case types.StrategicMergePatchType:
 | 
						|
				patchedData, err = strategicpatch.StrategicMergePatch(
 | 
						|
					patchedData,
 | 
						|
					patchBytes,
 | 
						|
					patchTarget.StrategicMergePatchObject,
 | 
						|
				)
 | 
						|
			}
 | 
						|
 | 
						|
			if err != nil {
 | 
						|
				return errors.Wrapf(err, "could not apply the following patch of type %q to target %q:\n%s\n",
 | 
						|
					patchSet.patchType,
 | 
						|
					patchTarget.Name,
 | 
						|
					patch)
 | 
						|
			}
 | 
						|
			fmt.Fprintf(pm.output, "[patches] Applied patch of type %q to target %q\n", patchSet.patchType, patchTarget.Name)
 | 
						|
		}
 | 
						|
 | 
						|
		// Update the data for this patch target.
 | 
						|
		patchTarget.Data = patchedData
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// parseFilename validates a file name and retrieves the encoded target name and patch type.
 | 
						|
// - On unknown extension or target name it returns a warning
 | 
						|
// - On unknown patch type it returns an error
 | 
						|
// - On success it returns a target name and patch type
 | 
						|
func parseFilename(fileName string, knownTargets []string) (string, types.PatchType, error, error) {
 | 
						|
	// Return a warning if the extension cannot be matched.
 | 
						|
	if !regExtension.MatchString(fileName) {
 | 
						|
		return "", "", errors.Errorf("the file extension must be one of %v", knownExtensions), nil
 | 
						|
	}
 | 
						|
 | 
						|
	regFileNameSplit := regexp.MustCompile(
 | 
						|
		fmt.Sprintf(`^(%s)([^.+\n]*)?(\+)?(%s)?`, strings.Join(knownTargets, "|"), patchTypesJoined),
 | 
						|
	)
 | 
						|
	// Extract the target name and patch type. The resulting sub-string slice would look like this:
 | 
						|
	//   [full-match, targetName, suffix, +, patchType]
 | 
						|
	sub := regFileNameSplit.FindStringSubmatch(fileName)
 | 
						|
	if sub == nil {
 | 
						|
		return "", "", errors.Errorf("unknown target, must be one of %v", knownTargets), nil
 | 
						|
	}
 | 
						|
	targetName := sub[1]
 | 
						|
 | 
						|
	if len(sub[3]) > 0 && len(sub[4]) == 0 {
 | 
						|
		return "", "", nil, errors.Errorf("unknown or missing patch type after '+', must be one of %v", patchTypeList)
 | 
						|
	}
 | 
						|
	patchType := patchTypes[sub[4]]
 | 
						|
 | 
						|
	return targetName, patchType, nil, nil
 | 
						|
}
 | 
						|
 | 
						|
// createPatchSet creates a patchSet object, by splitting the given "data" by "\n---".
 | 
						|
func createPatchSet(targetName string, patchType types.PatchType, data string) (*patchSet, error) {
 | 
						|
	var patches []string
 | 
						|
 | 
						|
	// Split the patches and convert them to JSON.
 | 
						|
	// Data that is already JSON will not cause an error.
 | 
						|
	buf := bytes.NewBuffer([]byte(data))
 | 
						|
	reader := utilyaml.NewYAMLReader(bufio.NewReader(buf))
 | 
						|
	for {
 | 
						|
		patch, err := reader.Read()
 | 
						|
		if err == io.EOF {
 | 
						|
			break
 | 
						|
		} else if err != nil {
 | 
						|
			return nil, errors.Wrapf(err, "could not split patches for data:\n%s\n", data)
 | 
						|
		}
 | 
						|
 | 
						|
		patch = bytes.TrimSpace(patch)
 | 
						|
		if len(patch) == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		patchJSON, err := yaml.YAMLToJSON(patch)
 | 
						|
		if err != nil {
 | 
						|
			return nil, errors.Wrapf(err, "could not convert patch to JSON:\n%s\n", patch)
 | 
						|
		}
 | 
						|
		patches = append(patches, string(patchJSON))
 | 
						|
	}
 | 
						|
 | 
						|
	return &patchSet{
 | 
						|
		targetName: targetName,
 | 
						|
		patchType:  patchType,
 | 
						|
		patches:    patches,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
// getPatchSetsFromPath walks a path, ignores sub-directories and non-patch files, and
 | 
						|
// returns a list of patchFile objects.
 | 
						|
func getPatchSetsFromPath(targetPath string, knownTargets []string, output io.Writer) ([]*patchSet, []string, []string, error) {
 | 
						|
	patchFiles := []string{}
 | 
						|
	ignoredFiles := []string{}
 | 
						|
	patchSets := []*patchSet{}
 | 
						|
 | 
						|
	// Check if targetPath is a directory.
 | 
						|
	info, err := os.Lstat(targetPath)
 | 
						|
	if err != nil {
 | 
						|
		goto return_path_error
 | 
						|
	}
 | 
						|
	if !info.IsDir() {
 | 
						|
		err = &os.PathError{
 | 
						|
			Op:   "getPatchSetsFromPath",
 | 
						|
			Path: info.Name(),
 | 
						|
			Err:  errors.New("not a directory"),
 | 
						|
		}
 | 
						|
		goto return_path_error
 | 
						|
	}
 | 
						|
 | 
						|
	err = filepath.Walk(targetPath, func(path string, info os.FileInfo, err error) error {
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// Sub-directories and "." are ignored.
 | 
						|
		if info.IsDir() {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		baseName := info.Name()
 | 
						|
 | 
						|
		// Parse the filename and retrieve the target and patch type
 | 
						|
		targetName, patchType, warn, err := parseFilename(baseName, knownTargets)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if warn != nil {
 | 
						|
			fmt.Fprintf(output, "[patches] Ignoring file %q: %v\n", baseName, warn)
 | 
						|
			ignoredFiles = append(ignoredFiles, baseName)
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		// Read the patch file.
 | 
						|
		data, err := ioutil.ReadFile(path)
 | 
						|
		if err != nil {
 | 
						|
			return errors.Wrapf(err, "could not read the file %q", path)
 | 
						|
		}
 | 
						|
 | 
						|
		if len(data) == 0 {
 | 
						|
			fmt.Fprintf(output, "[patches] Ignoring empty file: %q\n", baseName)
 | 
						|
			ignoredFiles = append(ignoredFiles, baseName)
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
 | 
						|
		// Create a patchSet object.
 | 
						|
		patchSet, err := createPatchSet(targetName, patchType, string(data))
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		patchFiles = append(patchFiles, baseName)
 | 
						|
		patchSets = append(patchSets, patchSet)
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
 | 
						|
return_path_error:
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, nil, errors.Wrapf(err, "could not list patch files for path %q", targetPath)
 | 
						|
	}
 | 
						|
 | 
						|
	return patchSets, patchFiles, ignoredFiles, nil
 | 
						|
}
 |