diff --git a/pkg/addons/addons.go b/pkg/addons/addons.go index 251be26c8a8b..aa97479b4e55 100644 --- a/pkg/addons/addons.go +++ b/pkg/addons/addons.go @@ -185,6 +185,12 @@ https://github.com/kubernetes/minikube/issues/7332`, out.V{"driver_name": cc.Dri exit.Error(reason.GuestCpConfig, "Error getting primary control plane", err) } + // Persist images even if the machine is running so starting gets the correct images. + images, customRegistries, err := assets.SelectAndPersistImages(addon, cc) + if err != nil { + exit.Error(reason.HostSaveProfile, "Failed to persist images", err) + } + mName := config.MachineName(*cc, cp) host, err := machine.LoadHost(api, mName) if err != nil || !machine.IsRunning(api, mName) { @@ -224,7 +230,7 @@ https://github.com/kubernetes/minikube/issues/7332`, out.V{"driver_name": cc.Dri out.WarningT("At least needs control plane nodes to enable addon") } - data := assets.GenerateTemplateData(addon, cc.KubernetesConfig, networkInfo) + data := assets.GenerateTemplateData(addon, cc.KubernetesConfig, networkInfo, images, customRegistries) return enableOrDisableAddonInternal(cc, addon, runner, data, enable) } diff --git a/pkg/minikube/assets/addons.go b/pkg/minikube/assets/addons.go index d0e3acca6316..779b5b6791dc 100644 --- a/pkg/minikube/assets/addons.go +++ b/pkg/minikube/assets/addons.go @@ -660,8 +660,110 @@ var Addons = map[string]*Addon{ }), } +// parseMapString creates a map based on `str` which is encoded as =,=,... +func parseMapString(str string) map[string]string { + mapResult := make(map[string]string) + if str == "" { + return mapResult + } + for _, pairText := range strings.Split(str, ",") { + vals := strings.Split(pairText, "=") + if len(vals) != 2 { + out.WarningT("Ignoring invalid pair entry {{.pair}}", out.V{"pair": pairText}) + continue + } + mapResult[vals[0]] = vals[1] + } + return mapResult +} + +// mergeMaps creates a map with the union of `sourceMap` and `overrideMap` where collisions take the value of `overrideMap`. +func mergeMaps(sourceMap, overrideMap map[string]string) map[string]string { + result := make(map[string]string) + for name, value := range sourceMap { + result[name] = value + } + for name, value := range overrideMap { + result[name] = value + } + return result +} + +// filterKeySpace creates a map of the values in `targetMap` where the keys are also in `keySpace`. +func filterKeySpace(keySpace map[string]string, targetMap map[string]string) map[string]string { + result := make(map[string]string) + for name := range keySpace { + if value, ok := targetMap[name]; ok { + result[name] = value + } + } + return result +} + +// overrideDefaults creates a copy of `defaultMap` where `overrideMap` replaces any of its values that `overrideMap` contains. +func overrideDefaults(defaultMap, overrideMap map[string]string) map[string]string { + return mergeMaps(defaultMap, filterKeySpace(defaultMap, overrideMap)) +} + +// SelectAndPersistImages selects which images to use based on addon default images, previously persisted images, and newly requested images - which are then persisted for future enables. +func SelectAndPersistImages(addon *Addon, cc *config.ClusterConfig) (images, customRegistries map[string]string, err error) { + addonDefaultImages := addon.Images + if addonDefaultImages == nil { + addonDefaultImages = make(map[string]string) + } + + // Use previously configured custom images. + images = overrideDefaults(addonDefaultImages, cc.CustomAddonImages) + if viper.IsSet(config.AddonImages) { + // Parse the AddonImages flag if present. + newImages := parseMapString(viper.GetString(config.AddonImages)) + for name, image := range newImages { + if image == "" { + out.WarningT("Ignoring empty custom image {{.name}}", out.V{"name": name}) + delete(newImages, name) + continue + } + if _, ok := addonDefaultImages[name]; !ok { + out.WarningT("Ignoring unknown custom image {{.name}}", out.V{"name": name}) + } + } + // Use newly configured custom images. + images = overrideDefaults(addonDefaultImages, newImages) + // Store custom addon images to be written. + cc.CustomAddonImages = mergeMaps(cc.CustomAddonImages, images) + } + + // Use previously configured custom registries. + customRegistries = filterKeySpace(addonDefaultImages, cc.CustomAddonRegistries) // filter by images map because registry map may omit default registry. + if viper.IsSet(config.AddonRegistries) { + // Parse the AddonRegistries flag if present. + customRegistries = parseMapString(viper.GetString(config.AddonRegistries)) + for name := range customRegistries { + if _, ok := addonDefaultImages[name]; !ok { // check images map because registry map may omitted default registry + out.WarningT("Ignoring unknown custom registry {{.name}}", out.V{"name": name}) + delete(customRegistries, name) + } + } + // Since registry map may omit default registry, any previously set custom registries for these images must be cleared. + for name := range addonDefaultImages { + delete(cc.CustomAddonRegistries, name) + } + // Merge newly set registries into custom addon registries to be written. + cc.CustomAddonRegistries = mergeMaps(cc.CustomAddonRegistries, customRegistries) + } + + err = nil + // If images or registries were specified, save the config afterward. + if viper.IsSet(config.AddonImages) || viper.IsSet(config.AddonRegistries) { + // Since these values are only set when a user enables an addon, it is safe to refer to the profile name. + err = config.Write(viper.GetString(config.ProfileName), cc) + // Whether err is nil or not we still return here. + } + return images, customRegistries, err +} + // GenerateTemplateData generates template data for template assets -func GenerateTemplateData(addon *Addon, cfg config.KubernetesConfig, netInfo NetworkInfo) interface{} { +func GenerateTemplateData(addon *Addon, cfg config.KubernetesConfig, netInfo NetworkInfo, images, customRegistries map[string]string) interface{} { a := runtime.GOARCH // Some legacy docker images still need the -arch suffix @@ -670,6 +772,7 @@ func GenerateTemplateData(addon *Addon, cfg config.KubernetesConfig, netInfo Net if runtime.GOARCH != "amd64" { ea = "-" + runtime.GOARCH } + opts := struct { Arch string ExoticArch string @@ -688,58 +791,21 @@ func GenerateTemplateData(addon *Addon, cfg config.KubernetesConfig, netInfo Net LoadBalancerStartIP: cfg.LoadBalancerStartIP, LoadBalancerEndIP: cfg.LoadBalancerEndIP, CustomIngressCert: cfg.CustomIngressCert, - Images: addon.Images, + Images: images, Registries: addon.Registries, - CustomRegistries: make(map[string]string), + CustomRegistries: customRegistries, NetworkInfo: make(map[string]string), } if opts.ImageRepository != "" && !strings.HasSuffix(opts.ImageRepository, "/") { opts.ImageRepository += "/" } - - // Network info for generating template - opts.NetworkInfo["ControlPlaneNodeIP"] = netInfo.ControlPlaneNodeIP - opts.NetworkInfo["ControlPlaneNodePort"] = fmt.Sprint(netInfo.ControlPlaneNodePort) - - if opts.Images == nil { - opts.Images = make(map[string]string) // Avoid nil access when rendering - } - - images := viper.GetString(config.AddonImages) - if images != "" { - for _, image := range strings.Split(images, ",") { - vals := strings.Split(image, "=") - if len(vals) != 2 || vals[1] == "" { - out.WarningT("Ignoring invalid custom image {{.conf}}", out.V{"conf": image}) - continue - } - if _, ok := opts.Images[vals[0]]; ok { - opts.Images[vals[0]] = vals[1] - } else { - out.WarningT("Ignoring unknown custom image {{.name}}", out.V{"name": vals[0]}) - } - } - } - if opts.Registries == nil { opts.Registries = make(map[string]string) } - registries := viper.GetString(config.AddonRegistries) - if registries != "" { - for _, registry := range strings.Split(registries, ",") { - vals := strings.Split(registry, "=") - if len(vals) != 2 { - out.WarningT("Ignoring invalid custom registry {{.conf}}", out.V{"conf": registry}) - continue - } - if _, ok := opts.Images[vals[0]]; ok { // check images map because registry map may omitted default registry - opts.CustomRegistries[vals[0]] = vals[1] - } else { - out.WarningT("Ignoring unknown custom registry {{.name}}", out.V{"name": vals[0]}) - } - } - } + // Network info for generating template + opts.NetworkInfo["ControlPlaneNodeIP"] = netInfo.ControlPlaneNodeIP + opts.NetworkInfo["ControlPlaneNodePort"] = fmt.Sprint(netInfo.ControlPlaneNodePort) // Append postfix "/" to registries for k, v := range opts.Registries { diff --git a/pkg/minikube/assets/addons_test.go b/pkg/minikube/assets/addons_test.go new file mode 100644 index 000000000000..22530cff069c --- /dev/null +++ b/pkg/minikube/assets/addons_test.go @@ -0,0 +1,144 @@ +/* +Copyright 2016 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 assets + +import "testing" + +// mapsEqual returns true if and only if `a` contains all the same pairs as `b`. +func mapsEqual(a, b map[string]string) bool { + for aKey, aValue := range a { + if bValue, ok := b[aKey]; !ok || aValue != bValue { + return false + } + } + + for bKey := range b { + if _, ok := a[bKey]; !ok { + return false + } + } + return true +} + +func TestParseMapString(t *testing.T) { + cases := map[string]map[string]string{ + "Ardvark=1,B=2,Cantaloupe=3": {"Ardvark": "1", "B": "2", "Cantaloupe": "3"}, + "A=,B=2,C=": {"A": "", "B": "2", "C": ""}, + "": {}, + "malformed,good=howdy,manyequals==,": {"good": "howdy"}, + } + for actual, expected := range cases { + if parsedMap := parseMapString(actual); !mapsEqual(parsedMap, expected) { + t.Errorf("Parsed map from string \"%s\" differs from expected map: Actual: %v Expected: %v", actual, parsedMap, expected) + } + } +} + +func TestMergeMaps(t *testing.T) { + type TestCase struct { + sourceMap map[string]string + overrideMap map[string]string + expectedMap map[string]string + } + cases := []TestCase{ + { + sourceMap: map[string]string{"A": "1", "B": "2"}, + overrideMap: map[string]string{"B": "7", "C": "3"}, + expectedMap: map[string]string{"A": "1", "B": "7", "C": "3"}, + }, + { + sourceMap: map[string]string{"B": "7", "C": "3"}, + overrideMap: map[string]string{"A": "1", "B": "2"}, + expectedMap: map[string]string{"A": "1", "B": "2", "C": "3"}, + }, + { + sourceMap: map[string]string{"B": "7", "C": "3"}, + overrideMap: map[string]string{}, + expectedMap: map[string]string{"B": "7", "C": "3"}, + }, + { + sourceMap: map[string]string{}, + overrideMap: map[string]string{"B": "7", "C": "3"}, + expectedMap: map[string]string{"B": "7", "C": "3"}, + }, + } + for _, test := range cases { + if actualMap := mergeMaps(test.sourceMap, test.overrideMap); !mapsEqual(actualMap, test.expectedMap) { + t.Errorf("Merging maps (source=%v, override=%v) differs from expected map: Actual: %v Expected: %v", test.sourceMap, test.overrideMap, actualMap, test.expectedMap) + } + } +} + +func TestFilterKeySpace(t *testing.T) { + type TestCase struct { + keySpace map[string]string + targetMap map[string]string + expectedMap map[string]string + } + cases := []TestCase{ + { + keySpace: map[string]string{"A": "0", "B": ""}, + targetMap: map[string]string{"B": "1", "C": "2", "D": "3"}, + expectedMap: map[string]string{"B": "1"}, + }, + { + keySpace: map[string]string{}, + targetMap: map[string]string{"B": "1", "C": "2", "D": "3"}, + expectedMap: map[string]string{}, + }, + { + keySpace: map[string]string{"B": "1", "C": "2", "D": "3"}, + targetMap: map[string]string{}, + expectedMap: map[string]string{}, + }, + } + for _, test := range cases { + if actualMap := filterKeySpace(test.keySpace, test.targetMap); !mapsEqual(actualMap, test.expectedMap) { + t.Errorf("Filtering keyspace of map (keyspace=%v, target=%v) differs from expected map: Actual: %v Expected: %v", test.keySpace, test.targetMap, actualMap, test.expectedMap) + } + } +} + +func TestOverrideDefautls(t *testing.T) { + type TestCase struct { + defaultMap map[string]string + overrideMap map[string]string + expectedMap map[string]string + } + cases := []TestCase{ + { + defaultMap: map[string]string{"A": "1", "B": "2", "C": "3"}, + overrideMap: map[string]string{"B": "7", "C": "8"}, + expectedMap: map[string]string{"A": "1", "B": "7", "C": "8"}, + }, + { + defaultMap: map[string]string{"A": "1", "B": "2", "C": "3"}, + overrideMap: map[string]string{"B": "7", "D": "8", "E": "9"}, + expectedMap: map[string]string{"A": "1", "B": "7", "C": "3"}, + }, + { + defaultMap: map[string]string{"A": "1", "B": "2", "C": "3"}, + overrideMap: map[string]string{"B": "7", "D": "8", "E": "9"}, + expectedMap: map[string]string{"A": "1", "B": "7", "C": "3"}, + }, + } + for _, test := range cases { + if actualMap := overrideDefaults(test.defaultMap, test.overrideMap); !mapsEqual(actualMap, test.expectedMap) { + t.Errorf("Override defaults (defaults=%v, overrides=%v) differs from expected map: Actual: %v Expected: %v", test.defaultMap, test.overrideMap, actualMap, test.expectedMap) + } + } +} diff --git a/pkg/minikube/config/types.go b/pkg/minikube/config/types.go index 672d32bd1203..aee9b1ac2840 100644 --- a/pkg/minikube/config/types.go +++ b/pkg/minikube/config/types.go @@ -74,7 +74,9 @@ type ClusterConfig struct { KubernetesConfig KubernetesConfig Nodes []Node Addons map[string]bool - VerifyComponents map[string]bool // map of components to verify and wait for after start. + CustomAddonImages map[string]string // Maps image names to the image to use for addons. e.g. Dashboard -> k8s.gcr.io/echoserver:1.4 makes dashboard addon use echoserver for its Dashboard deployment. + CustomAddonRegistries map[string]string // Maps image names to the registry to use for addons. See CustomAddonImages for example. + VerifyComponents map[string]bool // map of components to verify and wait for after start. StartHostTimeout time.Duration ScheduledStop *ScheduledStopConfig ExposedPorts []string // Only used by the docker and podman driver diff --git a/site/content/en/docs/contrib/tests.en.md b/site/content/en/docs/contrib/tests.en.md index 1e43bcd315e2..ec605bbd23cb 100644 --- a/site/content/en/docs/contrib/tests.en.md +++ b/site/content/en/docs/contrib/tests.en.md @@ -332,6 +332,9 @@ runs the initial minikube start #### validateDeploying deploys an app the minikube cluster +#### validateEnableAddonWhileActive +makes sure addons can be enabled while cluster is active. + #### validateStop tests minikube stop diff --git a/test/integration/start_stop_delete_test.go b/test/integration/start_stop_delete_test.go index 471e85b1136f..85e0f0f924cc 100644 --- a/test/integration/start_stop_delete_test.go +++ b/test/integration/start_stop_delete_test.go @@ -107,6 +107,7 @@ func TestStartStop(t *testing.T) { }{ {"FirstStart", validateFirstStart}, {"DeployApp", validateDeploying}, + {"EnableAddonWhileActive", validateEnableAddonWhileActive}, {"Stop", validateStop}, {"EnableAddonAfterStop", validateEnableAddonAfterStop}, {"SecondStart", validateSecondStart}, @@ -169,6 +170,31 @@ func validateDeploying(ctx context.Context, t *testing.T, profile string, tcName } } +// validateEnableAddonWhileActive makes sure addons can be enabled while cluster is active. +func validateEnableAddonWhileActive(ctx context.Context, t *testing.T, profile string, tcName string, tcVersion string, startArgs []string) { + defer PostMortemLogs(t, profile) + + // Enable an addon to assert it requests the correct image. + rr, err := Run(t, exec.CommandContext(ctx, Target(), "addons", "enable", "metrics-server", "-p", profile, "--images=MetricsServer=k8s.gcr.io/echoserver:1.4", "--registries=MetricsServer=fake.domain")) + if err != nil { + t.Errorf("failed to enable an addon post-stop. args %q: %v", rr.Command(), err) + } + + if strings.Contains(tcName, "cni") { + t.Logf("WARNING: cni mode requires additional setup before pods can schedule :(") + return + } + + rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "describe", "deploy/metrics-server", "-n", "kube-system")) + if err != nil { + t.Errorf("failed to get info on auto-pause deployments. args %q: %v", rr.Command(), err) + } + deploymentInfo := rr.Stdout.String() + if !strings.Contains(deploymentInfo, " fake.domain/k8s.gcr.io/echoserver:1.4") { + t.Errorf("addon did not load correct image. Expected to contain \" fake.domain/k8s.gcr.io/echoserver:1.4\". Addon deployment info: %s", deploymentInfo) + } +} + // validateStop tests minikube stop func validateStop(ctx context.Context, t *testing.T, profile string, tcName string, tcVersion string, startArgs []string) { defer PostMortemLogs(t, profile) @@ -190,7 +216,7 @@ func validateEnableAddonAfterStop(ctx context.Context, t *testing.T, profile str } // Enable an addon to assert it comes up afterwards - rr, err := Run(t, exec.CommandContext(ctx, Target(), "addons", "enable", "dashboard", "-p", profile)) + rr, err := Run(t, exec.CommandContext(ctx, Target(), "addons", "enable", "dashboard", "-p", profile, "--images=MetricsScraper=k8s.gcr.io/echoserver:1.4")) if err != nil { t.Errorf("failed to enable an addon post-stop. args %q: %v", rr.Command(), err) } @@ -229,9 +255,20 @@ func validateAddonAfterStop(ctx context.Context, t *testing.T, profile string, t defer PostMortemLogs(t, profile) if strings.Contains(tcName, "cni") { t.Logf("WARNING: cni mode requires additional setup before pods can schedule :(") - } else if _, err := PodWait(ctx, t, profile, "kubernetes-dashboard", "k8s-app=kubernetes-dashboard", Minutes(9)); err != nil { + return + } + if _, err := PodWait(ctx, t, profile, "kubernetes-dashboard", "k8s-app=kubernetes-dashboard", Minutes(9)); err != nil { t.Errorf("failed waiting for 'addon dashboard' pod post-stop-start: %v", err) } + + rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "describe", "deploy/dashboard-metrics-scraper", "-n", "kubernetes-dashboard")) + if err != nil { + t.Errorf("failed to get info on kubernetes-dashboard deployments. args %q: %v", rr.Command(), err) + } + deploymentInfo := rr.Stdout.String() + if !strings.Contains(deploymentInfo, " k8s.gcr.io/echoserver:1.4") { + t.Errorf("addon did not load correct image. Expected to contain \" k8s.gcr.io/echoserver:1.4\". Addon deployment info: %s", deploymentInfo) + } } // validateKubernetesImages verifies that a restarted cluster contains all the necessary images diff --git a/translations/de.json b/translations/de.json index 17cea8384a36..fac162380c7b 100644 --- a/translations/de.json +++ b/translations/de.json @@ -241,6 +241,7 @@ "Failed to list cached images": "", "Failed to list images": "", "Failed to load image": "", + "Failed to persist images": "", "Failed to pull image": "", "Failed to reload cached images": "", "Failed to remove image": "", @@ -314,8 +315,8 @@ "If you are running minikube within a VM, consider using --driver=none:": "", "If you are still interested to make {{.driver_name}} driver work. The following suggestions might help you get passed this issue:": "", "If you don't want your credentials mounted into a specific pod, add a label with the `gcp-auth-skip-secret` key to your pod configuration.": "", - "Ignoring invalid custom image {{.conf}}": "", - "Ignoring invalid custom registry {{.conf}}": "", + "Ignoring empty custom image {{.name}}": "", + "Ignoring invalid pair entry {{.pair}}": "", "Ignoring unknown custom image {{.name}}": "", "Ignoring unknown custom registry {{.name}}": "", "Images Commands:": "", diff --git a/translations/es.json b/translations/es.json index 456bd63922b9..3a5385341fa2 100644 --- a/translations/es.json +++ b/translations/es.json @@ -246,6 +246,7 @@ "Failed to list cached images": "", "Failed to list images": "", "Failed to load image": "", + "Failed to persist images": "", "Failed to pull image": "", "Failed to reload cached images": "", "Failed to remove image": "", @@ -319,8 +320,8 @@ "If you are running minikube within a VM, consider using --driver=none:": "", "If you are still interested to make {{.driver_name}} driver work. The following suggestions might help you get passed this issue:": "", "If you don't want your credentials mounted into a specific pod, add a label with the `gcp-auth-skip-secret` key to your pod configuration.": "", - "Ignoring invalid custom image {{.conf}}": "", - "Ignoring invalid custom registry {{.conf}}": "", + "Ignoring empty custom image {{.name}}": "", + "Ignoring invalid pair entry {{.pair}}": "", "Ignoring unknown custom image {{.name}}": "", "Ignoring unknown custom registry {{.name}}": "", "Images Commands:": "", diff --git a/translations/fr.json b/translations/fr.json index a8d5ed46f653..795b3ac85f4d 100644 --- a/translations/fr.json +++ b/translations/fr.json @@ -243,6 +243,7 @@ "Failed to list cached images": "", "Failed to list images": "", "Failed to load image": "", + "Failed to persist images": "", "Failed to pull image": "", "Failed to reload cached images": "", "Failed to remove image": "", @@ -316,8 +317,8 @@ "If you are running minikube within a VM, consider using --driver=none:": "", "If you are still interested to make {{.driver_name}} driver work. The following suggestions might help you get passed this issue:": "", "If you don't want your credentials mounted into a specific pod, add a label with the `gcp-auth-skip-secret` key to your pod configuration.": "", - "Ignoring invalid custom image {{.conf}}": "", - "Ignoring invalid custom registry {{.conf}}": "", + "Ignoring empty custom image {{.name}}": "", + "Ignoring invalid pair entry {{.pair}}": "", "Ignoring unknown custom image {{.name}}": "", "Ignoring unknown custom registry {{.name}}": "", "Images Commands:": "", diff --git a/translations/ja.json b/translations/ja.json index 03ef3465e601..e135ea5b7fd3 100644 --- a/translations/ja.json +++ b/translations/ja.json @@ -234,6 +234,7 @@ "Failed to list cached images": "", "Failed to list images": "", "Failed to load image": "", + "Failed to persist images": "", "Failed to pull image": "", "Failed to reload cached images": "", "Failed to remove image": "", @@ -304,8 +305,8 @@ "If you are running minikube within a VM, consider using --driver=none:": "", "If you are still interested to make {{.driver_name}} driver work. The following suggestions might help you get passed this issue:": "", "If you don't want your credentials mounted into a specific pod, add a label with the `gcp-auth-skip-secret` key to your pod configuration.": "", - "Ignoring invalid custom image {{.conf}}": "", - "Ignoring invalid custom registry {{.conf}}": "", + "Ignoring empty custom image {{.name}}": "", + "Ignoring invalid pair entry {{.pair}}": "", "Ignoring unknown custom image {{.name}}": "", "Ignoring unknown custom registry {{.name}}": "", "Images Commands:": "イメージ用コマンド:", diff --git a/translations/ko.json b/translations/ko.json index bd237a8bd122..919ac1b60fb3 100644 --- a/translations/ko.json +++ b/translations/ko.json @@ -263,6 +263,7 @@ "Failed to list cached images": "캐시된 이미지를 조회하는 데 실패하였습니다", "Failed to list images": "", "Failed to load image": "", + "Failed to persist images": "", "Failed to pull image": "", "Failed to reload cached images": "캐시된 이미지를 다시 불러오는 데 실패하였습니다", "Failed to remove image": "", @@ -338,8 +339,8 @@ "If you are running minikube within a VM, consider using --driver=none:": "", "If you are still interested to make {{.driver_name}} driver work. The following suggestions might help you get passed this issue:": "", "If you don't want your credentials mounted into a specific pod, add a label with the `gcp-auth-skip-secret` key to your pod configuration.": "", - "Ignoring invalid custom image {{.conf}}": "", - "Ignoring invalid custom registry {{.conf}}": "", + "Ignoring empty custom image {{.name}}": "", + "Ignoring invalid pair entry {{.pair}}": "", "Ignoring unknown custom image {{.name}}": "", "Ignoring unknown custom registry {{.name}}": "", "Images Commands:": "이미지 명령어", diff --git a/translations/pl.json b/translations/pl.json index 34d0ba69cf8b..223afa20589e 100644 --- a/translations/pl.json +++ b/translations/pl.json @@ -251,6 +251,7 @@ "Failed to list cached images": "", "Failed to list images": "", "Failed to load image": "", + "Failed to persist images": "", "Failed to pull image": "", "Failed to reload cached images": "", "Failed to remove image": "", @@ -326,8 +327,8 @@ "If you are running minikube within a VM, consider using --driver=none:": "", "If you are still interested to make {{.driver_name}} driver work. The following suggestions might help you get passed this issue:": "", "If you don't want your credentials mounted into a specific pod, add a label with the `gcp-auth-skip-secret` key to your pod configuration.": "", - "Ignoring invalid custom image {{.conf}}": "", - "Ignoring invalid custom registry {{.conf}}": "", + "Ignoring empty custom image {{.name}}": "", + "Ignoring invalid pair entry {{.pair}}": "", "Ignoring unknown custom image {{.name}}": "", "Ignoring unknown custom registry {{.name}}": "", "Images Commands:": "", diff --git a/translations/strings.txt b/translations/strings.txt index abf26870a45c..36aa212d4f44 100644 --- a/translations/strings.txt +++ b/translations/strings.txt @@ -226,6 +226,7 @@ "Failed to list cached images": "", "Failed to list images": "", "Failed to load image": "", + "Failed to persist images": "", "Failed to pull image": "", "Failed to reload cached images": "", "Failed to remove image": "", @@ -294,8 +295,8 @@ "If you are running minikube within a VM, consider using --driver=none:": "", "If you are still interested to make {{.driver_name}} driver work. The following suggestions might help you get passed this issue:": "", "If you don't want your credentials mounted into a specific pod, add a label with the `gcp-auth-skip-secret` key to your pod configuration.": "", - "Ignoring invalid custom image {{.conf}}": "", - "Ignoring invalid custom registry {{.conf}}": "", + "Ignoring empty custom image {{.name}}": "", + "Ignoring invalid pair entry {{.pair}}": "", "Ignoring unknown custom image {{.name}}": "", "Ignoring unknown custom registry {{.name}}": "", "Images Commands:": "", diff --git a/translations/zh-CN.json b/translations/zh-CN.json index 852adfc7d1f2..64c27b87980a 100644 --- a/translations/zh-CN.json +++ b/translations/zh-CN.json @@ -312,6 +312,7 @@ "Failed to list cached images": "无法列出缓存镜像", "Failed to list images": "", "Failed to load image": "", + "Failed to persist images": "", "Failed to pull image": "", "Failed to reload cached images": "重新加载缓存镜像失败", "Failed to remove image": "", @@ -395,8 +396,8 @@ "If you are running minikube within a VM, consider using --driver=none:": "", "If you are still interested to make {{.driver_name}} driver work. The following suggestions might help you get passed this issue:": "", "If you don't want your credentials mounted into a specific pod, add a label with the `gcp-auth-skip-secret` key to your pod configuration.": "", - "Ignoring invalid custom image {{.conf}}": "", - "Ignoring invalid custom registry {{.conf}}": "", + "Ignoring empty custom image {{.name}}": "", + "Ignoring invalid pair entry {{.pair}}": "", "Ignoring unknown custom image {{.name}}": "", "Ignoring unknown custom registry {{.name}}": "", "Images Commands:": "",