Merge branch 'master' into retry-cgroups

pull/8974/head
Thomas Stromberg 2020-08-12 16:40:36 -07:00
commit 1f4bfa195e
15 changed files with 336 additions and 53 deletions

View File

@ -794,7 +794,7 @@ jobs:
cp minikube-darwin-amd64 minikube cp minikube-darwin-amd64 minikube
chmod a+x minikube* chmod a+x minikube*
START_TIME=$(date -u +%s) START_TIME=$(date -u +%s)
KUBECONFIG=$(pwd)/testhome/kubeconfig MINIKUBE_HOME=$(pwd)/testhome ./e2e-darwin-amd64 -minikube-start-args=--vm-driver=virtualbox -test.run "(TestAddons|TestCertOptions|TestSkaffold)" -test.timeout=15m -test.v -timeout-multiplier=3 -binary=./minikube-darwin-amd64 2>&1 | tee ./report/testout.txt KUBECONFIG=$(pwd)/testhome/kubeconfig MINIKUBE_HOME=$(pwd)/testhome ./e2e-darwin-amd64 -minikube-start-args=--vm-driver=virtualbox -test.run "(TestAddons|TestCertOptions|TestSkaffold)" -test.timeout=20m -test.v -timeout-multiplier=1.5 -binary=./minikube-darwin-amd64 2>&1 | tee ./report/testout.txt
END_TIME=$(date -u +%s) END_TIME=$(date -u +%s)
TIME_ELAPSED=$(($END_TIME-$START_TIME)) TIME_ELAPSED=$(($END_TIME-$START_TIME))
min=$((${TIME_ELAPSED}/60)) min=$((${TIME_ELAPSED}/60))
@ -980,7 +980,7 @@ jobs:
chmod a+x e2e-* chmod a+x e2e-*
chmod a+x minikube-* chmod a+x minikube-*
START_TIME=$(date -u +%s) START_TIME=$(date -u +%s)
KUBECONFIG=$(pwd)/testhome/kubeconfig MINIKUBE_HOME=$(pwd)/testhome ./e2e-darwin-amd64 -minikube-start-args=--driver=virtualbox -test.run "TestMultiNode" -test.timeout=15m -test.v -timeout-multiplier=1.5 -binary=./minikube-darwin-amd64 2>&1 | tee ./report/testout.txt KUBECONFIG=$(pwd)/testhome/kubeconfig MINIKUBE_HOME=$(pwd)/testhome ./e2e-darwin-amd64 -minikube-start-args=--driver=virtualbox -test.run "TestMultiNode" -test.timeout=17m -test.v -timeout-multiplier=1.5 -binary=./minikube-darwin-amd64 2>&1 | tee ./report/testout.txt
END_TIME=$(date -u +%s) END_TIME=$(date -u +%s)
TIME_ELAPSED=$(($END_TIME-$START_TIME)) TIME_ELAPSED=$(($END_TIME-$START_TIME))
min=$((${TIME_ELAPSED}/60)) min=$((${TIME_ELAPSED}/60))

View File

@ -792,7 +792,7 @@ jobs:
cp minikube-darwin-amd64 minikube cp minikube-darwin-amd64 minikube
chmod a+x minikube* chmod a+x minikube*
START_TIME=$(date -u +%s) START_TIME=$(date -u +%s)
KUBECONFIG=$(pwd)/testhome/kubeconfig MINIKUBE_HOME=$(pwd)/testhome ./e2e-darwin-amd64 -minikube-start-args=--vm-driver=virtualbox -test.run "(TestAddons|TestCertOptions|TestSkaffold)" -test.timeout=15m -test.v -timeout-multiplier=3 -binary=./minikube-darwin-amd64 2>&1 | tee ./report/testout.txt KUBECONFIG=$(pwd)/testhome/kubeconfig MINIKUBE_HOME=$(pwd)/testhome ./e2e-darwin-amd64 -minikube-start-args=--vm-driver=virtualbox -test.run "(TestAddons|TestCertOptions|TestSkaffold)" -test.timeout=20m -test.v -timeout-multiplier=1.5 -binary=./minikube-darwin-amd64 2>&1 | tee ./report/testout.txt
END_TIME=$(date -u +%s) END_TIME=$(date -u +%s)
TIME_ELAPSED=$(($END_TIME-$START_TIME)) TIME_ELAPSED=$(($END_TIME-$START_TIME))
min=$((${TIME_ELAPSED}/60)) min=$((${TIME_ELAPSED}/60))
@ -978,7 +978,7 @@ jobs:
chmod a+x e2e-* chmod a+x e2e-*
chmod a+x minikube-* chmod a+x minikube-*
START_TIME=$(date -u +%s) START_TIME=$(date -u +%s)
KUBECONFIG=$(pwd)/testhome/kubeconfig MINIKUBE_HOME=$(pwd)/testhome ./e2e-darwin-amd64 -minikube-start-args=--driver=virtualbox -test.run "TestMultiNode" -test.timeout=15m -test.v -timeout-multiplier=1.5 -binary=./minikube-darwin-amd64 2>&1 | tee ./report/testout.txt KUBECONFIG=$(pwd)/testhome/kubeconfig MINIKUBE_HOME=$(pwd)/testhome ./e2e-darwin-amd64 -minikube-start-args=--driver=virtualbox -test.run "TestMultiNode" -test.timeout=17m -test.v -timeout-multiplier=1.5 -binary=./minikube-darwin-amd64 2>&1 | tee ./report/testout.txt
END_TIME=$(date -u +%s) END_TIME=$(date -u +%s)
TIME_ELAPSED=$(($END_TIME-$START_TIME)) TIME_ELAPSED=$(($END_TIME-$START_TIME))
min=$((${TIME_ELAPSED}/60)) min=$((${TIME_ELAPSED}/60))

View File

@ -1,5 +1,46 @@
# Release Notes # Release Notes
## Version 1.12.3 - 2020-08-12
Features:
* Make waiting for Host configurable via --wait-timeout flag [#8948](https://github.com/kubernetes/minikube/pull/8948)
Bug Fixes:
* Ignore localhost proxy started with scheme. [#8885](https://github.com/kubernetes/minikube/pull/8885)
* Improve error handling for validating memory limits [#8959](https://github.com/kubernetes/minikube/pull/8959)
* Skip validations if --force is supplied [#8969](https://github.com/kubernetes/minikube/pull/8969)
* Fix handling of parseIP error [#8820](https://github.com/kubernetes/minikube/pull/8820)
Improvements:
* GCP Auth Addon: Exit with better error messages [#8932](https://github.com/kubernetes/minikube/pull/8932)
* Add warning for ingress addon enabled with driver of none [#8870](https://github.com/kubernetes/minikube/pull/8870)
* Update Japanese translation [#8967](https://github.com/kubernetes/minikube/pull/8967)
* Fix for a few typos in polish translations [#8950](https://github.com/kubernetes/minikube/pull/8950)
Thank you to our contributors for this release!
- Anders F Björklund
- Andrej Guran
- Chris Paika
- Dean Coakley
- Evgeny Shmarnev
- Ling Samuel
- Ma Xinjian
- Marcin Niemira
- Medya Ghazizadeh
- Pablo Caderno
- Priya Wadhwa
- RA489
- Sharif Elgamal
- TAKAHASHI Shuuji
- Thomas Strömberg
- inductor
- priyawadhwa
- programistka
## Version 1.12.2 - 2020-08-03 ## Version 1.12.2 - 2020-08-03
Features: Features:

View File

@ -15,7 +15,7 @@
# Bump these on release - and please check ISO_VERSION for correctness. # Bump these on release - and please check ISO_VERSION for correctness.
VERSION_MAJOR ?= 1 VERSION_MAJOR ?= 1
VERSION_MINOR ?= 12 VERSION_MINOR ?= 12
VERSION_BUILD ?= 2 VERSION_BUILD ?= 3
RAW_VERSION=$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_BUILD) RAW_VERSION=$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_BUILD)
VERSION ?= v$(RAW_VERSION) VERSION ?= v$(RAW_VERSION)
@ -34,6 +34,7 @@ GO_VERSION ?= 1.14.6
INSTALL_SIZE ?= $(shell du out/minikube-windows-amd64.exe | cut -f1) INSTALL_SIZE ?= $(shell du out/minikube-windows-amd64.exe | cut -f1)
BUILDROOT_BRANCH ?= 2019.02.11 BUILDROOT_BRANCH ?= 2019.02.11
REGISTRY?=gcr.io/k8s-minikube REGISTRY?=gcr.io/k8s-minikube
REGISTRY_GH?=docker.pkg.github.com/kubernetes/minikube
# Get git commit id # Get git commit id
COMMIT_NO := $(shell git rev-parse HEAD 2> /dev/null || true) COMMIT_NO := $(shell git rev-parse HEAD 2> /dev/null || true)
@ -46,6 +47,10 @@ BUILD_IMAGE ?= us.gcr.io/k8s-artifacts-prod/build-image/kube-cross:v$(GO_VERSIO
ISO_BUILD_IMAGE ?= $(REGISTRY)/buildroot-image ISO_BUILD_IMAGE ?= $(REGISTRY)/buildroot-image
KVM_BUILD_IMAGE ?= $(REGISTRY)/kvm-build-image:$(GO_VERSION) KVM_BUILD_IMAGE ?= $(REGISTRY)/kvm-build-image:$(GO_VERSION)
KIC_BASE_IMAGE_GCR ?= $(REGISTRY)/kicbase:$(KIC_VERSION)
KIC_BASE_IMAGE_GH ?= $(REGISTRY_GH)/kicbase:$(KIC_VERSION)
KIC_BASE_IMAGE_HUB ?= kicbase/stable:$(KIC_VERSION)
ISO_BUCKET ?= minikube/iso ISO_BUCKET ?= minikube/iso
MINIKUBE_VERSION ?= $(ISO_VERSION) MINIKUBE_VERSION ?= $(ISO_VERSION)
@ -130,6 +135,20 @@ KVM2_LDFLAGS := -X k8s.io/minikube/pkg/drivers/kvm.version=$(VERSION) -X k8s.io/
# hyperkit ldflags # hyperkit ldflags
HYPERKIT_LDFLAGS := -X k8s.io/minikube/pkg/drivers/hyperkit.version=$(VERSION) -X k8s.io/minikube/pkg/drivers/hyperkit.gitCommitID=$(COMMIT) HYPERKIT_LDFLAGS := -X k8s.io/minikube/pkg/drivers/hyperkit.version=$(VERSION) -X k8s.io/minikube/pkg/drivers/hyperkit.gitCommitID=$(COMMIT)
# autopush artefacts
AUTOPUSH ?=
# don't ask for user confirmation
IN_CI := false
# $(call user_confirm, message)
define user_confirm
@if [ "${IN_CI}" = "false" ]; then\
echo "⚠️ $(1)";\
read -p "Do you want to proceed? (Y/N): " confirm && echo $$confirm | grep -iq "^[yY]" || exit 1;\
fi
endef
# $(call DOCKER, image, command) # $(call DOCKER, image, command)
define DOCKER define DOCKER
docker run --rm -e GOCACHE=/app/.cache -e IN_DOCKER=1 --user $(shell id -u):$(shell id -g) -w /app -v $(PWD):/app -v $(GOPATH):/go --init $(1) /bin/bash -c '$(2)' docker run --rm -e GOCACHE=/app/.cache -e IN_DOCKER=1 --user $(shell id -u):$(shell id -g) -w /app -v $(PWD):/app -v $(GOPATH):/go --init $(1) /bin/bash -c '$(2)'
@ -530,9 +549,12 @@ storage-provisioner-image: out/storage-provisioner-$(GOARCH) ## Build storage-pr
.PHONY: kic-base-image .PHONY: kic-base-image
kic-base-image: ## builds the base image used for kic. kic-base-image: ## builds the base image used for kic.
docker rmi -f $(REGISTRY)/kicbase:$(KIC_VERSION)-snapshot || true docker rmi -f $(KIC_BASE_IMAGE_GCR)-snapshot || true
docker build -f ./deploy/kicbase/Dockerfile -t local/kicbase:$(KIC_VERSION)-snapshot --build-arg COMMIT_SHA=${VERSION}-$(COMMIT) --cache-from $(REGISTRY)/kicbase:$(KIC_VERSION) --target base ./deploy/kicbase docker build -f ./deploy/kicbase/Dockerfile -t local/kicbase:$(KIC_VERSION)-snapshot --build-arg COMMIT_SHA=${VERSION}-$(COMMIT) --cache-from $(KIC_BASE_IMAGE_GCR) --target base ./deploy/kicbase
docker tag local/kicbase:$(KIC_VERSION)-snapshot $(REGISTRY)/kicbase:$(KIC_VERSION)-snapshot docker tag local/kicbase:$(KIC_VERSION)-snapshot $(KIC_BASE_IMAGE_GCR)-snapshot
docker tag local/kicbase:$(KIC_VERSION)-snapshot $(KIC_BASE_IMAGE_GCR)
docker tag local/kicbase:$(KIC_VERSION)-snapshot $(KIC_BASE_IMAGE_HUB)
docker tag local/kicbase:$(KIC_VERSION)-snapshot $(KIC_BASE_IMAGE_GH)
.PHONY: upload-preloaded-images-tar .PHONY: upload-preloaded-images-tar
upload-preloaded-images-tar: out/minikube # Upload the preloaded images for oldest supported, newest supported, and default kubernetes versions to GCS. upload-preloaded-images-tar: out/minikube # Upload the preloaded images for oldest supported, newest supported, and default kubernetes versions to GCS.
@ -543,6 +565,35 @@ upload-preloaded-images-tar: out/minikube # Upload the preloaded images for olde
push-storage-provisioner-image: storage-provisioner-image ## Push storage-provisioner docker image using gcloud push-storage-provisioner-image: storage-provisioner-image ## Push storage-provisioner docker image using gcloud
gcloud docker -- push $(STORAGE_PROVISIONER_IMAGE) gcloud docker -- push $(STORAGE_PROVISIONER_IMAGE)
.PHONY: push-docker
push-docker: # Push docker image base on to IMAGE variable
(docker pull $(IMAGE) && (echo "Image already exist"; exit 1) || echo "Image doesn't exist in registry")
ifndef AUTOPUSH
$(call user_confirm, 'Are you sure you want to push $(IMAGE) ?')
endif
docker push $(IMAGE) || gcloud docker -- push $(IMAGE)
.PHONY: push-kic-base-image-gcr
push-kic-base-image-gcr: kic-base-image ## Push kic-base to gcr
$(MAKE) push-docker IMAGE=$(KIC_BASE_IMAGE_GCR)
.PHONY: push-kic-base-image-gh
push-kic-base-image-gh: kic-base-image ## Push kic-base to github
$(MAKE) push-docker IMAGE=$(KIC_BASE_IMAGE_GH)
.PHONY: push-kic-base-image-hub
push-kic-base-image-hub: kic-base-image ## Push kic-base to docker hub
$(MAKE) push-docker IMAGE=$(KIC_BASE_IMAGE_HUB)
.PHONY: push-kic-base-image
push-kic-base-image: ## Push kic-base to all registries
ifndef AUTOPUSH
$(call user_confirm, 'Are you sure you want to push: $(KIC_BASE_IMAGE_GH) & $(KIC_BASE_IMAGE_GCR) & $(KIC_BASE_IMAGE_HUB) ?')
$(MAKE) push-kic-base-image AUTOPUSH=true
else
$(MAKE) push-kic-base-image-gh push-kic-base-image-gcr push-kic-base-image-hub
endif
.PHONY: out/gvisor-addon .PHONY: out/gvisor-addon
out/gvisor-addon: pkg/minikube/assets/assets.go pkg/minikube/translate/translations.go ## Build gvisor addon out/gvisor-addon: pkg/minikube/assets/assets.go pkg/minikube/translate/translations.go ## Build gvisor addon
GOOS=linux CGO_ENABLED=0 go build -o $@ cmd/gvisor/gvisor.go GOOS=linux CGO_ENABLED=0 go build -o $@ cmd/gvisor/gvisor.go

View File

@ -135,6 +135,9 @@ func runStart(cmd *cobra.Command, args []string) {
} }
displayEnviron(os.Environ()) displayEnviron(os.Environ())
if viper.GetBool(force) {
out.WarningT("minikube skips various validations when --force is supplied; this may lead to unexpected behavior")
}
// if --registry-mirror specified when run minikube start, // if --registry-mirror specified when run minikube start,
// take arg precedence over MINIKUBE_REGISTRY_MIRROR // take arg precedence over MINIKUBE_REGISTRY_MIRROR
@ -662,7 +665,7 @@ func validateDriver(ds registry.DriverState, existing *config.ClusterConfig) {
} }
out.ErrLn("") out.ErrLn("")
if !st.Installed && !viper.GetBool(force) { if !st.Installed {
if existing != nil { if existing != nil {
if old := hostDriver(existing); name == old { if old := hostDriver(existing); name == old {
exit.WithCodeT(exit.Unavailable, "{{.driver}} does not appear to be installed, but is specified by an existing profile. Please run 'minikube delete' or install {{.driver}}", out.V{"driver": name}) exit.WithCodeT(exit.Unavailable, "{{.driver}} does not appear to be installed, but is specified by an existing profile. Please run 'minikube delete' or install {{.driver}}", out.V{"driver": name})
@ -670,10 +673,7 @@ func validateDriver(ds registry.DriverState, existing *config.ClusterConfig) {
} }
exit.WithCodeT(exit.Unavailable, "{{.driver}} does not appear to be installed", out.V{"driver": name}) exit.WithCodeT(exit.Unavailable, "{{.driver}} does not appear to be installed", out.V{"driver": name})
} }
exitIfNotForced(exit.Unavailable, "Failed to validate '{{.driver}}' driver", out.V{"driver": name})
if !viper.GetBool(force) {
exit.WithCodeT(exit.Unavailable, "Failed to validate '{{.driver}}' driver", out.V{"driver": name})
}
} }
} }
@ -866,11 +866,10 @@ func validateMemorySize(req int, drvName string) {
// a more sane alternative to their high memory 80% // a more sane alternative to their high memory 80%
minAdvised := 0.50 * float64(sysLimit) minAdvised := 0.50 * float64(sysLimit)
if req < minUsableMem && !viper.GetBool(force) { if req < minUsableMem {
exit.WithCodeT(exit.Config, "Requested memory allocation {{.requested}}MB is less than the usable minimum of {{.minimum_memory}}MB", exitIfNotForced(exit.Config, "Requested memory allocation {{.requested}}MB is less than the usable minimum of {{.minimum_memory}}MB", out.V{"requested": req, "minimum_memory": minUsableMem})
out.V{"requested": req, "minimum_memory": minUsableMem})
} }
if req < minRecommendedMem && !viper.GetBool(force) { if req < minRecommendedMem {
out.WarningT("Requested memory allocation ({{.requested}}MB) is less than the recommended minimum {{.recommended}}MB. Kubernetes may crash unexpectedly.", out.WarningT("Requested memory allocation ({{.requested}}MB) is less than the recommended minimum {{.recommended}}MB. Kubernetes may crash unexpectedly.",
out.V{"requested": req, "recommended": minRecommendedMem}) out.V{"requested": req, "recommended": minRecommendedMem})
} }
@ -883,24 +882,21 @@ func validateMemorySize(req int, drvName string) {
`, out.V{"container_limit": containerLimit, "system_limit": sysLimit}) `, out.V{"container_limit": containerLimit, "system_limit": sysLimit})
} }
if req > sysLimit && !viper.GetBool(force) { if req > sysLimit {
out.T(out.Tip, "To suppress memory validations you can use --force flag.") message := `Requested memory allocation {{.requested}}MB is more than your system limit {{.system_limit}}MB. Try specifying a lower memory:
exit.WithCodeT(exit.Config, `Requested memory allocation {{.requested}}MB is more than your system limit {{.system_limit}}MB. Try specifying a lower memory:
miniube start --memory={{.min_advised}}mb
`,
out.V{"requested": req, "system_limit": sysLimit, "max_advised": int32(maxAdvised), "min_advised": minAdvised})
miniube start --memory={{.min_advised}}mb
`
exitIfNotForced(exit.Config, message, out.V{"requested": req, "system_limit": sysLimit, "max_advised": int32(maxAdvised), "min_advised": minAdvised})
} }
if float64(req) > maxAdvised && !viper.GetBool(force) { if float64(req) > maxAdvised {
out.WarningT(`You are allocating {{.requested}}MB to memory and your system only has {{.system_limit}}MB. You might face issues. try specifying a lower memory: out.WarningT(`You are allocating {{.requested}}MB to memory and your system only has {{.system_limit}}MB. You might face issues. try specifying a lower memory:
miniube start --memory={{.min_advised}}mb miniube start --memory={{.min_advised}}mb
`, out.V{"requested": req, "system_limit": sysLimit, "min_advised": minAdvised}) `, out.V{"requested": req, "system_limit": sysLimit, "min_advised": minAdvised})
out.T(out.Tip, "To suppress and ignore this warning you can use --force flag.")
} }
} }
@ -919,8 +915,8 @@ func validateCPUCount(drvName string) {
} else { } else {
cpuCount = viper.GetInt(cpus) cpuCount = viper.GetInt(cpus)
} }
if cpuCount < minimumCPUS && !viper.GetBool(force) { if cpuCount < minimumCPUS {
exit.UsageT("Requested cpu count {{.requested_cpus}} is less than the minimum allowed of {{.minimum_cpus}}", out.V{"requested_cpus": cpuCount, "minimum_cpus": minimumCPUS}) exitIfNotForced(exit.BadUsage, "Requested cpu count {{.requested_cpus}} is less than the minimum allowed of {{.minimum_cpus}}", out.V{"requested_cpus": cpuCount, "minimum_cpus": minimumCPUS})
} }
if driver.IsKIC((drvName)) { if driver.IsKIC((drvName)) {
@ -942,22 +938,22 @@ func validateCPUCount(drvName string) {
`) `)
} }
out.T(out.Documentation, "https://docs.docker.com/config/containers/resource_constraints/") out.T(out.Documentation, "https://docs.docker.com/config/containers/resource_constraints/")
exit.UsageT("Ensure your {{.driver_name}} system has enough CPUs. The minimum allowed is 2 CPUs.", out.V{"driver_name": driver.FullName(viper.GetString("driver"))}) exitIfNotForced(exit.BadUsage, "Ensure your {{.driver_name}} system has enough CPUs. The minimum allowed is 2 CPUs.", out.V{"driver_name": driver.FullName(viper.GetString("driver"))})
} }
} }
} }
// validateFlags validates the supplied flags against known bad combinations // validateFlags validates the supplied flags against known bad combinations
func validateFlags(cmd *cobra.Command, drvName string) { func validateFlags(cmd *cobra.Command, drvName string) {
if cmd.Flags().Changed(humanReadableDiskSize) { if cmd.Flags().Changed(humanReadableDiskSize) {
diskSizeMB, err := util.CalculateSizeInMB(viper.GetString(humanReadableDiskSize)) diskSizeMB, err := util.CalculateSizeInMB(viper.GetString(humanReadableDiskSize))
if err != nil { if err != nil {
exit.WithCodeT(exit.Config, "Validation unable to parse disk size '{{.diskSize}}': {{.error}}", out.V{"diskSize": viper.GetString(humanReadableDiskSize), "error": err}) exitIfNotForced(exit.Config, "Validation unable to parse disk size '{{.diskSize}}': {{.error}}", out.V{"diskSize": viper.GetString(humanReadableDiskSize), "error": err})
} }
if diskSizeMB < minimumDiskSize && !viper.GetBool(force) { if diskSizeMB < minimumDiskSize {
exit.WithCodeT(exit.Config, "Requested disk size {{.requested_size}} is less than minimum of {{.minimum_size}}", out.V{"requested_size": diskSizeMB, "minimum_size": minimumDiskSize}) exitIfNotForced(exit.Config, "Requested disk size {{.requested_size}} is less than minimum of {{.minimum_size}}", out.V{"requested_size": diskSizeMB, "minimum_size": minimumDiskSize})
} }
} }
@ -975,7 +971,7 @@ func validateFlags(cmd *cobra.Command, drvName string) {
} }
req, err := util.CalculateSizeInMB(viper.GetString(memory)) req, err := util.CalculateSizeInMB(viper.GetString(memory))
if err != nil { if err != nil {
exit.WithCodeT(exit.Config, "Unable to parse memory '{{.memory}}': {{.error}}", out.V{"memory": viper.GetString(memory), "error": err}) exitIfNotForced(exit.Config, "Unable to parse memory '{{.memory}}': {{.error}}", out.V{"memory": viper.GetString(memory), "error": err})
} }
validateMemorySize(req, drvName) validateMemorySize(req, drvName)
} }
@ -1153,11 +1149,10 @@ func validateKubernetesVersion(old *config.ClusterConfig) {
if nvs.LT(oldestVersion) { if nvs.LT(oldestVersion) {
out.WarningT("Specified Kubernetes version {{.specified}} is less than the oldest supported version: {{.oldest}}", out.V{"specified": nvs, "oldest": constants.OldestKubernetesVersion}) out.WarningT("Specified Kubernetes version {{.specified}} is less than the oldest supported version: {{.oldest}}", out.V{"specified": nvs, "oldest": constants.OldestKubernetesVersion})
if viper.GetBool(force) { if !viper.GetBool(force) {
out.WarningT("Kubernetes {{.version}} is not supported by this release of minikube", out.V{"version": nvs}) out.WarningT("You can force an unsupported Kubernetes version via the --force flag")
} else {
exit.WithCodeT(exit.Data, "Sorry, Kubernetes {{.version}} is not supported by this release of minikube. To use this version anyway, use the `--force` flag.", out.V{"version": nvs})
} }
exitIfNotForced(exit.Data, "Kubernetes {{.version}} is not supported by this release of minikube", out.V{"version": nvs})
} }
if old == nil || old.KubernetesConfig.KubernetesVersion == "" { if old == nil || old.KubernetesConfig.KubernetesVersion == "" {
@ -1220,3 +1215,10 @@ func getKubernetesVersion(old *config.ClusterConfig) string {
return version.VersionPrefix + nvs.String() return version.VersionPrefix + nvs.String()
} }
func exitIfNotForced(code int, message string, v out.V) {
if !viper.GetBool(force) {
exit.WithCodeT(code, message, v)
}
out.WarningT(message, v)
}

View File

@ -28,7 +28,7 @@ RUN sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/lib
RUN sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/1.18:/1.18.3/xUbuntu_20.04/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list" && \ RUN sh -c "echo 'deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/1.18:/1.18.3/xUbuntu_20.04/ /' > /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list" && \
curl -LO https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/1.18:/1.18.3/xUbuntu_20.04/Release.key && \ curl -LO https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable:/cri-o:/1.18:/1.18.3/xUbuntu_20.04/Release.key && \
apt-key add - < Release.key && apt-get update && \ apt-key add - < Release.key && apt-get update && \
apt-get install -y --no-install-recommends cri-o=1.18.3~2 apt-get install -y --no-install-recommends cri-o=1.18.3~3
# install podman # install podman
RUN sh -c "echo 'deb https://dl.bintray.com/afbjorklund/podman focal main' > /etc/apt/sources.list.d/podman.list" && \ RUN sh -c "echo 'deb https://dl.bintray.com/afbjorklund/podman focal main' > /etc/apt/sources.list.d/podman.list" && \

View File

@ -1,4 +1,12 @@
[ [
{
"name": "v1.12.3",
"checksums": {
"darwin": "060275cd2129fbf3ce38f245a85651a6f050dff25a3ed0454241e1319ea1ab08",
"linux": "664ebef01166ef6c6b13ff1d70faecca9292e2fcdc85766e8ebd2c607488711c",
"windows": "994e7a232c57b6b917c20ad2e40e88a6b8e80e057212b14def3307e4b22ebbe6"
}
},
{ {
"name": "v1.12.2", "name": "v1.12.2",
"checksums": { "checksums": {

1
go.mod
View File

@ -72,6 +72,7 @@ require (
github.com/zchee/go-vmnet v0.0.0-20161021174912-97ebf9174097 github.com/zchee/go-vmnet v0.0.0-20161021174912-97ebf9174097
golang.org/x/build v0.0.0-20190927031335-2835ba2e683f golang.org/x/build v0.0.0-20190927031335-2835ba2e683f
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
golang.org/x/sys v0.0.0-20200523222454-059865788121 golang.org/x/sys v0.0.0-20200523222454-059865788121

View File

@ -227,30 +227,39 @@ type podmanSysInfo struct {
} `json:"store"` } `json:"store"`
} }
var dockerInfoGetter = func() (string, error) {
rr, err := runCmd(exec.Command(Docker, "system", "info", "--format", "{{json .}}"))
return rr.Stdout.String(), err
}
// dockerSystemInfo returns docker system info --format '{{json .}}' // dockerSystemInfo returns docker system info --format '{{json .}}'
func dockerSystemInfo() (dockerSysInfo, error) { func dockerSystemInfo() (dockerSysInfo, error) {
var ds dockerSysInfo var ds dockerSysInfo
rr, err := runCmd(exec.Command(Docker, "system", "info", "--format", "{{json .}}")) rawJSON, err := dockerInfoGetter()
if err != nil { if err != nil {
return ds, errors.Wrap(err, "get docker system info") return ds, errors.Wrap(err, "docker system info")
} }
if err := json.Unmarshal([]byte(strings.TrimSpace(rawJSON)), &ds); err != nil {
if err := json.Unmarshal([]byte(strings.TrimSpace(rr.Stdout.String())), &ds); err != nil {
return ds, errors.Wrapf(err, "unmarshal docker system info") return ds, errors.Wrapf(err, "unmarshal docker system info")
} }
return ds, nil return ds, nil
} }
var podmanInfoGetter = func() (string, error) {
rr, err := runCmd(exec.Command(Podman, "system", "info", "--format", "json"))
return rr.Stdout.String(), err
}
// podmanSysInfo returns podman system info --format '{{json .}}' // podmanSysInfo returns podman system info --format '{{json .}}'
func podmanSystemInfo() (podmanSysInfo, error) { func podmanSystemInfo() (podmanSysInfo, error) {
var ps podmanSysInfo var ps podmanSysInfo
rr, err := runCmd(exec.Command(Podman, "system", "info", "--format", "json")) rawJSON, err := podmanInfoGetter()
if err != nil { if err != nil {
return ps, errors.Wrap(err, "get podman system info") return ps, errors.Wrap(err, "podman system info")
} }
if err := json.Unmarshal([]byte(strings.TrimSpace(rr.Stdout.String())), &ps); err != nil { if err := json.Unmarshal([]byte(strings.TrimSpace(rawJSON)), &ps); err != nil {
return ps, errors.Wrapf(err, "unmarshal podman system info") return ps, errors.Wrapf(err, "unmarshal podman system info")
} }
return ps, nil return ps, nil

View File

@ -0,0 +1,157 @@
/*
Copyright 2019 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 oci
import (
"testing"
)
var daemonResponseMock string
var daemonInfoGetterMock = func() (string, error) {
return daemonResponseMock, nil
}
func TestDockerSystemInfo(t *testing.T) {
testCases := []struct {
Name string // test case bane
OciBin string // Docker or Podman
RawJSON string // raw response from json
ShouldError bool
CPUs int
Memory int64
OS string
}{
{
Name: "linux_docker",
OciBin: "docker",
RawJSON: `{"ID":"7PYP:53DU:MLWX:EDQG:YG2Y:UJLB:J7SD:4SAI:XF2Y:N2MR:MU53:DR3N","Containers":3,"ContainersRunning":1,"ContainersPaused":0,"ContainersStopped":2,"Images":76,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Native Overlay Diff","true"]],"SystemStatus":null,"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":false,"KernelMemory":true,"KernelMemoryTCP":false,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":false,"NFd":27,"OomKillDisable":true,"NGoroutines":48,"SystemTime":"2020-08-11T18:16:17.494440681Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.9.0-8-amd64","OperatingSystem":"Debian GNU/Linux 9 (stretch)","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":[],"AllowNondistributableArtifactsHostnames":[],"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":[],"Secure":true,"Official":true}},"Mirrors":[]},"NCPU":16,"MemTotal":63336071168,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"image-builder-cloud-shell-v20200811-102837","Labels":[],"ExperimentalBuild":false,"ServerVersion":"18.09.0","ClusterStore":"","ClusterAdvertise":"","Runtimes":{"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"7ad184331fa3e55e52b890ea95e65ba581ae3429","Expected":"7ad184331fa3e55e52b890ea95e65ba581ae3429"},"RuncCommit":{"ID":"dc9208a3303feef5b3839f4323d9beb36df0a9dd","Expected":"dc9208a3303feef5b3839f4323d9beb36df0a9dd"},"InitCommit":{"ID":"fec3683","Expected":"fec3683"},"SecurityOptions":["name=seccomp,profile=default"],"ProductLicense":"Community Engine","Warnings":["WARNING: No swap limit support"],"ClientInfo":{"Debug":false,"Plugins":[],"Warnings":null}}`,
ShouldError: false,
CPUs: 16,
Memory: 63336071168,
OS: "linux",
},
{
Name: "macos_docker",
OciBin: "docker",
RawJSON: `{"ID":"T54Z:I56K:XRG5:BTMK:BI72:IMI3:QBBF:H2PD:DGAF:EQLJ:7JFZ:PF54","Containers":5,"ContainersRunning":1,"ContainersPaused":0,"ContainersStopped":4,"Images":84,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Native Overlay Diff","true"]],"SystemStatus":null,"Plugins":{"Volume":["local"],"Network":["bridge","host","ipvlan","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":true,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":46,"OomKillDisable":true,"NGoroutines":56,"SystemTime":"2020-08-11T19:33:23.8936297Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":3,"KernelVersion":"4.19.76-linuxkit","OperatingSystem":"Docker Desktop","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":[],"AllowNondistributableArtifactsHostnames":[],"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":[],"Secure":true,"Official":true}},"Mirrors":[]},"NCPU":4,"MemTotal":3142250496,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"gateway.docker.internal:3128","HttpsProxy":"gateway.docker.internal:3129","NoProxy":"","Name":"docker-desktop","Labels":[],"ExperimentalBuild":false,"ServerVersion":"19.03.12","ClusterStore":"","ClusterAdvertise":"","Runtimes":{"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"7ad184331fa3e55e52b890ea95e65ba581ae3429","Expected":"7ad184331fa3e55e52b890ea95e65ba581ae3429"},"RuncCommit":{"ID":"dc9208a3303feef5b3839f4323d9beb36df0a9dd","Expected":"dc9208a3303feef5b3839f4323d9beb36df0a9dd"},"InitCommit":{"ID":"fec3683","Expected":"fec3683"},"SecurityOptions":["name=seccomp,profile=default"],"ProductLicense":"Community Engine","Warnings":null,"ClientInfo":{"Debug":false,"Plugins":[],"Warnings":null}}
`,
ShouldError: false,
CPUs: 4,
Memory: 3142250496,
OS: "linux",
},
{
Name: "windows_docker",
OciBin: "docker",
RawJSON: `{"ID":"CVVH:7ZIB:S5EO:L6VO:MGZ3:TRLS:JGIS:4ZI2:27Z7:MQAQ:YSLT:HEHB","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":3,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Native Overlay Diff","true"]],"SystemStatus":null,"Plugins":{"Volume":["local"],"Network":["bridge","host","ipvlan","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":true,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":35,"OomKillDisable":true,"NGoroutines":45,"SystemTime":"2020-08-11T19:39:26.083212722Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":1,"KernelVersion":"4.19.76-linuxkit","OperatingSystem":"Docker Desktop","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":[],"AllowNondistributableArtifactsHostnames":[],"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":[],"Secure":true,"Official":true}},"Mirrors":[]},"NCPU":4,"MemTotal":10454695936,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"docker-desktop","Labels":[],"ExperimentalBuild":false,"ServerVersion":"19.03.12","ClusterStore":"","ClusterAdvertise":"","Runtimes":{"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"7ad184331fa3e55e52b890ea95e65ba581ae3429","Expected":"7ad184331fa3e55e52b890ea95e65ba581ae3429"},"RuncCommit":{"ID":"dc9208a3303feef5b3839f4323d9beb36df0a9dd","Expected":"dc9208a3303feef5b3839f4323d9beb36df0a9dd"},"InitCommit":{"ID":"fec3683","Expected":"fec3683"},"SecurityOptions":["name=seccomp,profile=default"],"ProductLicense":"Community Engine","Warnings":null,"ClientInfo":{"Debug":false,"Plugins":[],"Warnings":null}}
`,
ShouldError: false,
CPUs: 4,
Memory: 10454695936,
OS: "linux",
}, {
Name: "podman_1.8_linux",
OciBin: "podman",
RawJSON: `{
"host": {
"BuildahVersion": "1.13.1",
"CgroupVersion": "v1",
"Conmon": {
"package": "conmon: /usr/libexec/podman/conmon",
"path": "/usr/libexec/podman/conmon",
"version": "conmon version 2.0.10, commit: unknown"
},
"Distribution": {
"distribution": "debian",
"version": "10"
},
"MemFree": 4907147264,
"MemTotal": 7839653888,
"OCIRuntime": {
"name": "runc",
"package": "runc: /usr/sbin/runc",
"path": "/usr/sbin/runc",
"version": "runc version 1.0.0~rc6+dfsg1\ncommit: 1.0.0~rc6+dfsg1-3 spec: 1.0.1"
},
"SwapFree": 0,
"SwapTotal": 0,
"arch": "amd64",
"cpus": 2,
"eventlogger": "journald",
"hostname": "podman-exp-temp",
"kernel": "4.19.0-8-cloud-amd64",
"os": "linux",
"rootless": false,
"uptime": "2690h 47m 23.31s (Approximately 112.08 days)"
},
"registries": {
"search": [
"docker.io",
"quay.io"
]
},
"store": {
"ConfigFile": "/etc/containers/storage.conf",
"ContainerStore": {
"number": 1
},
"GraphDriverName": "overlay",
"GraphOptions": {},
"GraphRoot": "/var/lib/containers/storage",
"GraphStatus": {
"Backing Filesystem": "extfs",
"Native Overlay Diff": "true",
"Supports d_type": "true",
"Using metacopy": "false"
},
"ImageStore": {
"number": 2
},
"RunRoot": "/var/run/containers/storage",
"VolumePath": "/var/lib/containers/storage/volumes"
}
}
`, CPUs: 2,
Memory: 7839653888,
OS: "linux"},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
daemonResponseMock = tc.RawJSON
// setting up mock funcs
dockerInfoGetter = daemonInfoGetterMock
podmanInfoGetter = daemonInfoGetterMock
s, err := DaemonInfo(tc.OciBin)
if err != nil && !tc.ShouldError {
t.Errorf("Expected not to have error but got %v", err)
}
if s.CPUs != tc.CPUs {
t.Errorf("Expected CPUs to be %d but got %d", tc.CPUs, s.CPUs)
}
if s.TotalMemory != tc.Memory {
t.Errorf("Expected Memory to be %d but got %d", tc.Memory, s.TotalMemory)
}
if s.OSType != tc.OS {
t.Errorf("Expected OS type to be %q but got %q", tc.OS, s.OSType)
}
})
}
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package driver package driver
import ( import (
"fmt"
"net" "net"
"k8s.io/minikube/pkg/drivers/kic/oci" "k8s.io/minikube/pkg/drivers/kic/oci"
@ -30,6 +31,9 @@ func ControlPlaneEndpoint(cc *config.ClusterConfig, cp *config.Node, driverName
port, err := oci.ForwardedPort(cc.Driver, cc.Name, cp.Port) port, err := oci.ForwardedPort(cc.Driver, cc.Name, cp.Port)
hostname := oci.DefaultBindIPV4 hostname := oci.DefaultBindIPV4
ip := net.ParseIP(hostname) ip := net.ParseIP(hostname)
if ip == nil {
return hostname, ip, port, fmt.Errorf("failed to parse ip for %q", hostname)
}
// https://github.com/kubernetes/minikube/issues/3878 // https://github.com/kubernetes/minikube/issues/3878
if cc.KubernetesConfig.APIServerName != constants.APIServerName { if cc.KubernetesConfig.APIServerName != constants.APIServerName {
@ -43,5 +47,9 @@ func ControlPlaneEndpoint(cc *config.ClusterConfig, cp *config.Node, driverName
if cc.KubernetesConfig.APIServerName != constants.APIServerName { if cc.KubernetesConfig.APIServerName != constants.APIServerName {
hostname = cc.KubernetesConfig.APIServerName hostname = cc.KubernetesConfig.APIServerName
} }
return hostname, net.ParseIP(cp.IP), cp.Port, nil ip := net.ParseIP(cp.IP)
if ip == nil {
return hostname, ip, cp.Port, fmt.Errorf("failed to parse ip for %q", cp.IP)
}
return hostname, ip, cp.Port, nil
} }

View File

@ -44,6 +44,9 @@ func cmdOut(args ...string) (string, error) {
err := cmd.Run() err := cmd.Run()
glog.Infof("[stdout =====>] : %s", stdout.String()) glog.Infof("[stdout =====>] : %s", stdout.String())
glog.Infof("[stderr =====>] : %s", stderr.String()) glog.Infof("[stderr =====>] : %s", stderr.String())
if err != nil {
glog.Infof("[err =====>] : %v", err)
}
return stdout.String(), err return stdout.String(), err
} }

View File

@ -76,11 +76,11 @@ After job completion, click "Console Output" to verify that the release complete
**Note: If you are releasing a beta, you are done when you get here.** **Note: If you are releasing a beta, you are done when you get here.**
## Check releases.json ## Merge the releases.json change
This file is used for auto-update notifications, but is not active until releases.json is copied to GCS. The release script updates https://storage.googleapis.com/minikube/releases.json - which is used by the minikube binary to check for updates, and is live immediately.
minikube-bot will send out a PR to update the release checksums at the top of `deploy/minikube/releases.json`. You should merge this PR. minikube-bot will also send out a PR to merge this into the tree. Please merge this PR to keep GCS and Github in sync.
## Package managers which include minikube ## Package managers which include minikube

View File

@ -4,7 +4,7 @@ This is an experimental driver. Please use it only for experimental reasons unti
## Usage ## Usage
It's recommended to run minikube with the podman driver and [CRI-O container runtime](https://https://cri-o.io/): It's recommended to run minikube with the podman driver and [CRI-O container runtime](https://cri-o.io/):
```shell ```shell
minikube start --driver=podman --container-runtime=cri-o minikube start --driver=podman --container-runtime=cri-o

View File

@ -165,7 +165,10 @@ func TestDownloadOnlyKic(t *testing.T) {
args := []string{"start", "--download-only", "-p", profile, "--force", "--alsologtostderr"} args := []string{"start", "--download-only", "-p", profile, "--force", "--alsologtostderr"}
args = append(args, StartArgs()...) args = append(args, StartArgs()...)
if _, err := Run(t, exec.CommandContext(ctx, Target(), args...)); err != nil { cmd := exec.CommandContext(ctx, Target(), args...)
// make sure this works even if docker daemon isn't running
cmd.Env = append(os.Environ(), "DOCKER_HOST=/does/not/exist")
if _, err := Run(t, cmd); err != nil {
t.Errorf("start with download only failed %q : %v", args, err) t.Errorf("start with download only failed %q : %v", args, err)
} }