Support copy file from node to local host or between nodes.
parent
78dec3b851
commit
a88aec8b46
|
@ -25,7 +25,6 @@ import (
|
|||
pt "path"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/minikube/pkg/minikube/assets"
|
||||
"k8s.io/minikube/pkg/minikube/command"
|
||||
"k8s.io/minikube/pkg/minikube/exit"
|
||||
|
@ -36,72 +35,46 @@ import (
|
|||
"k8s.io/minikube/pkg/minikube/reason"
|
||||
)
|
||||
|
||||
// placeholders for flag values
|
||||
var (
|
||||
srcPath string
|
||||
dstPath string
|
||||
dstNode string
|
||||
)
|
||||
type remotePath struct {
|
||||
node string
|
||||
path string
|
||||
}
|
||||
|
||||
// cpCmd represents the cp command, similar to docker cp
|
||||
var cpCmd = &cobra.Command{
|
||||
Use: "cp <source file path> <target node name>:<target file absolute path>",
|
||||
Use: "cp <source node name>:<source file path> <target node name>:<target file absolute path>",
|
||||
Short: "Copy the specified file into minikube",
|
||||
Long: "Copy the specified file into minikube, it will be saved at path <target file absolute path> in your minikube.\n" +
|
||||
"Example Command : \"minikube cp a.txt /home/docker/b.txt\"\n" +
|
||||
" \"minikube cp a.txt minikube-m02:/home/docker/b.txt\"\n",
|
||||
Long: `Copy the specified file into minikube, it will be saved at path <target file absolute path> in your minikube.
|
||||
Default target node controlplane and If <source node name> is omitted, It will trying to copy from host.
|
||||
|
||||
Example Command : "minikube cp a.txt /home/docker/b.txt" +
|
||||
"minikube cp a.txt minikube-m02:/home/docker/b.txt"
|
||||
"minikube cp minikube-m01:a.txt minikube-m02:/home/docker/b.txt"`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) != 2 {
|
||||
exit.Message(reason.Usage, `Please specify the path to copy:
|
||||
minikube cp <source file path> <target file absolute path> (example: "minikube cp a/b.txt /copied.txt")`)
|
||||
}
|
||||
|
||||
srcPath = args[0]
|
||||
dstPath = args[1]
|
||||
|
||||
// if destination path is not a absolute path, trying to parse with <node>:<abs path> format
|
||||
if !strings.HasPrefix(dstPath, "/") {
|
||||
if sp := strings.SplitN(dstPath, ":", 2); len(sp) == 2 {
|
||||
dstNode = sp[0]
|
||||
dstPath = sp[1]
|
||||
}
|
||||
}
|
||||
|
||||
validateArgs(srcPath, dstPath)
|
||||
|
||||
fa, err := assets.NewFileAsset(srcPath, pt.Dir(dstPath), pt.Base(dstPath), "0644")
|
||||
if err != nil {
|
||||
out.ErrLn("%v", errors.Wrap(err, "getting file asset"))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer func() {
|
||||
if err := fa.Close(); err != nil {
|
||||
klog.Warningf("error closing the file %s: %v", fa.GetSourcePath(), err)
|
||||
}
|
||||
}()
|
||||
src := newRemotePath(args[0])
|
||||
dst := newRemotePath(args[1])
|
||||
validateArgs(src, dst)
|
||||
|
||||
co := mustload.Running(ClusterFlagValue())
|
||||
var runner command.Runner
|
||||
if dstNode == "" {
|
||||
|
||||
if dst.node != "" {
|
||||
runner = remoteCommandRunner(&co, dst.node)
|
||||
} else if src.node == "" {
|
||||
// if node name not explicitly specfied in both of source and target,
|
||||
// consider target is controlpanel node for backward compatibility.
|
||||
runner = co.CP.Runner
|
||||
} else {
|
||||
n, _, err := node.Retrieve(*co.Config, dstNode)
|
||||
if err != nil {
|
||||
exit.Message(reason.GuestNodeRetrieve, "Node {{.nodeName}} does not exist.", out.V{"nodeName": dstNode})
|
||||
}
|
||||
|
||||
h, err := machine.GetHost(co.API, *co.Config, *n)
|
||||
if err != nil {
|
||||
exit.Error(reason.GuestLoadHost, "Error getting host", err)
|
||||
}
|
||||
|
||||
runner, err = machine.CommandRunner(h)
|
||||
if err != nil {
|
||||
exit.Error(reason.InternalCommandRunner, "Failed to get command runner", err)
|
||||
}
|
||||
runner = command.NewExecRunner(false)
|
||||
}
|
||||
|
||||
if err = runner.Copy(fa); err != nil {
|
||||
fa := copyableFile(&co, src, dst)
|
||||
if err := runner.Copy(fa); err != nil {
|
||||
exit.Error(reason.InternalCommandRunner, fmt.Sprintf("Fail to copy file %s", fa.GetSourcePath()), err)
|
||||
}
|
||||
},
|
||||
|
@ -110,24 +83,84 @@ var cpCmd = &cobra.Command{
|
|||
func init() {
|
||||
}
|
||||
|
||||
func validateArgs(srcPath string, dstPath string) {
|
||||
if srcPath == "" {
|
||||
exit.Message(reason.Usage, "Source {{.path}} can not be empty", out.V{"path": srcPath})
|
||||
// split path to node name and file path
|
||||
func newRemotePath(path string) *remotePath {
|
||||
// if destination path is not a absolute path, trying to parse with <node>:<abs path> format
|
||||
sp := strings.SplitN(path, ":", 2)
|
||||
if len(sp) == 2 && len(sp[0]) > 0 && !strings.Contains(sp[0], "/") && strings.HasPrefix(sp[1], "/") {
|
||||
return &remotePath{node: sp[0], path: sp[1]}
|
||||
}
|
||||
|
||||
if dstPath == "" {
|
||||
exit.Message(reason.Usage, "Target {{.path}} can not be empty", out.V{"path": dstPath})
|
||||
return &remotePath{node: "", path: path}
|
||||
}
|
||||
|
||||
func remoteCommandRunner(co *mustload.ClusterController, nodeName string) command.Runner {
|
||||
n, _, err := node.Retrieve(*co.Config, nodeName)
|
||||
if err != nil {
|
||||
exit.Message(reason.GuestNodeRetrieve, "Node {{.nodeName}} does not exist.", out.V{"nodeName": nodeName})
|
||||
}
|
||||
|
||||
if _, err := os.Stat(srcPath); err != nil {
|
||||
h, err := machine.GetHost(co.API, *co.Config, *n)
|
||||
if err != nil {
|
||||
out.ErrLn("%v", errors.Wrap(err, "getting host"))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
runner, err := machine.CommandRunner(h)
|
||||
if err != nil {
|
||||
out.ErrLn("%v", errors.Wrap(err, "getting command runner"))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return runner
|
||||
}
|
||||
|
||||
func copyableFile(co *mustload.ClusterController, src, dst *remotePath) assets.CopyableFile {
|
||||
// get assets.CopyableFile from minikube node
|
||||
if src.node != "" {
|
||||
runner := remoteCommandRunner(co, src.node)
|
||||
f, err := runner.ReadableFile(src.path)
|
||||
if err != nil {
|
||||
out.ErrLn("%v", errors.Wrapf(err, "getting file from %s node", src.node))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fakeWriter := func(_ []byte) (n int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return assets.NewBaseCopyableFile(f, fakeWriter, pt.Dir(dst.path), pt.Base(dst.path))
|
||||
}
|
||||
|
||||
if _, err := os.Stat(src.path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
exit.Message(reason.HostPathMissing, "Cannot find directory {{.path}} for copy", out.V{"path": srcPath})
|
||||
exit.Message(reason.HostPathMissing, "Cannot find directory {{.path}} for copy", out.V{"path": src})
|
||||
} else {
|
||||
exit.Error(reason.HostPathStat, "stat failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(dstPath, "/") {
|
||||
exit.Message(reason.Usage, `<target file absolute path> must be an absolute Path. Relative Path is not allowed (example: "/home/docker/copied.txt")`)
|
||||
fa, err := assets.NewFileAsset(src.path, pt.Dir(dst.path), pt.Base(dst.path), "0644")
|
||||
if err != nil {
|
||||
out.ErrLn("%v", errors.Wrap(err, "getting file asset"))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return fa
|
||||
}
|
||||
|
||||
func validateArgs(src, dst *remotePath) {
|
||||
if src.path == "" {
|
||||
exit.Message(reason.Usage, "Source {{.path}} can not be empty", out.V{"path": src.path})
|
||||
}
|
||||
|
||||
if dst.path == "" {
|
||||
exit.Message(reason.Usage, "Target {{.path}} can not be empty", out.V{"path": dst.path})
|
||||
}
|
||||
|
||||
// if node name not explicitly specfied in both of source and target,
|
||||
// consider target node is controlpanel for backward compatibility.
|
||||
if src.node == "" && dst.node == "" && !strings.HasPrefix(dst.path, "/") {
|
||||
exit.Message(reason.Usage, `Target <remote file path> must be an absolute Path. Relative Path is not allowed (example: "minikube:/home/docker/copied.txt")`)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParsePath(t *testing.T) {
|
||||
var passedCases = []struct {
|
||||
path string
|
||||
expectedNode string
|
||||
expectedPath string
|
||||
}{
|
||||
|
||||
{"", "", ""},
|
||||
{":", "", ":"},
|
||||
{":/a", "", ":/a"},
|
||||
{":a", "", ":a"},
|
||||
{"minikube:", "", "minikube:"},
|
||||
{"minikube:./a", "", "minikube:./a"},
|
||||
{"minikube:a", "", "minikube:a"},
|
||||
{"minikube::a", "", "minikube::a"},
|
||||
{"./a", "", "./a"},
|
||||
{"./a/b", "", "./a/b"},
|
||||
{"a", "", "a"},
|
||||
{"a/b", "", "a/b"},
|
||||
{"/a", "", "/a"},
|
||||
{"/a/b", "", "/a/b"},
|
||||
{"./:a/b", "", "./:a/b"},
|
||||
{"c:\\a", "", "c:\\a"},
|
||||
{"c:\\a\\b", "", "c:\\a\\b"},
|
||||
{"minikube:/a", "minikube", "/a"},
|
||||
{"minikube:/a/b", "minikube", "/a/b"},
|
||||
{"minikube:/a/b:c", "minikube", "/a/b:c"},
|
||||
}
|
||||
|
||||
for _, c := range passedCases {
|
||||
rp := newRemotePath(c.path)
|
||||
expected := remotePath{
|
||||
node: c.expectedNode,
|
||||
path: c.expectedPath,
|
||||
}
|
||||
if *rp != expected {
|
||||
t.Errorf("parsePath \"%s\" expected: %q, got: %q", c.path, expected, *rp)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -35,23 +36,68 @@ import (
|
|||
// MemorySource is the source name used for in-memory copies
|
||||
const MemorySource = "memory"
|
||||
|
||||
// CopyableFile is something that can be copied
|
||||
type CopyableFile interface {
|
||||
// ReadableFile is something that can be read
|
||||
type ReadableFile interface {
|
||||
io.Reader
|
||||
io.Writer
|
||||
GetLength() int
|
||||
SetLength(int)
|
||||
GetSourcePath() string
|
||||
GetTargetPath() string
|
||||
|
||||
GetTargetDir() string
|
||||
GetTargetName() string
|
||||
GetPermissions() string
|
||||
GetModTime() (time.Time, error)
|
||||
Seek(int64, int) (int64, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// CopyableFile is something that can be copied
|
||||
type CopyableFile interface {
|
||||
ReadableFile
|
||||
|
||||
io.Writer
|
||||
SetLength(int)
|
||||
GetTargetPath() string
|
||||
GetTargetDir() string
|
||||
GetTargetName() string
|
||||
}
|
||||
|
||||
type writeFn func(d []byte) (n int, err error)
|
||||
type BaseCopyableFile struct {
|
||||
ReadableFile
|
||||
|
||||
writer writeFn
|
||||
length int
|
||||
targetDir string
|
||||
targetName string
|
||||
}
|
||||
|
||||
func (r *BaseCopyableFile) Write(d []byte) (n int, err error) {
|
||||
return r.writer(d)
|
||||
}
|
||||
|
||||
func (r *BaseCopyableFile) SetLength(length int) {
|
||||
r.length = length
|
||||
}
|
||||
|
||||
func (r *BaseCopyableFile) GetTargetPath() string {
|
||||
return filepath.Join(r.GetTargetDir(), r.GetTargetName())
|
||||
}
|
||||
|
||||
func (r *BaseCopyableFile) GetTargetDir() string {
|
||||
return r.targetDir
|
||||
}
|
||||
|
||||
func (r *BaseCopyableFile) GetTargetName() string {
|
||||
return r.targetName
|
||||
}
|
||||
|
||||
func NewBaseCopyableFile(source ReadableFile, writer writeFn, targetDir, targetName string) *BaseCopyableFile {
|
||||
return &BaseCopyableFile{
|
||||
ReadableFile: source,
|
||||
writer: writer,
|
||||
targetDir: targetDir,
|
||||
targetName: targetName,
|
||||
}
|
||||
}
|
||||
|
||||
// BaseAsset is the base asset class
|
||||
type BaseAsset struct {
|
||||
SourcePath string
|
||||
|
|
|
@ -80,6 +80,9 @@ type Runner interface {
|
|||
|
||||
// Remove is a convenience method that runs a command to remove a file
|
||||
Remove(assets.CopyableFile) error
|
||||
|
||||
// ReadableFile open a remote file for reading
|
||||
ReadableFile(sourcePath string) (assets.ReadableFile, error)
|
||||
}
|
||||
|
||||
// Command returns a human readable command string that does not induce eye fatigue
|
||||
|
|
|
@ -219,3 +219,7 @@ func (e *execRunner) Remove(f assets.CopyableFile) error {
|
|||
}
|
||||
return os.Remove(dst)
|
||||
}
|
||||
|
||||
func (e *execRunner) ReadableFile(sourcePath string) (assets.ReadableFile, error) {
|
||||
return nil, fmt.Errorf("execRunner does not support ReadableFile - you could be the first to add it")
|
||||
}
|
||||
|
|
|
@ -161,6 +161,10 @@ func (f *FakeCommandRunner) Remove(file assets.CopyableFile) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeCommandRunner) ReadableFile(sourcePath string) (assets.ReadableFile, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// SetFileToContents stores the file to contents map for the FakeCommandRunner
|
||||
func (f *FakeCommandRunner) SetFileToContents(fileToContents map[string]string) {
|
||||
for k, v := range fileToContents {
|
||||
|
|
|
@ -139,6 +139,10 @@ func (k *kicRunner) WaitCmd(sc *StartedCmd) (*RunResult, error) {
|
|||
return nil, fmt.Errorf("kicRunner does not support WaitCmd - you could be the first to add it")
|
||||
}
|
||||
|
||||
func (k *kicRunner) ReadableFile(sourcePath string) (assets.ReadableFile, error) {
|
||||
return nil, fmt.Errorf("kicRunner does not support ReadableFile - you could be the first to add it")
|
||||
}
|
||||
|
||||
// Copy copies a file and its permissions
|
||||
func (k *kicRunner) Copy(f assets.CopyableFile) error {
|
||||
dst := path.Join(path.Join(f.GetTargetDir(), f.GetTargetName()))
|
||||
|
|
|
@ -44,7 +44,7 @@ var (
|
|||
)
|
||||
|
||||
// SSHRunner runs commands through SSH.
|
||||
//
|
||||
|
||||
// It implements the CommandRunner interface.
|
||||
type SSHRunner struct {
|
||||
d drivers.Driver
|
||||
|
@ -52,6 +52,49 @@ type SSHRunner struct {
|
|||
s *ssh.Session
|
||||
}
|
||||
|
||||
type sshReadableFile struct {
|
||||
length int
|
||||
sourcePath string
|
||||
permissions string
|
||||
sess *ssh.Session
|
||||
modTime time.Time
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
// GetLength returns lentgh of file
|
||||
func (s *sshReadableFile) GetLength() int {
|
||||
return s.length
|
||||
}
|
||||
|
||||
// GetSourcePath returns asset name
|
||||
func (s *sshReadableFile) GetSourcePath() string {
|
||||
return s.sourcePath
|
||||
}
|
||||
|
||||
// GetPermissions returns permissions
|
||||
func (s *sshReadableFile) GetPermissions() string {
|
||||
return s.permissions
|
||||
}
|
||||
|
||||
func (s *sshReadableFile) GetModTime() (time.Time, error) {
|
||||
return s.modTime, nil
|
||||
}
|
||||
|
||||
func (s *sshReadableFile) Read(p []byte) (int, error) {
|
||||
if s.GetLength() == 0 {
|
||||
return 0, fmt.Errorf("attempted read from a 0 length asset")
|
||||
}
|
||||
return s.reader.Read(p)
|
||||
}
|
||||
|
||||
func (s *sshReadableFile) Seek(offset int64, whence int) (int64, error) {
|
||||
return 0, fmt.Errorf("Seek is not implemented for sshReadableFile")
|
||||
}
|
||||
|
||||
func (s *sshReadableFile) Close() error {
|
||||
return s.sess.Close()
|
||||
}
|
||||
|
||||
// NewSSHRunner returns a new SSHRunner that will run commands
|
||||
// through the ssh.Client provided.
|
||||
func NewSSHRunner(d drivers.Driver) *SSHRunner {
|
||||
|
@ -455,3 +498,55 @@ func (s *SSHRunner) CopyFrom(f assets.CopyableFile) error {
|
|||
}
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (s *SSHRunner) ReadableFile(sourcePath string) (assets.ReadableFile, error) {
|
||||
klog.V(4).Infof("NewsshReadableFile: %s -> %s", sourcePath)
|
||||
|
||||
if !strings.HasPrefix(sourcePath, "/") {
|
||||
return nil, fmt.Errorf("sourcePath must be an absolute Path. Relative Path is not allowed")
|
||||
}
|
||||
|
||||
// get file size and modtime of the destination
|
||||
rr, err := s.RunCmd(exec.Command("stat", "-c", "%#a %s %y", sourcePath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stdout := strings.TrimSpace(rr.Stdout.String())
|
||||
outputs := strings.SplitN(stdout, " ", 3)
|
||||
|
||||
permission := outputs[0]
|
||||
size, err := strconv.Atoi(outputs[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modTime, err := time.Parse(layout, outputs[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess, err := s.session()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "NewSession")
|
||||
}
|
||||
|
||||
r, err := sess.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "StdOutPipe")
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("cat %s", sourcePath)
|
||||
if err := sess.Start(cmd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sshReadableFile{
|
||||
length: size,
|
||||
sourcePath: sourcePath,
|
||||
permissions: permission,
|
||||
reader: r,
|
||||
modTime: modTime,
|
||||
sess: sess,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -69,6 +69,8 @@ type CommandRunner interface {
|
|||
CopyFrom(assets.CopyableFile) error
|
||||
// Remove is a convenience method that runs a command to remove a file
|
||||
Remove(assets.CopyableFile) error
|
||||
|
||||
ReadableFile(sourcePath string) (assets.ReadableFile, error)
|
||||
}
|
||||
|
||||
// Manager is a common interface for container runtimes
|
||||
|
|
|
@ -244,6 +244,10 @@ func (f *FakeRunner) Remove(assets.CopyableFile) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeRunner) ReadableFile(sourcePath string) (assets.ReadableFile, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *FakeRunner) dockerPs(args []string) (string, error) {
|
||||
// ps -a --filter="name=apiserver" --format="{{.ID}}"
|
||||
if args[1] == "-a" && strings.HasPrefix(args[2], "--filter") {
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -1555,7 +1556,22 @@ func validateCpCmd(ctx context.Context, t *testing.T, profile string) {
|
|||
// docs: Run `minikube cp ...` to copy a file to the minikube node
|
||||
// docs: Run `minikube ssh sudo cat ...` to print out the copied file within minikube
|
||||
// docs: make sure the file is correctly copied
|
||||
testCpCmd(ctx, t, profile, "")
|
||||
|
||||
srcPath := cpTestLocalPath()
|
||||
dstPath := cpTestMinikubePath()
|
||||
|
||||
// copy to node
|
||||
testCpCmd(ctx, t, profile, "", srcPath, "", dstPath)
|
||||
|
||||
// copy from node
|
||||
tmpDir, err := ioutil.TempDir("", "mk_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
tmpPath := filepath.Join(tmpDir, "cp-test.txt")
|
||||
testCpCmd(ctx, t, profile, profile, dstPath, "", tmpPath)
|
||||
}
|
||||
|
||||
// validateMySQL validates a minimalist MySQL deployment
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -519,18 +520,38 @@ func cpTestLocalPath() string {
|
|||
return filepath.Join(*testdataDir, "cp-test.txt")
|
||||
}
|
||||
|
||||
// testCpCmd ensures copy functionality into minikube instance.
|
||||
func testCpCmd(ctx context.Context, t *testing.T, profile string, node string) {
|
||||
srcPath := cpTestLocalPath()
|
||||
dstPath := cpTestMinikubePath()
|
||||
|
||||
cpArgv := []string{"-p", profile, "cp", srcPath}
|
||||
func cpTestReadText(ctx context.Context, t *testing.T, profile, node, path string) string {
|
||||
if node == "" {
|
||||
cpArgv = append(cpArgv, dstPath)
|
||||
} else {
|
||||
cpArgv = append(cpArgv, fmt.Sprintf("%s:%s", node, dstPath))
|
||||
expected, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Errorf("failed to read test file 'testdata/cp-test.txt' : %v", err)
|
||||
}
|
||||
return string(expected)
|
||||
}
|
||||
|
||||
sshArgv := []string{"-p", profile, "ssh", "-n", node, fmt.Sprintf("sudo cat %s", path)}
|
||||
rr, err := Run(t, exec.CommandContext(ctx, Target(), sshArgv...))
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
t.Errorf("failed to run command by deadline. exceeded timeout : %s", rr.Command())
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("failed to run an cp command. args %q : %v", rr.Command(), err)
|
||||
}
|
||||
|
||||
return rr.Stdout.String()
|
||||
}
|
||||
|
||||
func cpTestMergePath(node, path string) string {
|
||||
if node == "" {
|
||||
return path
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%s", node, path)
|
||||
}
|
||||
|
||||
// testCpCmd ensures copy functionality into minikube instance.
|
||||
func testCpCmd(ctx context.Context, t *testing.T, profile string, srcNode, srcPath, dstNode, dstPath string) {
|
||||
cpArgv := []string{"-p", profile, "cp", cpTestMergePath(srcNode, srcPath), cpTestMergePath(dstNode, dstPath)}
|
||||
rr, err := Run(t, exec.CommandContext(ctx, Target(), cpArgv...))
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
t.Errorf("failed to run command by deadline. exceeded timeout : %s", rr.Command())
|
||||
|
@ -539,26 +560,15 @@ func testCpCmd(ctx context.Context, t *testing.T, profile string, node string) {
|
|||
t.Errorf("failed to run an cp command. args %q : %v", rr.Command(), err)
|
||||
}
|
||||
|
||||
sshArgv := []string{"-p", profile, "ssh"}
|
||||
if node != "" {
|
||||
sshArgv = append(sshArgv, "-n", node)
|
||||
}
|
||||
sshArgv = append(sshArgv, fmt.Sprintf("sudo cat %s", dstPath))
|
||||
|
||||
rr, err = Run(t, exec.CommandContext(ctx, Target(), sshArgv...))
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
t.Errorf("failed to run command by deadline. exceeded timeout : %s", rr.Command())
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("failed to run an cp command. args %q : %v", rr.Command(), err)
|
||||
expected := cpTestReadText(ctx, t, profile, srcNode, srcPath)
|
||||
var copiedText string
|
||||
if srcNode == "" && dstNode == "" {
|
||||
copiedText = cpTestReadText(ctx, t, profile, profile, dstPath)
|
||||
} else {
|
||||
copiedText = cpTestReadText(ctx, t, profile, dstNode, dstPath)
|
||||
}
|
||||
|
||||
expected, err := os.ReadFile(srcPath)
|
||||
if err != nil {
|
||||
t.Errorf("failed to read test file 'testdata/cp-test.txt' : %v", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(string(expected), rr.Stdout.String()); diff != "" {
|
||||
if diff := cmp.Diff(expected, copiedText); diff != "" {
|
||||
t.Errorf("/testdata/cp-test.txt content mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,8 +23,11 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -177,11 +180,30 @@ func validateCopyFileWithMultiNode(ctx context.Context, t *testing.T, profile st
|
|||
t.Errorf("failed to decode json from status: args %q: %v", rr.Command(), err)
|
||||
}
|
||||
|
||||
for _, s := range statuses {
|
||||
if s.Worker {
|
||||
testCpCmd(ctx, t, profile, s.Name)
|
||||
} else {
|
||||
testCpCmd(ctx, t, profile, "")
|
||||
tmpDir, err := ioutil.TempDir("", "mk_cp_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
srcPath := cpTestLocalPath()
|
||||
dstPath := cpTestMinikubePath()
|
||||
|
||||
for _, n := range statuses {
|
||||
// copy local to node
|
||||
testCpCmd(ctx, t, profile, "", srcPath, n.Name, dstPath)
|
||||
|
||||
// copy back from node to lcoal
|
||||
tmpPath := filepath.Join(tmpDir, fmt.Sprintf("cp-test_%s.txt", n.Name))
|
||||
testCpCmd(ctx, t, profile, n.Name, dstPath, "", tmpPath)
|
||||
|
||||
// copy node to node
|
||||
for _, n2 := range statuses {
|
||||
if n.Name == n2.Name {
|
||||
continue
|
||||
}
|
||||
fp := filepath.Join("/home/docker", fmt.Sprintf("cp-test_%s_%s.txt", n.Name, n2.Name))
|
||||
testCpCmd(ctx, t, profile, n.Name, dstPath, n2.Name, fp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
"--kvm-numa-count range is 1-8": "",
|
||||
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
|
||||
"127.0.0.1": "",
|
||||
"\u003ctarget file absolute path\u003e must be an absolute Path. Relative Path is not allowed (example: \"/home/docker/copied.txt\")": "",
|
||||
"==\u003e Audit \u003c==": "",
|
||||
"==\u003e Last Start \u003c==": "",
|
||||
"A VPN or firewall is interfering with HTTP access to the minikube VM. Alternatively, try a different VM driver: https://minikube.sigs.k8s.io/docs/start/": "",
|
||||
|
@ -598,6 +597,7 @@
|
|||
"System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
|
||||
"Tag images": "",
|
||||
"Tag to apply to the new image (optional)": "",
|
||||
"Target \u003cremote file path\u003e must be an absolute Path. Relative Path is not allowed (example: \"minikube:/home/docker/copied.txt\")": "",
|
||||
"Target directory {{.path}} must be an absolute path": "",
|
||||
"Target {{.path}} can not be empty": "",
|
||||
"Test docs have been saved at - {{.path}}": "",
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
"--kvm-numa-count range is 1-8": "",
|
||||
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
|
||||
"127.0.0.1": "",
|
||||
"\u003ctarget file absolute path\u003e must be an absolute Path. Relative Path is not allowed (example: \"/home/docker/copied.txt\")": "",
|
||||
"==\u003e Audit \u003c==": "",
|
||||
"==\u003e Last Start \u003c==": "",
|
||||
"A VPN or firewall is interfering with HTTP access to the minikube VM. Alternatively, try a different VM driver: https://minikube.sigs.k8s.io/docs/start/": "Una VPN o cortafuegos está interfiriendo con el acceso HTTP a la máquina virtual de minikube. Alternativamente prueba otro controlador: https://minikube.sigs.k8s.io/docs/start/",
|
||||
|
@ -604,6 +603,7 @@
|
|||
"System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
|
||||
"Tag images": "",
|
||||
"Tag to apply to the new image (optional)": "",
|
||||
"Target \u003cremote file path\u003e must be an absolute Path. Relative Path is not allowed (example: \"minikube:/home/docker/copied.txt\")": "",
|
||||
"Target directory {{.path}} must be an absolute path": "",
|
||||
"Target {{.path}} can not be empty": "",
|
||||
"Test docs have been saved at - {{.path}}": "",
|
||||
|
|
|
@ -567,6 +567,7 @@
|
|||
"System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "Le système n'a que {{.size}} Mio disponibles, moins que les {{.req}} Mio requis pour Kubernetes",
|
||||
"Tag images": "Marquer des images",
|
||||
"Tag to apply to the new image (optional)": "Tag à appliquer à la nouvelle image (facultatif)",
|
||||
"Target \u003cremote file path\u003e must be an absolute Path. Relative Path is not allowed (example: \"minikube:/home/docker/copied.txt\")": "",
|
||||
"Target directory {{.path}} must be an absolute path": "Le répertoire cible {{.path}} doit être un chemin absolu",
|
||||
"Target {{.path}} can not be empty": "La cible {{.path}} ne peut pas être vide",
|
||||
"Test docs have been saved at - {{.path}}": "Les documents de test ont été enregistrés à - {{.path}}",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
|
||||
"\"The '{{.minikube_addon}}' addon is disabled": "'{{.minikube_addon}}' アドオンが無効です",
|
||||
"\"{{.context}}\" context has been updated to point to {{.hostname}}:{{.port}}": "「{{.context}}」コンテキストが更新されて、{{.hostname}}:{{.port}} を指すようになりました",
|
||||
"\"{{.machineName}}\" does not exist, nothing to stop": "「{{.machineName}}」は存在しません。停止対象がありません",
|
||||
|
@ -598,6 +599,7 @@
|
|||
"System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
|
||||
"Tag images": "イメージのタグ付与",
|
||||
"Tag to apply to the new image (optional)": "新しいイメージに適用するタグ (任意)",
|
||||
"Target \u003cremote file path\u003e must be an absolute Path. Relative Path is not allowed (example: \"minikube:/home/docker/copied.txt\")": "",
|
||||
"Target directory {{.path}} must be an absolute path": "ターゲットディレクトリ {{.path}} は絶対パスでなければなりません。",
|
||||
"Target {{.path}} can not be empty": "ターゲット {{.path}} は空にできません",
|
||||
"Test docs have been saved at - {{.path}}": "テストドキュメントは {{.path}} に保存されました",
|
||||
|
@ -1001,4 +1003,4 @@
|
|||
"{{.profile}} profile is not valid: {{.err}}": "",
|
||||
"{{.type}} is not yet a supported filesystem. We will try anyways!": "{{.type}} はまだサポートされていなファイルシステムです。とにかくやってみます!",
|
||||
"{{.url}} is not accessible: {{.error}}": "{{.url}} はアクセス可能ではありません。 {{.error}}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
"--kvm-numa-count range is 1-8": "--kvm-numa-count 범위는 1부터 8입니다",
|
||||
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
|
||||
"127.0.0.1": "",
|
||||
"\u003ctarget file absolute path\u003e must be an absolute Path. Relative Path is not allowed (example: \"/home/docker/copied.txt\")": "",
|
||||
"==\u003e Audit \u003c==": "",
|
||||
"==\u003e Last Start \u003c==": "",
|
||||
"A VPN or firewall is interfering with HTTP access to the minikube VM. Alternatively, try a different VM driver: https://minikube.sigs.k8s.io/docs/start/": "",
|
||||
|
@ -619,6 +618,7 @@
|
|||
"System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
|
||||
"Tag images": "",
|
||||
"Tag to apply to the new image (optional)": "",
|
||||
"Target \u003cremote file path\u003e must be an absolute Path. Relative Path is not allowed (example: \"minikube:/home/docker/copied.txt\")": "",
|
||||
"Target directory {{.path}} must be an absolute path": "타겟 폴더 {{.path}} 는 절대 경로여야 합니다",
|
||||
"Target {{.path}} can not be empty": "",
|
||||
"Test docs have been saved at - {{.path}}": "",
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
"--kvm-numa-count range is 1-8": "",
|
||||
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
|
||||
"127.0.0.1": "",
|
||||
"\u003ctarget file absolute path\u003e must be an absolute Path. Relative Path is not allowed (example: \"/home/docker/copied.txt\")": "",
|
||||
"==\u003e Audit \u003c==": "==\u003e Audyt \u003c==",
|
||||
"==\u003e Last Start \u003c==": "==\u003e Ostatni start \u003c==",
|
||||
"A VPN or firewall is interfering with HTTP access to the minikube VM. Alternatively, try a different VM driver: https://minikube.sigs.k8s.io/docs/start/": "VPN lub zapora sieciowa przeszkadza w komunikacji protokołem HTTP z maszyną wirtualną minikube. Spróbuj użyć innego sterownika: https://minikube.sigs.k8s.io/docs/start/",
|
||||
|
@ -619,6 +618,7 @@
|
|||
"System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
|
||||
"Tag images": "",
|
||||
"Tag to apply to the new image (optional)": "",
|
||||
"Target \u003cremote file path\u003e must be an absolute Path. Relative Path is not allowed (example: \"minikube:/home/docker/copied.txt\")": "",
|
||||
"Target directory {{.path}} must be an absolute path": "",
|
||||
"Target {{.path}} can not be empty": "",
|
||||
"Test docs have been saved at - {{.path}}": "",
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
"--kvm-numa-count range is 1-8": "",
|
||||
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
|
||||
"127.0.0.1": "",
|
||||
"\u003ctarget file absolute path\u003e must be an absolute Path. Relative Path is not allowed (example: \"/home/docker/copied.txt\")": "",
|
||||
"==\u003e Audit \u003c==": "",
|
||||
"==\u003e Last Start \u003c==": "",
|
||||
"A VPN or firewall is interfering with HTTP access to the minikube VM. Alternatively, try a different VM driver: https://minikube.sigs.k8s.io/docs/start/": "",
|
||||
|
@ -567,6 +566,7 @@
|
|||
"System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
|
||||
"Tag images": "",
|
||||
"Tag to apply to the new image (optional)": "",
|
||||
"Target \u003cremote file path\u003e must be an absolute Path. Relative Path is not allowed (example: \"minikube:/home/docker/copied.txt\")": "",
|
||||
"Target directory {{.path}} must be an absolute path": "",
|
||||
"Target {{.path}} can not be empty": "",
|
||||
"Test docs have been saved at - {{.path}}": "",
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
"--kvm-numa-count range is 1-8": "",
|
||||
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
|
||||
"127.0.0.1": "",
|
||||
"\u003ctarget file absolute path\u003e must be an absolute Path. Relative Path is not allowed (example: \"/home/docker/copied.txt\")": "",
|
||||
"==\u003e Audit \u003c==": "",
|
||||
"==\u003e Last Start \u003c==": "",
|
||||
"A VPN or firewall is interfering with HTTP access to the minikube VM. Alternatively, try a different VM driver: https://minikube.sigs.k8s.io/docs/start/": "VPN 或者防火墙正在干扰对 minikube 虚拟机的 HTTP 访问。或者,您可以使用其它的虚拟机驱动:https://minikube.sigs.k8s.io/docs/start/",
|
||||
|
@ -703,6 +702,7 @@
|
|||
"System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
|
||||
"Tag images": "",
|
||||
"Tag to apply to the new image (optional)": "",
|
||||
"Target \u003cremote file path\u003e must be an absolute Path. Relative Path is not allowed (example: \"minikube:/home/docker/copied.txt\")": "",
|
||||
"Target directory {{.path}} must be an absolute path": "",
|
||||
"Target {{.path}} can not be empty": "",
|
||||
"Test docs have been saved at - {{.path}}": "",
|
||||
|
|
Loading…
Reference in New Issue