Add stand-alone image pull and image tag commands
Needed for testing, but maybe elsewhere as wellpull/12326/head
parent
350e1ccaa9
commit
380b846715
|
@ -165,6 +165,24 @@ $ minikube image unload image busybox
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pullImageCmd = &cobra.Command{
|
||||||
|
Use: "pull",
|
||||||
|
Short: "Pull images",
|
||||||
|
Example: `
|
||||||
|
$ minikube image pull busybox
|
||||||
|
`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
profile, err := config.LoadProfile(viper.GetString(config.ProfileName))
|
||||||
|
if err != nil {
|
||||||
|
exit.Error(reason.Usage, "loading profile", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := machine.PullImages(args, profile); err != nil {
|
||||||
|
exit.Error(reason.GuestImagePull, "Failed to pull images", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func createTar(dir string) (string, error) {
|
func createTar(dir string) (string, error) {
|
||||||
tar, err := docker.CreateTarStream(dir, dockerFile)
|
tar, err := docker.CreateTarStream(dir, dockerFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -245,6 +263,28 @@ $ minikube image ls
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var tagImageCmd = &cobra.Command{
|
||||||
|
Use: "tag",
|
||||||
|
Short: "Tag images",
|
||||||
|
Example: `
|
||||||
|
$ minikube image tag source target
|
||||||
|
`,
|
||||||
|
Aliases: []string{"list"},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
exit.Message(reason.Usage, "Please provide source and target image")
|
||||||
|
}
|
||||||
|
profile, err := config.LoadProfile(viper.GetString(config.ProfileName))
|
||||||
|
if err != nil {
|
||||||
|
exit.Error(reason.Usage, "loading profile", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := machine.TagImage(profile, args[0], args[1]); err != nil {
|
||||||
|
exit.Error(reason.GuestImageTag, "Failed to tag images", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
loadImageCmd.Flags().BoolVarP(&pull, "pull", "", false, "Pull the remote image (no caching)")
|
loadImageCmd.Flags().BoolVarP(&pull, "pull", "", false, "Pull the remote image (no caching)")
|
||||||
loadImageCmd.Flags().BoolVar(&imgDaemon, "daemon", false, "Cache image from docker daemon")
|
loadImageCmd.Flags().BoolVar(&imgDaemon, "daemon", false, "Cache image from docker daemon")
|
||||||
|
@ -252,6 +292,7 @@ func init() {
|
||||||
loadImageCmd.Flags().BoolVar(&overwrite, "overwrite", true, "Overwrite image even if same image:tag name exists")
|
loadImageCmd.Flags().BoolVar(&overwrite, "overwrite", true, "Overwrite image even if same image:tag name exists")
|
||||||
imageCmd.AddCommand(loadImageCmd)
|
imageCmd.AddCommand(loadImageCmd)
|
||||||
imageCmd.AddCommand(removeImageCmd)
|
imageCmd.AddCommand(removeImageCmd)
|
||||||
|
imageCmd.AddCommand(pullImageCmd)
|
||||||
buildImageCmd.Flags().StringVarP(&tag, "tag", "t", "", "Tag to apply to the new image (optional)")
|
buildImageCmd.Flags().StringVarP(&tag, "tag", "t", "", "Tag to apply to the new image (optional)")
|
||||||
buildImageCmd.Flags().BoolVarP(&push, "push", "", false, "Push the new image (requires tag)")
|
buildImageCmd.Flags().BoolVarP(&push, "push", "", false, "Push the new image (requires tag)")
|
||||||
buildImageCmd.Flags().StringVarP(&dockerFile, "file", "f", "", "Path to the Dockerfile to use (optional)")
|
buildImageCmd.Flags().StringVarP(&dockerFile, "file", "f", "", "Path to the Dockerfile to use (optional)")
|
||||||
|
@ -259,4 +300,5 @@ func init() {
|
||||||
buildImageCmd.Flags().StringArrayVar(&buildOpt, "build-opt", nil, "Specify arbitrary flags to pass to the build. (format: key=value)")
|
buildImageCmd.Flags().StringArrayVar(&buildOpt, "build-opt", nil, "Specify arbitrary flags to pass to the build. (format: key=value)")
|
||||||
imageCmd.AddCommand(buildImageCmd)
|
imageCmd.AddCommand(buildImageCmd)
|
||||||
imageCmd.AddCommand(listImageCmd)
|
imageCmd.AddCommand(listImageCmd)
|
||||||
|
imageCmd.AddCommand(tagImageCmd)
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,6 +305,16 @@ func (r *Containerd) RemoveImage(name string) error {
|
||||||
return removeCRIImage(r.Runner, name)
|
return removeCRIImage(r.Runner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagImage tags an image in this runtime
|
||||||
|
func (r *Containerd) TagImage(source string, target string) error {
|
||||||
|
klog.Infof("Tagging image %s: %s", source, target)
|
||||||
|
c := exec.Command("sudo", "ctr", "-n=k8s.io", "images", "tag", source, target)
|
||||||
|
if _, err := r.Runner.RunCmd(c); err != nil {
|
||||||
|
return errors.Wrapf(err, "ctr images tag")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func gitClone(cr CommandRunner, src string) (string, error) {
|
func gitClone(cr CommandRunner, src string) (string, error) {
|
||||||
// clone to a temporary directory
|
// clone to a temporary directory
|
||||||
rr, err := cr.RunCmd(exec.Command("mktemp", "-d"))
|
rr, err := cr.RunCmd(exec.Command("mktemp", "-d"))
|
||||||
|
|
|
@ -216,6 +216,16 @@ func (r *CRIO) RemoveImage(name string) error {
|
||||||
return removeCRIImage(r.Runner, name)
|
return removeCRIImage(r.Runner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagImage tags an image in this runtime
|
||||||
|
func (r *CRIO) TagImage(source string, target string) error {
|
||||||
|
klog.Infof("Tagging image %s: %s", source, target)
|
||||||
|
c := exec.Command("sudo", "podman", "tag", source, target)
|
||||||
|
if _, err := r.Runner.RunCmd(c); err != nil {
|
||||||
|
return errors.Wrap(err, "crio tag image")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// BuildImage builds an image into this runtime
|
// BuildImage builds an image into this runtime
|
||||||
func (r *CRIO) BuildImage(src string, file string, tag string, push bool, env []string, opts []string) error {
|
func (r *CRIO) BuildImage(src string, file string, tag string, push bool, env []string, opts []string) error {
|
||||||
klog.Infof("Building image: %s", src)
|
klog.Infof("Building image: %s", src)
|
||||||
|
|
|
@ -101,6 +101,8 @@ type Manager interface {
|
||||||
BuildImage(string, string, string, bool, []string, []string) error
|
BuildImage(string, string, string, bool, []string, []string) error
|
||||||
// Save an image from the runtime on a host
|
// Save an image from the runtime on a host
|
||||||
SaveImage(string, string) error
|
SaveImage(string, string) error
|
||||||
|
// Tag an image
|
||||||
|
TagImage(string, string) error
|
||||||
|
|
||||||
// ImageExists takes image name and image sha checks if an it exists
|
// ImageExists takes image name and image sha checks if an it exists
|
||||||
ImageExists(string, string) bool
|
ImageExists(string, string) bool
|
||||||
|
|
|
@ -244,6 +244,16 @@ func (r *Docker) RemoveImage(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagImage tags an image in this runtime
|
||||||
|
func (r *Docker) TagImage(source string, target string) error {
|
||||||
|
klog.Infof("Tagging image %s: %s", source, target)
|
||||||
|
c := exec.Command("docker", "tag", source, target)
|
||||||
|
if _, err := r.Runner.RunCmd(c); err != nil {
|
||||||
|
return errors.Wrap(err, "tag image docker.")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// BuildImage builds an image into this runtime
|
// BuildImage builds an image into this runtime
|
||||||
func (r *Docker) BuildImage(src string, file string, tag string, push bool, env []string, opts []string) error {
|
func (r *Docker) BuildImage(src string, file string, tag string, push bool, env []string, opts []string) error {
|
||||||
klog.Infof("Building image: %s", src)
|
klog.Infof("Building image: %s", src)
|
||||||
|
|
|
@ -539,3 +539,60 @@ func ListImages(profile *config.Profile) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagImage tags image in all nodes in profile
|
||||||
|
func TagImage(profile *config.Profile, source string, target string) error {
|
||||||
|
api, err := NewAPIClient()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error creating api client")
|
||||||
|
}
|
||||||
|
defer api.Close()
|
||||||
|
|
||||||
|
succeeded := []string{}
|
||||||
|
failed := []string{}
|
||||||
|
|
||||||
|
pName := profile.Name
|
||||||
|
|
||||||
|
c, err := config.Load(pName)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("Failed to load profile %q: %v", pName, err)
|
||||||
|
return errors.Wrapf(err, "error loading config for profile :%v", pName)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range c.Nodes {
|
||||||
|
m := config.MachineName(*c, n)
|
||||||
|
|
||||||
|
status, err := Status(api, m)
|
||||||
|
if err != nil {
|
||||||
|
klog.Warningf("error getting status for %s: %v", m, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if status == state.Running.String() {
|
||||||
|
h, err := api.Load(m)
|
||||||
|
if err != nil {
|
||||||
|
klog.Warningf("Failed to load machine %q: %v", m, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
runner, err := CommandRunner(h)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cruntime, err := cruntime.New(cruntime.Config{Type: c.KubernetesConfig.ContainerRuntime, Runner: runner})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error creating container runtime")
|
||||||
|
}
|
||||||
|
err = cruntime.TagImage(source, target)
|
||||||
|
if err != nil {
|
||||||
|
failed = append(failed, m)
|
||||||
|
klog.Warningf("Failed to tag image for profile %s %v", pName, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
succeeded = append(succeeded, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.Infof("succeeded tagging in: %s", strings.Join(succeeded, " "))
|
||||||
|
klog.Infof("failed tagging in: %s", strings.Join(failed, " "))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -315,8 +315,12 @@ var (
|
||||||
GuestImageLoad = Kind{ID: "GUEST_IMAGE_LOAD", ExitCode: ExGuestError}
|
GuestImageLoad = Kind{ID: "GUEST_IMAGE_LOAD", ExitCode: ExGuestError}
|
||||||
// minikube failed to remove an image
|
// minikube failed to remove an image
|
||||||
GuestImageRemove = Kind{ID: "GUEST_IMAGE_REMOVE", ExitCode: ExGuestError}
|
GuestImageRemove = Kind{ID: "GUEST_IMAGE_REMOVE", ExitCode: ExGuestError}
|
||||||
|
// minikube failed to pull an image
|
||||||
|
GuestImagePull = Kind{ID: "GUEST_IMAGE_PULL", ExitCode: ExGuestError}
|
||||||
// minikube failed to build an image
|
// minikube failed to build an image
|
||||||
GuestImageBuild = Kind{ID: "GUEST_IMAGE_BUILD", ExitCode: ExGuestError}
|
GuestImageBuild = Kind{ID: "GUEST_IMAGE_BUILD", ExitCode: ExGuestError}
|
||||||
|
// minikube failed to tag an image
|
||||||
|
GuestImageTag = Kind{ID: "GUEST_IMAGE_TAG", ExitCode: ExGuestError}
|
||||||
// minikube failed to load host
|
// minikube failed to load host
|
||||||
GuestLoadHost = Kind{ID: "GUEST_LOAD_HOST", ExitCode: ExGuestError}
|
GuestLoadHost = Kind{ID: "GUEST_LOAD_HOST", ExitCode: ExGuestError}
|
||||||
// minkube failed to create a mount
|
// minkube failed to create a mount
|
||||||
|
|
|
@ -216,6 +216,48 @@ $ minikube image ls
|
||||||
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## minikube image pull
|
||||||
|
|
||||||
|
Pull images
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Pull images
|
||||||
|
|
||||||
|
```shell
|
||||||
|
minikube image pull [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
$ minikube image pull busybox
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--add_dir_header If true, adds the file directory to the header of the log messages
|
||||||
|
--alsologtostderr log to standard error as well as files
|
||||||
|
-b, --bootstrapper string The name of the cluster bootstrapper that will set up the Kubernetes cluster. (default "kubeadm")
|
||||||
|
-h, --help
|
||||||
|
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
|
||||||
|
--log_dir string If non-empty, write log files in this directory
|
||||||
|
--log_file string If non-empty, use this log file
|
||||||
|
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
|
||||||
|
--logtostderr log to standard error instead of files
|
||||||
|
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level)
|
||||||
|
-p, --profile string The name of the minikube VM being used. This can be set to allow having multiple instances of minikube independently. (default "minikube")
|
||||||
|
--skip_headers If true, avoid header prefixes in the log messages
|
||||||
|
--skip_log_headers If true, avoid headers when opening log files
|
||||||
|
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
|
||||||
|
--user string Specifies the user executing the operation. Useful for auditing operations executed by 3rd party tools. Defaults to the operating system username.
|
||||||
|
-v, --v Level number for the log level verbosity
|
||||||
|
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
||||||
|
```
|
||||||
|
|
||||||
## minikube image rm
|
## minikube image rm
|
||||||
|
|
||||||
Remove one or more images
|
Remove one or more images
|
||||||
|
@ -264,3 +306,49 @@ $ minikube image unload image busybox
|
||||||
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## minikube image tag
|
||||||
|
|
||||||
|
Tag images
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Tag images
|
||||||
|
|
||||||
|
```shell
|
||||||
|
minikube image tag [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Aliases
|
||||||
|
|
||||||
|
[list]
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
$ minikube image tag source target
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--add_dir_header If true, adds the file directory to the header of the log messages
|
||||||
|
--alsologtostderr log to standard error as well as files
|
||||||
|
-b, --bootstrapper string The name of the cluster bootstrapper that will set up the Kubernetes cluster. (default "kubeadm")
|
||||||
|
-h, --help
|
||||||
|
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
|
||||||
|
--log_dir string If non-empty, write log files in this directory
|
||||||
|
--log_file string If non-empty, use this log file
|
||||||
|
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
|
||||||
|
--logtostderr log to standard error instead of files
|
||||||
|
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level)
|
||||||
|
-p, --profile string The name of the minikube VM being used. This can be set to allow having multiple instances of minikube independently. (default "minikube")
|
||||||
|
--skip_headers If true, avoid header prefixes in the log messages
|
||||||
|
--skip_log_headers If true, avoid headers when opening log files
|
||||||
|
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
|
||||||
|
--user string Specifies the user executing the operation. Useful for auditing operations executed by 3rd party tools. Defaults to the operating system username.
|
||||||
|
-v, --v Level number for the log level verbosity
|
||||||
|
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
@ -378,9 +378,15 @@ minikube failed to pull or load an image
|
||||||
"GUEST_IMAGE_REMOVE" (Exit code ExGuestError)
|
"GUEST_IMAGE_REMOVE" (Exit code ExGuestError)
|
||||||
minikube failed to remove an image
|
minikube failed to remove an image
|
||||||
|
|
||||||
|
"GUEST_IMAGE_PULL" (Exit code ExGuestError)
|
||||||
|
minikube failed to pull an image
|
||||||
|
|
||||||
"GUEST_IMAGE_BUILD" (Exit code ExGuestError)
|
"GUEST_IMAGE_BUILD" (Exit code ExGuestError)
|
||||||
minikube failed to build an image
|
minikube failed to build an image
|
||||||
|
|
||||||
|
"GUEST_IMAGE_TAG" (Exit code ExGuestError)
|
||||||
|
minikube failed to tag an image
|
||||||
|
|
||||||
"GUEST_LOAD_HOST" (Exit code ExGuestError)
|
"GUEST_LOAD_HOST" (Exit code ExGuestError)
|
||||||
minikube failed to load host
|
minikube failed to load host
|
||||||
|
|
||||||
|
|
|
@ -235,6 +235,7 @@
|
||||||
"Failed to load image": "",
|
"Failed to load image": "",
|
||||||
"Failed to persist images": "",
|
"Failed to persist images": "",
|
||||||
"Failed to pull image": "",
|
"Failed to pull image": "",
|
||||||
|
"Failed to pull images": "",
|
||||||
"Failed to reload cached images": "",
|
"Failed to reload cached images": "",
|
||||||
"Failed to remove image": "",
|
"Failed to remove image": "",
|
||||||
"Failed to save config {{.profile}}": "",
|
"Failed to save config {{.profile}}": "",
|
||||||
|
@ -245,6 +246,7 @@
|
||||||
"Failed to start container runtime": "",
|
"Failed to start container runtime": "",
|
||||||
"Failed to start {{.driver}} {{.driver_type}}. Running \"{{.cmd}}\" may fix it: {{.error}}": "",
|
"Failed to start {{.driver}} {{.driver_type}}. Running \"{{.cmd}}\" may fix it: {{.error}}": "",
|
||||||
"Failed to stop node {{.name}}": "",
|
"Failed to stop node {{.name}}": "",
|
||||||
|
"Failed to tag images": "",
|
||||||
"Failed to update cluster": "",
|
"Failed to update cluster": "",
|
||||||
"Failed to update config": "",
|
"Failed to update config": "",
|
||||||
"Failed unmount: {{.error}}": "",
|
"Failed unmount: {{.error}}": "",
|
||||||
|
@ -409,6 +411,7 @@
|
||||||
"Please make sure the service you are looking for is deployed or is in the correct namespace.": "",
|
"Please make sure the service you are looking for is deployed or is in the correct namespace.": "",
|
||||||
"Please provide a path or url to build": "",
|
"Please provide a path or url to build": "",
|
||||||
"Please provide an image in your local daemon to load into minikube via \u003cminikube image load IMAGE_NAME\u003e": "",
|
"Please provide an image in your local daemon to load into minikube via \u003cminikube image load IMAGE_NAME\u003e": "",
|
||||||
|
"Please provide source and target image": "",
|
||||||
"Please re-eval your docker-env, To ensure your environment variables have updated ports:\n\n\t'minikube -p {{.profile_name}} docker-env'\n\n\t": "",
|
"Please re-eval your docker-env, To ensure your environment variables have updated ports:\n\n\t'minikube -p {{.profile_name}} docker-env'\n\n\t": "",
|
||||||
"Please re-eval your podman-env, To ensure your environment variables have updated ports:\n\n\t'minikube -p {{.profile_name}} podman-env'\n\n\t": "",
|
"Please re-eval your podman-env, To ensure your environment variables have updated ports:\n\n\t'minikube -p {{.profile_name}} podman-env'\n\n\t": "",
|
||||||
"Please see {{.documentation_url}} for more details": "",
|
"Please see {{.documentation_url}} for more details": "",
|
||||||
|
@ -433,6 +436,7 @@
|
||||||
"Profile name '{{.profilename}}' is not valid": "",
|
"Profile name '{{.profilename}}' is not valid": "",
|
||||||
"Profile name should be unique": "",
|
"Profile name should be unique": "",
|
||||||
"Provide VM UUID to restore MAC address (hyperkit driver only)": "",
|
"Provide VM UUID to restore MAC address (hyperkit driver only)": "",
|
||||||
|
"Pull images": "",
|
||||||
"Pull the remote image (no caching)": "",
|
"Pull the remote image (no caching)": "",
|
||||||
"Pulling base image ...": "",
|
"Pulling base image ...": "",
|
||||||
"Push the new image (requires tag)": "",
|
"Push the new image (requires tag)": "",
|
||||||
|
@ -549,6 +553,7 @@
|
||||||
"Successfully stopped node {{.name}}": "",
|
"Successfully stopped node {{.name}}": "",
|
||||||
"Suggestion: {{.advice}}": "",
|
"Suggestion: {{.advice}}": "",
|
||||||
"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 to apply to the new image (optional)": "",
|
"Tag to apply to the new image (optional)": "",
|
||||||
"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": "",
|
||||||
|
|
Loading…
Reference in New Issue