mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			193 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
Copyright 2015 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 editor
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"math/rand"
 | 
						|
	"os"
 | 
						|
	"os/exec"
 | 
						|
	"path/filepath"
 | 
						|
	"runtime"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/golang/glog"
 | 
						|
 | 
						|
	"k8s.io/kubernetes/pkg/util/term"
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// sorry, blame Git
 | 
						|
	// TODO: on Windows rely on 'start' to launch the editor associated
 | 
						|
	// with the given file type. If we can't because of the need of
 | 
						|
	// blocking, use a script with 'ftype' and 'assoc' to detect it.
 | 
						|
	defaultEditor = "vi"
 | 
						|
	defaultShell  = "/bin/bash"
 | 
						|
	windowsEditor = "notepad"
 | 
						|
	windowsShell  = "cmd"
 | 
						|
)
 | 
						|
 | 
						|
type Editor struct {
 | 
						|
	Args  []string
 | 
						|
	Shell bool
 | 
						|
}
 | 
						|
 | 
						|
// NewDefaultEditor creates a struct Editor that uses the OS environment to
 | 
						|
// locate the editor program, looking at EDITOR environment variable to find
 | 
						|
// the proper command line. If the provided editor has no spaces, or no quotes,
 | 
						|
// it is treated as a bare command to be loaded. Otherwise, the string will
 | 
						|
// be passed to the user's shell for execution.
 | 
						|
func NewDefaultEditor(envs []string) Editor {
 | 
						|
	args, shell := defaultEnvEditor(envs)
 | 
						|
	return Editor{
 | 
						|
		Args:  args,
 | 
						|
		Shell: shell,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func defaultEnvShell() []string {
 | 
						|
	shell := os.Getenv("SHELL")
 | 
						|
	if len(shell) == 0 {
 | 
						|
		shell = platformize(defaultShell, windowsShell)
 | 
						|
	}
 | 
						|
	flag := "-c"
 | 
						|
	if shell == windowsShell {
 | 
						|
		flag = "/C"
 | 
						|
	}
 | 
						|
	return []string{shell, flag}
 | 
						|
}
 | 
						|
 | 
						|
func defaultEnvEditor(envs []string) ([]string, bool) {
 | 
						|
	var editor string
 | 
						|
	for _, env := range envs {
 | 
						|
		if len(env) > 0 {
 | 
						|
			editor = os.Getenv(env)
 | 
						|
		}
 | 
						|
		if len(editor) > 0 {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if len(editor) == 0 {
 | 
						|
		editor = platformize(defaultEditor, windowsEditor)
 | 
						|
	}
 | 
						|
	if !strings.Contains(editor, " ") {
 | 
						|
		return []string{editor}, false
 | 
						|
	}
 | 
						|
	if !strings.ContainsAny(editor, "\"'\\") {
 | 
						|
		return strings.Split(editor, " "), false
 | 
						|
	}
 | 
						|
	// rather than parse the shell arguments ourselves, punt to the shell
 | 
						|
	shell := defaultEnvShell()
 | 
						|
	return append(shell, editor), true
 | 
						|
}
 | 
						|
 | 
						|
func (e Editor) args(path string) []string {
 | 
						|
	args := make([]string, len(e.Args))
 | 
						|
	copy(args, e.Args)
 | 
						|
	if e.Shell {
 | 
						|
		last := args[len(args)-1]
 | 
						|
		args[len(args)-1] = fmt.Sprintf("%s %q", last, path)
 | 
						|
	} else {
 | 
						|
		args = append(args, path)
 | 
						|
	}
 | 
						|
	return args
 | 
						|
}
 | 
						|
 | 
						|
// Launch opens the described or returns an error. The TTY will be protected, and
 | 
						|
// SIGQUIT, SIGTERM, and SIGINT will all be trapped.
 | 
						|
func (e Editor) Launch(path string) error {
 | 
						|
	if len(e.Args) == 0 {
 | 
						|
		return fmt.Errorf("no editor defined, can't open %s", path)
 | 
						|
	}
 | 
						|
	abs, err := filepath.Abs(path)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	args := e.args(abs)
 | 
						|
	cmd := exec.Command(args[0], args[1:]...)
 | 
						|
	cmd.Stdout = os.Stdout
 | 
						|
	cmd.Stderr = os.Stderr
 | 
						|
	cmd.Stdin = os.Stdin
 | 
						|
	glog.V(5).Infof("Opening file with editor %v", args)
 | 
						|
	if err := (term.TTY{In: os.Stdin, TryDev: true}).Safe(cmd.Run); err != nil {
 | 
						|
		if err, ok := err.(*exec.Error); ok {
 | 
						|
			if err.Err == exec.ErrNotFound {
 | 
						|
				return fmt.Errorf("unable to launch the editor %q", strings.Join(e.Args, " "))
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return fmt.Errorf("there was a problem with the editor %q", strings.Join(e.Args, " "))
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// LaunchTempFile reads the provided stream into a temporary file in the given directory
 | 
						|
// and file prefix, and then invokes Launch with the path of that file. It will return
 | 
						|
// the contents of the file after launch, any errors that occur, and the path of the
 | 
						|
// temporary file so the caller can clean it up as needed.
 | 
						|
func (e Editor) LaunchTempFile(prefix, suffix string, r io.Reader) ([]byte, string, error) {
 | 
						|
	f, err := tempFile(prefix, suffix)
 | 
						|
	if err != nil {
 | 
						|
		return nil, "", err
 | 
						|
	}
 | 
						|
	defer f.Close()
 | 
						|
	path := f.Name()
 | 
						|
	if _, err := io.Copy(f, r); err != nil {
 | 
						|
		os.Remove(path)
 | 
						|
		return nil, path, err
 | 
						|
	}
 | 
						|
	// This file descriptor needs to close so the next process (Launch) can claim it.
 | 
						|
	f.Close()
 | 
						|
	if err := e.Launch(path); err != nil {
 | 
						|
		return nil, path, err
 | 
						|
	}
 | 
						|
	bytes, err := ioutil.ReadFile(path)
 | 
						|
	return bytes, path, err
 | 
						|
}
 | 
						|
 | 
						|
func tempFile(prefix, suffix string) (f *os.File, err error) {
 | 
						|
	dir := os.TempDir()
 | 
						|
 | 
						|
	for i := 0; i < 10000; i++ {
 | 
						|
		name := filepath.Join(dir, prefix+randSeq(5)+suffix)
 | 
						|
		f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
 | 
						|
		if os.IsExist(err) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		break
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
var letters = []rune("abcdefghijklmnopqrstuvwxyz0123456789")
 | 
						|
 | 
						|
func randSeq(n int) string {
 | 
						|
	b := make([]rune, n)
 | 
						|
	for i := range b {
 | 
						|
		b[i] = letters[rand.Intn(len(letters))]
 | 
						|
	}
 | 
						|
	return string(b)
 | 
						|
}
 | 
						|
 | 
						|
func platformize(linux, windows string) string {
 | 
						|
	if runtime.GOOS == "windows" {
 | 
						|
		return windows
 | 
						|
	}
 | 
						|
	return linux
 | 
						|
}
 |