Merge branch 'kubernetes:master' into Hyperkit-StaticWarning

pull/21676/head
Divy Singhvi 2025-10-01 19:19:23 +05:30 committed by GitHub
commit 6d7f04ebfd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 8493 additions and 170 deletions

View File

@ -13,7 +13,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
- name: Install bom
uses: kubernetes-sigs/release-actions/setup-bom@a30d93cf2aa029e1e4c8a6c79f766aebf429fddb # main
uses: kubernetes-sigs/release-actions/setup-bom@8af7b2a5596dff526de9db59b2c4b8457e9f52a1 # main
- name: Generage SBOM
run: |
bom generate -o minikube_${{github.ref_name}}_sbom.spdx \

File diff suppressed because it is too large Load Diff

View File

@ -70,7 +70,7 @@ gsutil.cmd -m cp -r gs://minikube-builds/$env:MINIKUBE_LOCATION/installers/check
# Download gopogh and gotestsum
go install github.com/medyagh/gopogh/cmd/gopogh@v0.29.0
go install gotest.tools/gotestsum@v1.12.3
go install gotest.tools/gotestsum@v1.13.0
# temporary: remove the old install of gopogh & gotestsum as it's taking priority over our current install, preventing updating
if (Test-Path "C:\Go") {
Remove-Item "C:\Go" -Recurse -Force

View File

@ -16,7 +16,7 @@
set -eux -o pipefail
GH_VERSION="2.78.0"
GH_VERSION="2.80.0"
echo "Installing latest version of gh"
curl -qLO "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz"

View File

@ -18,7 +18,7 @@ set -eux -o pipefail
function install_gotestsum() {
rm -f $(which gotestsum)
GOBIN="$GOROOT/bin" go install gotest.tools/gotestsum@v1.12.3
GOBIN="$GOROOT/bin" go install gotest.tools/gotestsum@v1.13.0
}
which gotestsum || install_gotestsum

View File

@ -72,6 +72,14 @@ type interfaceInfo struct {
// The returned error.Kind can be used to provide a suggestion for resolving the
// issue.
func ValidateHelper() error {
// Ideally minikube will not try to validate in download-only mode, but this
// is called from different places in different drivers, so the easier way
// to skip validation is to skip it here.
if viper.GetBool("download-only") {
log.Debug("Skipping vmnet-helper validation in download-only mode")
return nil
}
// Is it installed?
if _, err := os.Stat(executablePath); err != nil {
if errors.Is(err, os.ErrNotExist) {

View File

@ -292,7 +292,7 @@ var Addons = map[string]*Addon{
"0640"),
}, false, "kong", "3rd party (Kong HQ)", "@gAmUssA", "https://minikube.sigs.k8s.io/docs/handbook/addons/kong-ingress/", map[string]string{
"Kong": "kong:3.9.1@sha256:14c689c0caf1b8da1403a742016ec64d2f5b5b12ecdec2989f36b2c2c4aa1ac0",
"KongIngress": "kong/kubernetes-ingress-controller:3.5.1@sha256:4dc74b6fd7bcea07196900b53b6e1d5fabf6b7e56a7ed5d786e0d1c0ab950d1d",
"KongIngress": "kong/kubernetes-ingress-controller:3.5.2@sha256:472012b0f7dca838f2b9cd8fcf6e6daeb2572631b37adb0dbb0770defeb89f04",
}, map[string]string{
"Kong": "docker.io",
"KongIngress": "docker.io",
@ -580,9 +580,9 @@ var Addons = map[string]*Addon{
"volcano-deployment.yaml",
"0640"),
}, false, "volcano", "third-party (volcano)", "hwdef", "", map[string]string{
"vc_webhook_manager": "volcanosh/vc-webhook-manager:v1.12.2@sha256:b7c3bd73e2d9240cf17662451d50e0d73654342235a66cdfb2ec221f1628ae35",
"vc_controller_manager": "volcanosh/vc-controller-manager:v1.12.2@sha256:286112e70bdbf88174a66895bb3c64dd9026b5a762025b61bcd8f6cac04e1b90",
"vc_scheduler": "volcanosh/vc-scheduler:v1.12.2@sha256:6e28f0f79d4cd09c1c34afaba41623c1b4d0fd7087cc99d6449a8a62e073b50e",
"vc_webhook_manager": "volcanosh/vc-webhook-manager:v1.13.0@sha256:03e36eb220766397b4cd9c2f42bab8666661a0112fa9033ae9bd80d2a9611001",
"vc_controller_manager": "volcanosh/vc-controller-manager:v1.13.0@sha256:8dd7ce0cef2df19afb14ba26bec90e2999a3c0397ebe5c9d75a5f68d1c80d242",
"vc_scheduler": "volcanosh/vc-scheduler:v1.13.0@sha256:b05b30b3c25eff5af77e1859f47fc6acfc3520d62dc2838f0623aa4309c40b34",
}, map[string]string{
"vc_webhook_manager": "docker.io",
"vc_controller_manager": "docker.io",
@ -751,7 +751,7 @@ var Addons = map[string]*Addon{
MustBinAsset(addons.NvidiaDevicePlugin, "nvidia-device-plugin/nvidia-device-plugin.yaml.tmpl", vmpath.GuestAddonsDir, "nvidia-device-plugin.yaml", "0640"),
}, false, "nvidia-device-plugin", "3rd party (NVIDIA)", "", "https://minikube.sigs.k8s.io/docs/tutorials/nvidia/",
map[string]string{
"NvidiaDevicePlugin": "nvidia/k8s-device-plugin:v0.17.3@sha256:630596340f8e83aa10b0bc13a46db76772e31b7dccfc34d3a4e41ab7e0aa6117",
"NvidiaDevicePlugin": "nvidia/k8s-device-plugin:v0.17.4@sha256:3c54348fe5a57e5700e7d8068e7531d2ef2d5f3ccb70c8f6bac0953432527abd",
}, map[string]string{
"NvidiaDevicePlugin": "nvcr.io",
}),

View File

@ -866,7 +866,7 @@ spec:
type: Unconfined
containers:
- name: cilium-agent
image: "quay.io/cilium/cilium:v1.18.1@sha256:65ab17c052d8758b2ad157ce766285e04173722df59bdee1ea6d5fda7149f0e9"
image: "quay.io/cilium/cilium:v1.18.2@sha256:858f807ea4e20e85e3ea3240a762e1f4b29f1cb5bbd0463b8aa77e7b097c0667"
imagePullPolicy: IfNotPresent
command:
- cilium-agent
@ -1030,7 +1030,7 @@ spec:
initContainers:
- name: config
image: "quay.io/cilium/cilium:v1.18.1@sha256:65ab17c052d8758b2ad157ce766285e04173722df59bdee1ea6d5fda7149f0e9"
image: "quay.io/cilium/cilium:v1.18.2@sha256:858f807ea4e20e85e3ea3240a762e1f4b29f1cb5bbd0463b8aa77e7b097c0667"
imagePullPolicy: IfNotPresent
command:
- cilium-dbg
@ -1053,7 +1053,7 @@ spec:
# Required to mount cgroup2 filesystem on the underlying Kubernetes node.
# We use nsenter command with host's cgroup and mount namespaces enabled.
- name: mount-cgroup
image: "quay.io/cilium/cilium:v1.18.1@sha256:65ab17c052d8758b2ad157ce766285e04173722df59bdee1ea6d5fda7149f0e9"
image: "quay.io/cilium/cilium:v1.18.2@sha256:858f807ea4e20e85e3ea3240a762e1f4b29f1cb5bbd0463b8aa77e7b097c0667"
imagePullPolicy: IfNotPresent
env:
- name: CGROUP_ROOT
@ -1090,7 +1090,7 @@ spec:
drop:
- ALL
- name: apply-sysctl-overwrites
image: "quay.io/cilium/cilium:v1.18.1@sha256:65ab17c052d8758b2ad157ce766285e04173722df59bdee1ea6d5fda7149f0e9"
image: "quay.io/cilium/cilium:v1.18.2@sha256:858f807ea4e20e85e3ea3240a762e1f4b29f1cb5bbd0463b8aa77e7b097c0667"
imagePullPolicy: IfNotPresent
env:
- name: BIN_PATH
@ -1128,7 +1128,7 @@ spec:
# from a privileged container because the mount propagation bidirectional
# only works from privileged containers.
- name: mount-bpf-fs
image: "quay.io/cilium/cilium:v1.18.1@sha256:65ab17c052d8758b2ad157ce766285e04173722df59bdee1ea6d5fda7149f0e9"
image: "quay.io/cilium/cilium:v1.18.2@sha256:858f807ea4e20e85e3ea3240a762e1f4b29f1cb5bbd0463b8aa77e7b097c0667"
imagePullPolicy: IfNotPresent
args:
- 'mount | grep "/sys/fs/bpf type bpf" || mount -t bpf bpf /sys/fs/bpf'
@ -1144,7 +1144,7 @@ spec:
mountPath: /sys/fs/bpf
mountPropagation: Bidirectional
- name: clean-cilium-state
image: "quay.io/cilium/cilium:v1.18.1@sha256:65ab17c052d8758b2ad157ce766285e04173722df59bdee1ea6d5fda7149f0e9"
image: "quay.io/cilium/cilium:v1.18.2@sha256:858f807ea4e20e85e3ea3240a762e1f4b29f1cb5bbd0463b8aa77e7b097c0667"
imagePullPolicy: IfNotPresent
command:
- /init-container.sh
@ -1191,7 +1191,7 @@ spec:
mountPath: /var/run/cilium # wait-for-kube-proxy
# Install the CNI binaries in an InitContainer so we don't have a writable host mount in the agent
- name: install-cni-binaries
image: "quay.io/cilium/cilium:v1.18.1@sha256:65ab17c052d8758b2ad157ce766285e04173722df59bdee1ea6d5fda7149f0e9"
image: "quay.io/cilium/cilium:v1.18.2@sha256:858f807ea4e20e85e3ea3240a762e1f4b29f1cb5bbd0463b8aa77e7b097c0667"
imagePullPolicy: IfNotPresent
command:
- "/install-plugin.sh"
@ -1216,6 +1216,7 @@ spec:
automountServiceAccountToken: true
terminationGracePeriodSeconds: 1
hostNetwork: true
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
@ -1374,7 +1375,7 @@ spec:
type: Unconfined
containers:
- name: cilium-envoy
image: "quay.io/cilium/cilium-envoy:v1.34.4-1754895458-68cffdfa568b6b226d70a7ef81fc65dda3b890bf@sha256:247e908700012f7ef56f75908f8c965215c26a27762f296068645eb55450bda2"
image: "quay.io/cilium/cilium-envoy:v1.34.7-1757592137-1a52bb680a956879722f48c591a2ca90f7791324@sha256:7932d656b63f6f866b6732099d33355184322123cfe1182e6f05175a3bc2e0e0"
imagePullPolicy: IfNotPresent
command:
- /usr/bin/cilium-envoy-starter
@ -1552,7 +1553,7 @@ spec:
type: RuntimeDefault
containers:
- name: cilium-operator
image: "quay.io/cilium/operator-generic:v1.18.1@sha256:97f4553afa443465bdfbc1cc4927c93f16ac5d78e4dd2706736e7395382201bc"
image: "quay.io/cilium/operator-generic:v1.18.2@sha256:cb4e4ffc5789fd5ff6a534e3b1460623df61cba00f5ea1c7b40153b5efb81805"
imagePullPolicy: IfNotPresent
command:
- cilium-operator-generic
@ -1633,6 +1634,8 @@ spec:
operator: Exists
- key: node.kubernetes.io/not-ready
operator: Exists
- key: node.cloudprovider.kubernetes.io/uninitialized
operator: Exists
- key: node.cilium.io/agent-not-ready
operator: Exists

View File

@ -34,10 +34,10 @@ var (
const (
// DefaultKubernetesVersion is the default Kubernetes version
DefaultKubernetesVersion = "v1.34.0"
DefaultKubernetesVersion = "v1.34.1"
// NewestKubernetesVersion is the newest Kubernetes version to test against
// NOTE: You may need to update coreDNS & etcd versions in pkg/minikube/bootstrapper/images/images.go
NewestKubernetesVersion = "v1.34.0"
NewestKubernetesVersion = "v1.34.1"
// OldestKubernetesVersion is the oldest Kubernetes version to test against
// TODO: upodate to 6 releases before from DefaultKubernetesVersion
OldestKubernetesVersion = "v1.28.0"

View File

@ -18,6 +18,26 @@ package constants
var (
KubeadmImages = map[string]map[string]string{
"v1.34.1": {
"coredns/coredns": "v1.12.1",
"etcd": "3.6.4-0",
"pause": "3.10.1",
},
"v1.33.5": {
"coredns/coredns": "v1.12.0",
"etcd": "3.5.21-0",
"pause": "3.10",
},
"v1.32.9": {
"coredns/coredns": "v1.11.3",
"etcd": "3.5.16-0",
"pause": "3.10",
},
"v1.31.13": {
"coredns/coredns": "v1.11.3",
"etcd": "3.5.15-0",
"pause": "3.10",
},
"v1.34.0": {
"coredns/coredns": "v1.12.1",
"etcd": "3.6.4-0",

View File

@ -21,6 +21,7 @@ package constants
// ValidKubernetesVersions is a list of Kubernetes versions in order from newest to oldest
// This is used when outputting Kubernetes versions and to select the latest patch version when unspecified
var ValidKubernetesVersions = []string{
"v1.34.1",
"v1.34.0",
"v1.34.0-rc.2",
"v1.34.0-rc.1",
@ -29,6 +30,7 @@ var ValidKubernetesVersions = []string{
"v1.34.0-alpha.3",
"v1.34.0-alpha.2",
"v1.34.0-alpha.1",
"v1.33.5",
"v1.33.4",
"v1.33.3",
"v1.33.2",
@ -40,6 +42,7 @@ var ValidKubernetesVersions = []string{
"v1.33.0-alpha.3",
"v1.33.0-alpha.2",
"v1.33.0-alpha.1",
"v1.32.9",
"v1.32.8",
"v1.32.7",
"v1.32.6",
@ -56,6 +59,7 @@ var ValidKubernetesVersions = []string{
"v1.32.0-alpha.3",
"v1.32.0-alpha.2",
"v1.32.0-alpha.1",
"v1.31.13",
"v1.31.12",
"v1.31.11",
"v1.31.10",

View File

@ -25,6 +25,7 @@ import (
"time"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/localpath"
)
// Force download tests to run in serial.
@ -33,7 +34,6 @@ func TestDownload(t *testing.T) {
t.Run("PreloadDownloadPreventsMultipleDownload", testPreloadDownloadPreventsMultipleDownload)
t.Run("ImageToCache", testImageToCache)
t.Run("PreloadNotExists", testPreloadNotExists)
t.Run("PreloadChecksumMismatch", testPreloadChecksumMismatch)
t.Run("PreloadExistsCaching", testPreloadExistsCaching)
t.Run("PreloadWithCachedSizeZero", testPreloadWithCachedSizeZero)
}
@ -48,7 +48,31 @@ func mockSleepDownload(downloadsCounter *int) func(src, dst string) error {
}
}
// point each subtest at an isolated MINIKUBE_HOME, pre-create the preload cache directory,
//
// and automatically restore the global download/preload mocks after each run.
// Applied the helper across all download-related tests
func setupTestMiniHome(t *testing.T) {
t.Helper()
tmpHome := t.TempDir()
t.Setenv(localpath.MinikubeHome, tmpHome)
if err := os.MkdirAll(targetDir(), 0o755); err != nil {
t.Fatalf("failed to create preload cache dir: %v", err)
}
origDownloadMock := DownloadMock
origCheckCache := checkCache
origCheckPreloadExists := checkPreloadExists
origGetChecksumGCS := getChecksumGCS
t.Cleanup(func() {
DownloadMock = origDownloadMock
checkCache = origCheckCache
checkPreloadExists = origCheckPreloadExists
getChecksumGCS = origGetChecksumGCS
})
}
func testBinaryDownloadPreventsMultipleDownload(t *testing.T) {
setupTestMiniHome(t)
downloadNum := 0
DownloadMock = mockSleepDownload(&downloadNum)
@ -79,6 +103,8 @@ func testBinaryDownloadPreventsMultipleDownload(t *testing.T) {
}
func testPreloadDownloadPreventsMultipleDownload(t *testing.T) {
setupTestMiniHome(t)
downloadNum := 0
DownloadMock = mockSleepDownload(&downloadNum)
f, err := os.CreateTemp("", "preload")
@ -97,8 +123,7 @@ func testPreloadDownloadPreventsMultipleDownload(t *testing.T) {
return os.Stat(f.Name())
}
checkPreloadExists = func(_, _, _ string, _ ...bool) bool { return true }
getChecksum = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
ensureChecksumValid = func(_, _, _ string, _ []byte) error { return nil }
getChecksumGCS = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
var group sync.WaitGroup
group.Add(2)
@ -120,13 +145,13 @@ func testPreloadDownloadPreventsMultipleDownload(t *testing.T) {
}
func testPreloadNotExists(t *testing.T) {
setupTestMiniHome(t)
downloadNum := 0
DownloadMock = mockSleepDownload(&downloadNum)
checkCache = func(_ string) (fs.FileInfo, error) { return nil, fmt.Errorf("cache not found") }
checkPreloadExists = func(_, _, _ string, _ ...bool) bool { return false }
getChecksum = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
ensureChecksumValid = func(_, _, _ string, _ []byte) error { return nil }
getChecksumGCS = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
err := Preload(constants.DefaultKubernetesVersion, constants.Docker, "docker")
if err != nil {
@ -138,27 +163,8 @@ func testPreloadNotExists(t *testing.T) {
}
}
func testPreloadChecksumMismatch(t *testing.T) {
downloadNum := 0
DownloadMock = mockSleepDownload(&downloadNum)
checkCache = func(_ string) (fs.FileInfo, error) { return nil, fmt.Errorf("cache not found") }
checkPreloadExists = func(_, _, _ string, _ ...bool) bool { return true }
getChecksum = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
ensureChecksumValid = func(_, _, _ string, _ []byte) error {
return fmt.Errorf("checksum mismatch")
}
err := Preload(constants.DefaultKubernetesVersion, constants.Docker, "docker")
expectedErrMsg := "checksum mismatch"
if err == nil {
t.Errorf("Expected error when checksum mismatches")
} else if err.Error() != expectedErrMsg {
t.Errorf("Expected error to be %s, got %s", expectedErrMsg, err.Error())
}
}
func testImageToCache(t *testing.T) {
setupTestMiniHome(t)
downloadNum := 0
DownloadMock = mockSleepDownload(&downloadNum)
@ -184,44 +190,72 @@ func testImageToCache(t *testing.T) {
}
// Validates that preload existence checks correctly caches values retrieved by remote checks.
// testPreloadExistsCaching verifies the caching semantics of PreloadExists when
// the local cache is absent and remote existence checks are required.
// In summary, this test enforces that:
// - PreloadExists performs remote checks only on cache misses.
// - Negative and positive results are cached per (k8sVersion, containerVersion, runtime) key.
// - GitHub is only consulted when GCS reports the preload as not existing.
// - Global state is correctly restored after the test.
func testPreloadExistsCaching(t *testing.T) {
setupTestMiniHome(t)
checkCache = func(_ string) (fs.FileInfo, error) {
return nil, fmt.Errorf("cache not found")
}
doesPreloadExist := false
checkCalled := false
checkRemotePreloadExists = func(_, _ string) bool {
checkCalled = true
gcsCheckCalls := 0
ghCheckCalls := 0
savedGCSCheck := checkRemotePreloadExistsGCS
savedGHCheck := checkRemotePreloadExistsGitHub
preloadStates = make(map[string]map[string]preloadState)
checkRemotePreloadExistsGCS = func(_, _ string) bool {
gcsCheckCalls++
return doesPreloadExist
}
existence := PreloadExists("v1", "c1", "docker", true)
if existence || !checkCalled {
t.Errorf("Expected preload not to exist and a check to be performed. Existence: %v, Check: %v", existence, checkCalled)
checkRemotePreloadExistsGitHub = func(_, _ string) bool {
ghCheckCalls++
return false
}
checkCalled = false
t.Cleanup(func() {
checkRemotePreloadExistsGCS = savedGCSCheck
checkRemotePreloadExistsGitHub = savedGHCheck
preloadStates = make(map[string]map[string]preloadState)
})
existence := PreloadExists("v1", "c1", "docker", true)
if existence || gcsCheckCalls != 1 || ghCheckCalls != 1 {
t.Errorf("Expected preload not to exist and checks to be performed. Existence: %v, GCS Calls: %d, GH Calls: %d", existence, gcsCheckCalls, ghCheckCalls)
}
gcsCheckCalls = 0
ghCheckCalls = 0
existence = PreloadExists("v1", "c1", "docker", true)
if existence || checkCalled {
t.Errorf("Expected preload not to exist and no check to be performed. Existence: %v, Check: %v", existence, checkCalled)
if existence || gcsCheckCalls != 0 || ghCheckCalls != 0 {
t.Errorf("Expected preload not to exist and no checks to be performed. Existence: %v, GCS Calls: %d, GH Calls: %d", existence, gcsCheckCalls, ghCheckCalls)
}
doesPreloadExist = true
checkCalled = false
gcsCheckCalls = 0
ghCheckCalls = 0
existence = PreloadExists("v2", "c1", "docker", true)
if !existence || !checkCalled {
t.Errorf("Expected preload to exist and a check to be performed. Existence: %v, Check: %v", existence, checkCalled)
if !existence || gcsCheckCalls != 1 || ghCheckCalls != 0 {
t.Errorf("Expected preload to exist via GCS. Existence: %v, GCS Calls: %d, GH Calls: %d", existence, gcsCheckCalls, ghCheckCalls)
}
checkCalled = false
gcsCheckCalls = 0
ghCheckCalls = 0
existence = PreloadExists("v2", "c2", "docker", true)
if !existence || !checkCalled {
t.Errorf("Expected preload to exist and a check to be performed. Existence: %v, Check: %v", existence, checkCalled)
if !existence || gcsCheckCalls != 1 || ghCheckCalls != 0 {
t.Errorf("Expected preload to exist via GCS for new runtime. Existence: %v, GCS Calls: %d, GH Calls: %d", existence, gcsCheckCalls, ghCheckCalls)
}
checkCalled = false
gcsCheckCalls = 0
ghCheckCalls = 0
existence = PreloadExists("v2", "c2", "docker", true)
if !existence || checkCalled {
t.Errorf("Expected preload to exist and no check to be performed. Existence: %v, Check: %v", existence, checkCalled)
if !existence || gcsCheckCalls != 0 || ghCheckCalls != 0 {
t.Errorf("Expected preload to exist and no checks to be performed. Existence: %v, GCS Calls: %d, GH Calls: %d", existence, gcsCheckCalls, ghCheckCalls)
}
}
func testPreloadWithCachedSizeZero(t *testing.T) {
setupTestMiniHome(t)
downloadNum := 0
DownloadMock = mockSleepDownload(&downloadNum)
f, err := os.CreateTemp("", "preload")
@ -231,8 +265,7 @@ func testPreloadWithCachedSizeZero(t *testing.T) {
checkCache = func(_ string) (fs.FileInfo, error) { return os.Stat(f.Name()) }
checkPreloadExists = func(_, _, _ string, _ ...bool) bool { return true }
getChecksum = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
ensureChecksumValid = func(_, _, _ string, _ []byte) error { return nil }
getChecksumGCS = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
if err := Preload(constants.DefaultKubernetesVersion, constants.Docker, "docker"); err != nil {
t.Errorf("Expected no error with cached preload of size zero")

View File

@ -0,0 +1,68 @@
/*
Copyright 2025 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 gh provides helper utilities for interacting with the GitHub API
package gh
import (
"context"
"fmt"
"net/http"
"os"
"strings"
"github.com/google/go-github/v74/github"
"golang.org/x/oauth2"
)
// ReleaseAssets retrieves a GitHub release by tag from org/project.
// Try to not call this too often. preferably cache and re-use. to avoid rate limits.
func ReleaseAssets(org, project, tag string) ([]*github.ReleaseAsset, error) {
ctx := context.Background()
// Use an authenticated client when GITHUB_TOKEN is set to avoid low rate limits.
httpClient := oauthClient(ctx, os.Getenv("GITHUB_TOKEN"))
ghc := github.NewClient(httpClient)
rel, _, err := ghc.Repositories.GetReleaseByTag(ctx, org, project, tag)
return rel.Assets, err
}
// AssetSHA256 returns the SHA-256 digest for the asset with the given name
// from the provided release assets from github API.
// to avoid rate limits. encouraged to call pass results of ReleaseAssets here.
func AssetSHA256(assetName string, assets []*github.ReleaseAsset) ([]byte, error) {
for _, asset := range assets {
if asset.GetName() != assetName {
continue
}
d := asset.GetDigest() // e.g. "sha256:fdcb..."
if d == "" {
return []byte(""), fmt.Errorf("asset %q has no digest; id=%d url=%s", assetName, asset.GetID(), asset.GetBrowserDownloadURL())
}
const prefix = "sha256:"
d = strings.TrimPrefix(d, prefix)
return []byte(d), nil
}
return []byte(""), fmt.Errorf("asset %q not found", assetName)
}
func oauthClient(ctx context.Context, token string) *http.Client {
if token == "" {
return nil // unauthenticated client (lower rate limit)
}
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
return oauth2.NewClient(ctx, ts)
}

View File

@ -0,0 +1,98 @@
/*
Copyright 2025 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 gh
import (
"strings"
"testing"
"github.com/google/go-github/v74/github"
)
func TestAssetSHA256(t *testing.T) {
t.Run("found_with_sha256_prefix", func(t *testing.T) {
assets := []*github.ReleaseAsset{
{
Name: github.Ptr("minikube-linux-amd64"),
Digest: github.Ptr("sha256:abcdef123456"),
ID: github.Ptr(int64(101)),
BrowserDownloadURL: github.Ptr("http://example/minikube-linux-amd64"),
},
}
got, err := AssetSHA256("minikube-linux-amd64", assets)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(got) != "abcdef123456" {
t.Fatalf("expected digest %q, got %q", "abcdef123456", string(got))
}
})
t.Run("found_without_prefix", func(t *testing.T) {
assets := []*github.ReleaseAsset{
{
Name: github.Ptr("minikube-darwin-arm64"),
Digest: github.Ptr("1234abcd"),
ID: github.Ptr(int64(102)),
BrowserDownloadURL: github.Ptr("http://example/minikube-darwin-arm64"),
},
}
got, err := AssetSHA256("minikube-darwin-arm64", assets)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if string(got) != "1234abcd" {
t.Fatalf("expected digest %q, got %q", "1234abcd", string(got))
}
})
t.Run("asset_missing_digest", func(t *testing.T) {
assets := []*github.ReleaseAsset{
{
Name: github.Ptr("minikube-windows-amd64.exe"),
Digest: github.Ptr(""),
ID: github.Ptr(int64(103)),
BrowserDownloadURL: github.Ptr("http://example/minikube-windows-amd64.exe"),
},
}
_, err := AssetSHA256("minikube-windows-amd64.exe", assets)
if err == nil {
t.Fatalf("expected error, got nil")
}
if !strings.Contains(err.Error(), "has no digest") {
t.Fatalf("unexpected error message: %v", err)
}
})
t.Run("asset_not_found", func(t *testing.T) {
assets := []*github.ReleaseAsset{
{
Name: github.Ptr("unrelated"),
Digest: github.Ptr("sha256:deadbeef"),
ID: github.Ptr(int64(104)),
BrowserDownloadURL: github.Ptr("http://example/unrelated"),
},
}
_, err := AssetSHA256("missing", assets)
if err == nil {
t.Fatalf("expected error, got nil")
}
if !strings.Contains(err.Error(), `asset "missing" not found`) {
t.Fatalf("unexpected error message: %v", err)
}
})
}

View File

@ -18,7 +18,6 @@ package download
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"net/http"
@ -34,6 +33,7 @@ import (
"github.com/pkg/errors"
"github.com/spf13/viper"
"k8s.io/klog/v2"
"k8s.io/minikube/pkg/minikube/download/gh"
"k8s.io/minikube/pkg/minikube/driver"
"k8s.io/minikube/pkg/minikube/localpath"
"k8s.io/minikube/pkg/minikube/out"
@ -46,11 +46,27 @@ const (
// NOTE: You may need to bump this version up when upgrading auxiliary docker images
PreloadVersion = "v18"
// PreloadBucket is the name of the GCS bucket where preloaded volume tarballs exist
PreloadBucket = "minikube-preloaded-volume-tarballs"
PreloadBucket = "minikube-preloaded-volume-tarballs"
PreloadGitHubOrg = "kubernetes-sigs"
PreloadGitHubRepo = "minikube-preloads"
)
type preloadSource string
const (
preloadSourceNone preloadSource = ""
preloadSourceLocal preloadSource = "local"
preloadSourceGCS preloadSource = "gcs"
preloadSourceGitHub preloadSource = "github"
)
type preloadState struct {
exists bool
source preloadSource
}
var (
preloadStates = make(map[string]map[string]bool)
preloadStates = make(map[string]map[string]preloadState)
)
// TarballName returns name of the tarball
@ -67,42 +83,56 @@ func TarballName(k8sVersion, containerRuntime string) string {
return fmt.Sprintf("preloaded-images-k8s-%s-%s-%s-%s-%s.tar.lz4", PreloadVersion, k8sVersion, containerRuntime, storageDriver, runtime.GOARCH)
}
// returns the name of the checksum file
func checksumName(k8sVersion, containerRuntime string) string {
return fmt.Sprintf("%s.checksum", TarballName(k8sVersion, containerRuntime))
}
// returns target dir for all cached items related to preloading
func targetDir() string {
return localpath.MakeMiniPath("cache", "preloaded-tarball")
}
// PreloadChecksumPath returns the local path to the cached checksum file
func PreloadChecksumPath(k8sVersion, containerRuntime string) string {
return filepath.Join(targetDir(), checksumName(k8sVersion, containerRuntime))
}
// TarballPath returns the local path to the cached preload tarball
func TarballPath(k8sVersion, containerRuntime string) string {
return filepath.Join(targetDir(), TarballName(k8sVersion, containerRuntime))
}
// remoteTarballURL returns the URL for the remote tarball in GCS
func remoteTarballURL(k8sVersion, containerRuntime string) string {
// remoteTarballURLGCS returns the URL for the remote tarball in GCS
func remoteTarballURLGCS(k8sVersion, containerRuntime string) string {
return fmt.Sprintf("https://%s/%s/%s/%s/%s", downloadHost, PreloadBucket, PreloadVersion, k8sVersion, TarballName(k8sVersion, containerRuntime))
}
func setPreloadState(k8sVersion, containerRuntime string, value bool) {
cRuntimes, ok := preloadStates[k8sVersion]
if !ok {
cRuntimes = make(map[string]bool)
preloadStates[k8sVersion] = cRuntimes
}
cRuntimes[containerRuntime] = value
// remoteTarballURLGitHub returns the URL for the remote tarball hosted on GitHub releases
func remoteTarballURLGitHub(k8sVersion, containerRuntime string) string {
return fmt.Sprintf("https://github.com/%s/%s/releases/download/%s/%s", PreloadGitHubOrg, PreloadGitHubRepo, PreloadVersion, TarballName(k8sVersion, containerRuntime))
}
var checkRemotePreloadExists = func(k8sVersion, containerRuntime string) bool {
url := remoteTarballURL(k8sVersion, containerRuntime)
func remoteTarballURL(k8sVersion, containerRuntime string, source preloadSource) string {
switch source {
case preloadSourceGitHub:
return remoteTarballURLGitHub(k8sVersion, containerRuntime)
case preloadSourceGCS:
return remoteTarballURLGCS(k8sVersion, containerRuntime)
default:
return string(preloadSourceNone)
}
}
func setPreloadState(k8sVersion, containerRuntime string, state preloadState) {
cRuntimes, ok := preloadStates[k8sVersion]
if !ok {
cRuntimes = make(map[string]preloadState)
preloadStates[k8sVersion] = cRuntimes
}
cRuntimes[containerRuntime] = state
}
func getPreloadState(k8sVersion, containerRuntime string) (preloadState, bool) {
if cRuntimes, ok := preloadStates[k8sVersion]; ok {
if state, ok := cRuntimes[containerRuntime]; ok {
return state, true
}
}
return preloadState{}, false
}
func remotePreloadExists(url string) bool {
resp, err := http.Head(url)
if err != nil {
klog.Warningf("%s fetch error: %v", url, err)
@ -119,6 +149,28 @@ var checkRemotePreloadExists = func(k8sVersion, containerRuntime string) bool {
return true
}
// this is a function variable so it can be overridden in tests
var checkRemotePreloadExistsGCS = func(k8sVersion, containerRuntime string) bool {
url := remoteTarballURLGCS(k8sVersion, containerRuntime)
return remotePreloadExists(url)
}
// this is a function variable so it can be overridden in tests
var checkRemotePreloadExistsGitHub = func(k8sVersion, containerRuntime string) bool {
url := remoteTarballURLGitHub(k8sVersion, containerRuntime)
return remotePreloadExists(url)
}
// PreloadExistsGCS returns true if there is a preloaded tarball in GCS that can be used
func PreloadExistsGCS(k8sVersion, containerRuntime string) bool {
return checkRemotePreloadExistsGCS(k8sVersion, containerRuntime)
}
// PreloadExistsGH returns true if there is a preloaded tarball in GitHub releases that can be used
func PreloadExistsGH(k8sVersion, containerRuntime string) bool {
return checkRemotePreloadExistsGitHub(k8sVersion, containerRuntime)
}
// PreloadExists returns true if there is a preloaded tarball that can be used
func PreloadExists(k8sVersion, containerRuntime, driverName string, forcePreload ...bool) bool {
// TODO (#8166): Get rid of the need for this and viper at all
@ -136,21 +188,30 @@ func PreloadExists(k8sVersion, containerRuntime, driverName string, forcePreload
}
// If the preload existence is cached, just return that value.
if preloadState, ok := preloadStates[k8sVersion][containerRuntime]; ok {
return preloadState
if state, ok := getPreloadState(k8sVersion, containerRuntime); ok {
return state.exists
}
// Omit remote check if tarball exists locally
targetPath := TarballPath(k8sVersion, containerRuntime)
if f, err := checkCache(targetPath); err == nil && f.Size() != 0 {
klog.Infof("Found local preload: %s", targetPath)
setPreloadState(k8sVersion, containerRuntime, true)
setPreloadState(k8sVersion, containerRuntime, preloadState{exists: true, source: preloadSourceLocal})
return true
}
existence := checkRemotePreloadExists(k8sVersion, containerRuntime)
setPreloadState(k8sVersion, containerRuntime, existence)
return existence
if PreloadExistsGCS(k8sVersion, containerRuntime) {
setPreloadState(k8sVersion, containerRuntime, preloadState{exists: true, source: preloadSourceGCS})
return true
}
if PreloadExistsGH(k8sVersion, containerRuntime) {
setPreloadState(k8sVersion, containerRuntime, preloadState{exists: true, source: preloadSourceGitHub})
return true
}
setPreloadState(k8sVersion, containerRuntime, preloadState{exists: false, source: preloadSourceNone})
return false
}
var checkPreloadExists = PreloadExists
@ -180,31 +241,34 @@ func Preload(k8sVersion, containerRuntime, driverName string) error {
}
out.Step(style.FileDownload, "Downloading Kubernetes {{.version}} preload ...", out.V{"version": k8sVersion})
url := remoteTarballURL(k8sVersion, containerRuntime)
state, ok := getPreloadState(k8sVersion, containerRuntime)
source := preloadSourceNone
if ok && state.source != preloadSourceNone {
source = state.source
}
url := remoteTarballURL(k8sVersion, containerRuntime, source)
var checksum []byte
var chksErr error
checksum, chksErr = getChecksum(source, k8sVersion, containerRuntime)
checksum, err := getChecksum(k8sVersion, containerRuntime)
var realPath string
if err != nil {
klog.Warningf("No checksum for preloaded tarball for k8s version %s: %v", k8sVersion, err)
if chksErr != nil {
klog.Warningf("No checksum for preloaded tarball for k8s version %s: %v", k8sVersion, chksErr)
realPath = targetPath
tmp, err := os.CreateTemp(targetDir(), TarballName(k8sVersion, containerRuntime)+".*")
if err != nil {
return errors.Wrap(err, "tempfile")
}
targetPath = tmp.Name()
} else if checksum != nil {
// add URL parameter for go-getter to automatically verify the checksum
url += fmt.Sprintf("?checksum=md5:%s", hex.EncodeToString(checksum))
} else if checksum != nil { // add URL parameter for go-getter to automatically verify the checksum
url = addChecksumToURL(url, source, checksum)
}
if err := download(url, targetPath); err != nil {
return errors.Wrapf(err, "download failed: %s", url)
}
if err := ensureChecksumValid(k8sVersion, containerRuntime, targetPath, checksum); err != nil {
return err
}
// to avoid partial/corrupt files in final dest. only rename tmp if download didn't error out.
if realPath != "" {
klog.Infof("renaming tempfile to %s ...", TarballName(k8sVersion, containerRuntime))
err := os.Rename(targetPath, realPath)
@ -214,10 +278,23 @@ func Preload(k8sVersion, containerRuntime, driverName string) error {
}
// If the download was successful, mark off that the preload exists in the cache.
setPreloadState(k8sVersion, containerRuntime, true)
setPreloadState(k8sVersion, containerRuntime, preloadState{exists: true, source: source})
return nil
}
// addChecksumToURL appends the checksum query parameter to the URL for go-getter (so it can verify before/after download)
func addChecksumToURL(url string, ps preloadSource, checksum []byte) string {
switch ps {
case preloadSourceGCS: // GCS API gives us MD5 checksums only
url += fmt.Sprintf("?checksum=md5:%s", hex.EncodeToString(checksum))
klog.Infof("Got checksum from GCS API %q", hex.EncodeToString(checksum))
case preloadSourceGitHub: // GCS API gives us sha256
url += fmt.Sprintf("?checksum=sha256:%s", checksum)
klog.Infof("Got checksum from Github API %q", checksum)
}
return url
}
func getStorageAttrs(name string) (*storage.ObjectAttrs, error) {
ctx := context.Background()
client, err := storage.NewClient(ctx, option.WithoutAuthentication())
@ -231,9 +308,9 @@ func getStorageAttrs(name string) (*storage.ObjectAttrs, error) {
return attrs, nil
}
// getChecksum returns the MD5 checksum of the preload tarball
var getChecksum = func(k8sVersion, containerRuntime string) ([]byte, error) {
klog.Infof("getting checksum for %s ...", TarballName(k8sVersion, containerRuntime))
// getChecksumGCS returns the MD5 checksum of the preload tarball
var getChecksumGCS = func(k8sVersion, containerRuntime string) ([]byte, error) {
klog.Infof("getting checksum for %s from gcs api...", TarballName(k8sVersion, containerRuntime))
filename := fmt.Sprintf("%s/%s/%s", PreloadVersion, k8sVersion, TarballName(k8sVersion, containerRuntime))
attrs, err := getStorageAttrs(filename)
if err != nil {
@ -242,46 +319,25 @@ var getChecksum = func(k8sVersion, containerRuntime string) ([]byte, error) {
return attrs.MD5, nil
}
// saveChecksumFile saves the checksum to a local file for later verification
func saveChecksumFile(k8sVersion, containerRuntime string, checksum []byte) error {
klog.Infof("saving checksum for %s ...", TarballName(k8sVersion, containerRuntime))
return os.WriteFile(PreloadChecksumPath(k8sVersion, containerRuntime), checksum, 0o644)
// getChecksumGithub returns the SHA256 checksum of the preload tarball
var getChecksumGithub = func(k8sVersion, containerRuntime string) ([]byte, error) {
klog.Infof("getting checksum for %s from github api...", TarballName(k8sVersion, containerRuntime))
assets, err := gh.ReleaseAssets(PreloadGitHubRepo, PreloadGitHubRepo, PreloadVersion)
if err != nil { // could not find release or rate limited
return nil, err
}
return gh.AssetSHA256(TarballName(k8sVersion, containerRuntime), assets)
}
// verifyChecksum returns true if the checksum of the local binary matches
// the checksum of the remote binary
func verifyChecksum(k8sVersion, containerRuntime, binaryPath string) error {
klog.Infof("verifying checksum of %s ...", binaryPath)
// get md5 checksum of tarball path
contents, err := os.ReadFile(binaryPath)
if err != nil {
return errors.Wrap(err, "reading tarball")
func getChecksum(ps preloadSource, k8sVersion, containerRuntime string) ([]byte, error) {
switch ps {
case preloadSourceGCS:
return getChecksumGCS(k8sVersion, containerRuntime)
case preloadSourceGitHub:
return getChecksumGithub(k8sVersion, containerRuntime)
default:
return nil, fmt.Errorf("unknown preload source: %s", ps)
}
checksum := md5.Sum(contents)
remoteChecksum, err := os.ReadFile(PreloadChecksumPath(k8sVersion, containerRuntime))
if err != nil {
return errors.Wrap(err, "reading checksum file")
}
// create a slice of checksum, which is [16]byte
if string(remoteChecksum) != string(checksum[:]) {
return fmt.Errorf("checksum of %s does not match remote checksum (%s != %s)", binaryPath, string(remoteChecksum), string(checksum[:]))
}
return nil
}
// ensureChecksumValid saves and verifies local binary checksum matches remote binary checksum
var ensureChecksumValid = func(k8sVersion, containerRuntime, targetPath string, checksum []byte) error {
if err := saveChecksumFile(k8sVersion, containerRuntime, checksum); err != nil {
return errors.Wrap(err, "saving checksum file")
}
if err := verifyChecksum(k8sVersion, containerRuntime, targetPath); err != nil {
return errors.Wrap(err, "verify")
}
return nil
}
// CleanUpOlderPreloads deletes preload files belonging to older minikube versions

View File

@ -74,7 +74,7 @@ minikube start [flags]
--interactive Allow user prompts for more information (default true)
--iso-url strings Locations to fetch the minikube ISO from. The list depends on the machine architecture.
--keep-context This will keep the existing kubectl context and will create a minikube context.
--kubernetes-version string The Kubernetes version that the minikube VM will use (ex: v1.2.3, 'stable' for v1.34.0, 'latest' for v1.34.0). Defaults to 'stable'.
--kubernetes-version string The Kubernetes version that the minikube VM will use (ex: v1.2.3, 'stable' for v1.34.1, 'latest' for v1.34.1). Defaults to 'stable'.
--kvm-gpu Enable experimental NVIDIA GPU support in minikube
--kvm-hidden Hide the hypervisor signature from the guest in minikube (kvm2 driver only)
--kvm-network string The KVM default network name. (kvm2 driver only) (default "default")

View File

@ -22,7 +22,6 @@ import (
"bufio"
"bytes"
"context"
"crypto/md5"
"crypto/sha256"
"encoding/json"
"fmt"
@ -235,23 +234,19 @@ func TestDownloadOnlyKic(t *testing.T) {
// Make sure the downloaded image tarball exists
tarball := download.TarballPath(constants.DefaultKubernetesVersion, cRuntime)
contents, err := os.ReadFile(tarball)
fi, err := os.Stat(tarball)
if err != nil {
t.Errorf("failed to read tarball file %q: %v", tarball, err)
t.Errorf("expected tarball file %q to exist, but got error: %v", tarball, err)
} else {
const minSize int64 = 200 * 1024 * 1024 // 200 MB
if fi.Size() <= minSize {
t.Errorf("expected tarball file %q to be larger than 200MB (>%d bytes), got %d bytes", tarball, minSize, fi.Size())
}
}
if arm64Platform() {
t.Skip("Skip for arm64 platform. See https://github.com/kubernetes/minikube/issues/10144")
}
// Make sure it has the correct checksum
checksum := md5.Sum(contents)
remoteChecksum, err := os.ReadFile(download.PreloadChecksumPath(constants.DefaultKubernetesVersion, cRuntime))
if err != nil {
t.Errorf("failed to read checksum file %q : %v", download.PreloadChecksumPath(constants.DefaultKubernetesVersion, cRuntime), err)
}
if string(remoteChecksum) != string(checksum[:]) {
t.Errorf("failed to verify checksum. checksum of %q does not match remote checksum (%q != %q)", tarball, string(remoteChecksum), string(checksum[:]))
}
}
// createSha256File is a helper function which creates sha256 checksum file from given file