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"
"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")`)
}
}

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"
"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

View File

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

View File

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

View File

@ -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 {

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")
}
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()))

View File

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

View File

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

View File

@ -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") {

View File

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

View File

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

View File

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

View File

@ -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}}": "",

View File

@ -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}}": "",

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",
"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}}",

View File

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

View File

@ -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}}": "",

View File

@ -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}}": "",

View File

@ -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}}": "",

View File

@ -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}}": "",