Support copy file from node to local host or between nodes.

pull/11598/head
Daehyeok Mun 2021-05-05 21:56:51 -07:00
parent 78dec3b851
commit a88aec8b46
21 changed files with 415 additions and 107 deletions

View File

@ -25,7 +25,6 @@ import (
pt "path" pt "path"
"strings" "strings"
"k8s.io/klog/v2"
"k8s.io/minikube/pkg/minikube/assets" "k8s.io/minikube/pkg/minikube/assets"
"k8s.io/minikube/pkg/minikube/command" "k8s.io/minikube/pkg/minikube/command"
"k8s.io/minikube/pkg/minikube/exit" "k8s.io/minikube/pkg/minikube/exit"
@ -36,72 +35,46 @@ import (
"k8s.io/minikube/pkg/minikube/reason" "k8s.io/minikube/pkg/minikube/reason"
) )
// placeholders for flag values type remotePath struct {
var ( node string
srcPath string path string
dstPath string }
dstNode string
)
// cpCmd represents the cp command, similar to docker cp // cpCmd represents the cp command, similar to docker cp
var cpCmd = &cobra.Command{ 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", 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" + Long: `Copy the specified file into minikube, it will be saved at path <target file absolute path> in your minikube.
"Example Command : \"minikube cp a.txt /home/docker/b.txt\"\n" + Default target node controlplane and If <source node name> is omitted, It will trying to copy from host.
" \"minikube cp a.txt minikube-m02:/home/docker/b.txt\"\n",
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) { Run: func(cmd *cobra.Command, args []string) {
if len(args) != 2 { if len(args) != 2 {
exit.Message(reason.Usage, `Please specify the path to copy: 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")`) minikube cp <source file path> <target file absolute path> (example: "minikube cp a/b.txt /copied.txt")`)
} }
srcPath = args[0] src := newRemotePath(args[0])
dstPath = args[1] dst := newRemotePath(args[1])
validateArgs(src, dst)
// 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)
}
}()
co := mustload.Running(ClusterFlagValue()) co := mustload.Running(ClusterFlagValue())
var runner command.Runner 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 runner = co.CP.Runner
} else { } else {
n, _, err := node.Retrieve(*co.Config, dstNode) runner = command.NewExecRunner(false)
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)
}
} }
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) 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 init() {
} }
func validateArgs(srcPath string, dstPath string) { // split path to node name and file path
if srcPath == "" { func newRemotePath(path string) *remotePath {
exit.Message(reason.Usage, "Source {{.path}} can not be empty", out.V{"path": srcPath}) // 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 == "" { return &remotePath{node: "", path: path}
exit.Message(reason.Usage, "Target {{.path}} can not be empty", out.V{"path": dstPath}) }
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) { 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 { } else {
exit.Error(reason.HostPathStat, "stat failed", err) exit.Error(reason.HostPathStat, "stat failed", err)
} }
} }
if !strings.HasPrefix(dstPath, "/") { fa, err := assets.NewFileAsset(src.path, pt.Dir(dst.path), pt.Base(dst.path), "0644")
exit.Message(reason.Usage, `<target file absolute path> must be an absolute Path. Relative Path is not allowed (example: "/home/docker/copied.txt")`) 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")`)
} }
} }

View File

@ -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)
}
}
}

View File

@ -24,6 +24,7 @@ import (
"io" "io"
"os" "os"
"path" "path"
"path/filepath"
"strconv" "strconv"
"time" "time"
@ -35,23 +36,68 @@ import (
// MemorySource is the source name used for in-memory copies // MemorySource is the source name used for in-memory copies
const MemorySource = "memory" const MemorySource = "memory"
// CopyableFile is something that can be copied // ReadableFile is something that can be read
type CopyableFile interface { type ReadableFile interface {
io.Reader io.Reader
io.Writer
GetLength() int GetLength() int
SetLength(int)
GetSourcePath() string GetSourcePath() string
GetTargetPath() string
GetTargetDir() string
GetTargetName() string
GetPermissions() string GetPermissions() string
GetModTime() (time.Time, error) GetModTime() (time.Time, error)
Seek(int64, int) (int64, error) Seek(int64, int) (int64, error)
Close() 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 // BaseAsset is the base asset class
type BaseAsset struct { type BaseAsset struct {
SourcePath string SourcePath string

View File

@ -80,6 +80,9 @@ type Runner interface {
// Remove is a convenience method that runs a command to remove a file // Remove is a convenience method that runs a command to remove a file
Remove(assets.CopyableFile) error 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 // Command returns a human readable command string that does not induce eye fatigue

View File

@ -219,3 +219,7 @@ func (e *execRunner) Remove(f assets.CopyableFile) error {
} }
return os.Remove(dst) 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")
}

View File

@ -161,6 +161,10 @@ func (f *FakeCommandRunner) Remove(file assets.CopyableFile) error {
return nil return nil
} }
func (f *FakeCommandRunner) ReadableFile(sourcePath string) (assets.ReadableFile, error) {
return nil, nil
}
// SetFileToContents stores the file to contents map for the FakeCommandRunner // SetFileToContents stores the file to contents map for the FakeCommandRunner
func (f *FakeCommandRunner) SetFileToContents(fileToContents map[string]string) { func (f *FakeCommandRunner) SetFileToContents(fileToContents map[string]string) {
for k, v := range fileToContents { for k, v := range fileToContents {

View File

@ -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") 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 // Copy copies a file and its permissions
func (k *kicRunner) Copy(f assets.CopyableFile) error { func (k *kicRunner) Copy(f assets.CopyableFile) error {
dst := path.Join(path.Join(f.GetTargetDir(), f.GetTargetName())) dst := path.Join(path.Join(f.GetTargetDir(), f.GetTargetName()))

View File

@ -44,7 +44,7 @@ var (
) )
// SSHRunner runs commands through SSH. // SSHRunner runs commands through SSH.
//
// It implements the CommandRunner interface. // It implements the CommandRunner interface.
type SSHRunner struct { type SSHRunner struct {
d drivers.Driver d drivers.Driver
@ -52,6 +52,49 @@ type SSHRunner struct {
s *ssh.Session 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 // NewSSHRunner returns a new SSHRunner that will run commands
// through the ssh.Client provided. // through the ssh.Client provided.
func NewSSHRunner(d drivers.Driver) *SSHRunner { func NewSSHRunner(d drivers.Driver) *SSHRunner {
@ -455,3 +498,55 @@ func (s *SSHRunner) CopyFrom(f assets.CopyableFile) error {
} }
return g.Wait() 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
}

View File

@ -69,6 +69,8 @@ type CommandRunner interface {
CopyFrom(assets.CopyableFile) error CopyFrom(assets.CopyableFile) error
// Remove is a convenience method that runs a command to remove a file // Remove is a convenience method that runs a command to remove a file
Remove(assets.CopyableFile) error Remove(assets.CopyableFile) error
ReadableFile(sourcePath string) (assets.ReadableFile, error)
} }
// Manager is a common interface for container runtimes // Manager is a common interface for container runtimes

View File

@ -244,6 +244,10 @@ func (f *FakeRunner) Remove(assets.CopyableFile) error {
return nil return nil
} }
func (f *FakeRunner) ReadableFile(sourcePath string) (assets.ReadableFile, error) {
return nil, nil
}
func (f *FakeRunner) dockerPs(args []string) (string, error) { func (f *FakeRunner) dockerPs(args []string) (string, error) {
// ps -a --filter="name=apiserver" --format="{{.ID}}" // ps -a --filter="name=apiserver" --format="{{.ID}}"
if args[1] == "-a" && strings.HasPrefix(args[2], "--filter") { if args[1] == "-a" && strings.HasPrefix(args[2], "--filter") {

View File

@ -26,6 +26,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os" "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 cp ...` to copy a file to the minikube node
// docs: Run `minikube ssh sudo cat ...` to print out the copied file within minikube // docs: Run `minikube ssh sudo cat ...` to print out the copied file within minikube
// docs: make sure the file is correctly copied // 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 // validateMySQL validates a minimalist MySQL deployment

View File

@ -29,6 +29,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -519,18 +520,38 @@ func cpTestLocalPath() string {
return filepath.Join(*testdataDir, "cp-test.txt") return filepath.Join(*testdataDir, "cp-test.txt")
} }
// testCpCmd ensures copy functionality into minikube instance. func cpTestReadText(ctx context.Context, t *testing.T, profile, node, path string) string {
func testCpCmd(ctx context.Context, t *testing.T, profile string, node string) {
srcPath := cpTestLocalPath()
dstPath := cpTestMinikubePath()
cpArgv := []string{"-p", profile, "cp", srcPath}
if node == "" { if node == "" {
cpArgv = append(cpArgv, dstPath) expected, err := ioutil.ReadFile(path)
} else { if err != nil {
cpArgv = append(cpArgv, fmt.Sprintf("%s:%s", node, dstPath)) 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...)) rr, err := Run(t, exec.CommandContext(ctx, Target(), cpArgv...))
if ctx.Err() == context.DeadlineExceeded { if ctx.Err() == context.DeadlineExceeded {
t.Errorf("failed to run command by deadline. exceeded timeout : %s", rr.Command()) 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) t.Errorf("failed to run an cp command. args %q : %v", rr.Command(), err)
} }
sshArgv := []string{"-p", profile, "ssh"} expected := cpTestReadText(ctx, t, profile, srcNode, srcPath)
if node != "" { var copiedText string
sshArgv = append(sshArgv, "-n", node) if srcNode == "" && dstNode == "" {
} copiedText = cpTestReadText(ctx, t, profile, profile, dstPath)
sshArgv = append(sshArgv, fmt.Sprintf("sudo cat %s", dstPath)) } else {
copiedText = cpTestReadText(ctx, t, profile, dstNode, 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, err := os.ReadFile(srcPath) if diff := cmp.Diff(expected, copiedText); diff != "" {
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 != "" {
t.Errorf("/testdata/cp-test.txt content mismatch (-want +got):\n%s", diff) t.Errorf("/testdata/cp-test.txt content mismatch (-want +got):\n%s", diff)
} }
} }

View File

@ -23,8 +23,11 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"net" "net"
"os"
"os/exec" "os/exec"
"path/filepath"
"strings" "strings"
"testing" "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) t.Errorf("failed to decode json from status: args %q: %v", rr.Command(), err)
} }
for _, s := range statuses { tmpDir, err := ioutil.TempDir("", "mk_cp_test")
if s.Worker { if err != nil {
testCpCmd(ctx, t, profile, s.Name) t.Fatal(err)
} else { }
testCpCmd(ctx, t, profile, "") 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)
} }
} }
} }

View File

@ -18,7 +18,6 @@
"--kvm-numa-count range is 1-8": "", "--kvm-numa-count range is 1-8": "",
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "", "--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
"127.0.0.1": "", "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 Audit \u003c==": "",
"==\u003e Last Start \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/": "", "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": "", "System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
"Tag images": "", "Tag images": "",
"Tag to apply to the new image (optional)": "", "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 directory {{.path}} must be an absolute path": "",
"Target {{.path}} can not be empty": "", "Target {{.path}} can not be empty": "",
"Test docs have been saved at - {{.path}}": "", "Test docs have been saved at - {{.path}}": "",

View File

@ -19,7 +19,6 @@
"--kvm-numa-count range is 1-8": "", "--kvm-numa-count range is 1-8": "",
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "", "--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
"127.0.0.1": "", "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 Audit \u003c==": "",
"==\u003e Last Start \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/", "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": "", "System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
"Tag images": "", "Tag images": "",
"Tag to apply to the new image (optional)": "", "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 directory {{.path}} must be an absolute path": "",
"Target {{.path}} can not be empty": "", "Target {{.path}} can not be empty": "",
"Test docs have been saved at - {{.path}}": "", "Test docs have been saved at - {{.path}}": "",

View File

@ -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", "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 images": "Marquer des images",
"Tag to apply to the new image (optional)": "Tag à appliquer à la nouvelle image (facultatif)", "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 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", "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}}", "Test docs have been saved at - {{.path}}": "Les documents de test ont été enregistrés à - {{.path}}",

View File

@ -1,4 +1,5 @@
{ {
"\"The '{{.minikube_addon}}' addon is disabled": "'{{.minikube_addon}}' アドオンが無効です", "\"The '{{.minikube_addon}}' addon is disabled": "'{{.minikube_addon}}' アドオンが無効です",
"\"{{.context}}\" context has been updated to point to {{.hostname}}:{{.port}}": "「{{.context}}」コンテキストが更新されて、{{.hostname}}:{{.port}} を指すようになりました", "\"{{.context}}\" context has been updated to point to {{.hostname}}:{{.port}}": "「{{.context}}」コンテキストが更新されて、{{.hostname}}:{{.port}} を指すようになりました",
"\"{{.machineName}}\" does not exist, nothing to stop": "「{{.machineName}}」は存在しません。停止対象がありません", "\"{{.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": "", "System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
"Tag images": "イメージのタグ付与", "Tag images": "イメージのタグ付与",
"Tag to apply to the new image (optional)": "新しいイメージに適用するタグ (任意)", "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 directory {{.path}} must be an absolute path": "ターゲットディレクトリ {{.path}} は絶対パスでなければなりません。",
"Target {{.path}} can not be empty": "ターゲット {{.path}} は空にできません", "Target {{.path}} can not be empty": "ターゲット {{.path}} は空にできません",
"Test docs have been saved at - {{.path}}": "テストドキュメントは {{.path}} に保存されました", "Test docs have been saved at - {{.path}}": "テストドキュメントは {{.path}} に保存されました",
@ -1001,4 +1003,4 @@
"{{.profile}} profile is not valid: {{.err}}": "", "{{.profile}} profile is not valid: {{.err}}": "",
"{{.type}} is not yet a supported filesystem. We will try anyways!": "{{.type}} はまだサポートされていなファイルシステムです。とにかくやってみます!", "{{.type}} is not yet a supported filesystem. We will try anyways!": "{{.type}} はまだサポートされていなファイルシステムです。とにかくやってみます!",
"{{.url}} is not accessible: {{.error}}": "{{.url}} はアクセス可能ではありません。 {{.error}}" "{{.url}} is not accessible: {{.error}}": "{{.url}} はアクセス可能ではありません。 {{.error}}"
} }

View File

@ -24,7 +24,6 @@
"--kvm-numa-count range is 1-8": "--kvm-numa-count 범위는 1부터 8입니다", "--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": "", "--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
"127.0.0.1": "", "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 Audit \u003c==": "",
"==\u003e Last Start \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/": "", "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": "", "System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
"Tag images": "", "Tag images": "",
"Tag to apply to the new image (optional)": "", "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 directory {{.path}} must be an absolute path": "타겟 폴더 {{.path}} 는 절대 경로여야 합니다",
"Target {{.path}} can not be empty": "", "Target {{.path}} can not be empty": "",
"Test docs have been saved at - {{.path}}": "", "Test docs have been saved at - {{.path}}": "",

View File

@ -23,7 +23,6 @@
"--kvm-numa-count range is 1-8": "", "--kvm-numa-count range is 1-8": "",
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "", "--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
"127.0.0.1": "", "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 Audit \u003c==": "==\u003e Audyt \u003c==",
"==\u003e Last Start \u003c==": "==\u003e Ostatni start \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/", "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": "", "System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
"Tag images": "", "Tag images": "",
"Tag to apply to the new image (optional)": "", "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 directory {{.path}} must be an absolute path": "",
"Target {{.path}} can not be empty": "", "Target {{.path}} can not be empty": "",
"Test docs have been saved at - {{.path}}": "", "Test docs have been saved at - {{.path}}": "",

View File

@ -18,7 +18,6 @@
"--kvm-numa-count range is 1-8": "", "--kvm-numa-count range is 1-8": "",
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "", "--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
"127.0.0.1": "", "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 Audit \u003c==": "",
"==\u003e Last Start \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/": "", "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": "", "System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
"Tag images": "", "Tag images": "",
"Tag to apply to the new image (optional)": "", "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 directory {{.path}} must be an absolute path": "",
"Target {{.path}} can not be empty": "", "Target {{.path}} can not be empty": "",
"Test docs have been saved at - {{.path}}": "", "Test docs have been saved at - {{.path}}": "",

View File

@ -25,7 +25,6 @@
"--kvm-numa-count range is 1-8": "", "--kvm-numa-count range is 1-8": "",
"--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "", "--network flag is only valid with the docker/podman and KVM drivers, it will be ignored": "",
"127.0.0.1": "", "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 Audit \u003c==": "",
"==\u003e Last Start \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/", "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": "", "System only has {{.size}}MiB available, less than the required {{.req}}MiB for Kubernetes": "",
"Tag images": "", "Tag images": "",
"Tag to apply to the new image (optional)": "", "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 directory {{.path}} must be an absolute path": "",
"Target {{.path}} can not be empty": "", "Target {{.path}} can not be empty": "",
"Test docs have been saved at - {{.path}}": "", "Test docs have been saved at - {{.path}}": "",