Skip to content

Commit 1bcb23e

Browse files
Merge pull request #18766 from hakman/automated-cherry-pick-of-#18764-#18765-upstream-release-1.37
Automated cherry pick of #18764: assets: cache file hashes instead of nodeup assets #18765: nodeup: make nodeup.xz the canonical release artifact
2 parents 389b1fb + 95c0a5c commit 1bcb23e

644 files changed

Lines changed: 4391 additions & 5075 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,8 +613,7 @@ version-dist-nodeup: version-dist-nodeup-amd64 version-dist-nodeup-arm64
613613

614614
version-dist-nodeup-amd64 version-dist-nodeup-arm64: version-dist-nodeup-%: nodeup-%
615615
mkdir -p ${UPLOAD}/kops/${VERSION}/linux/$*/
616-
cp -fp ${DIST}/linux/$*/nodeup ${UPLOAD}/kops/${VERSION}/linux/$*/nodeup
617-
tools/sha256 ${UPLOAD}/kops/${VERSION}/linux/$*/nodeup ${UPLOAD}/kops/${VERSION}/linux/$*/nodeup.sha256
616+
rm -f ${UPLOAD}/kops/${VERSION}/linux/$*/nodeup ${UPLOAD}/kops/${VERSION}/linux/$*/nodeup.sha256
618617
tools/xz.sh ${DIST}/linux/$*/nodeup ${UPLOAD}/kops/${VERSION}/linux/$*/nodeup.xz
619618
tools/sha256 ${UPLOAD}/kops/${VERSION}/linux/$*/nodeup.xz ${UPLOAD}/kops/${VERSION}/linux/$*/nodeup.xz.sha256
620619

cmd/kops/integration_test.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,15 @@ import (
4545
"k8s.io/kops/upup/pkg/fi/cloudup"
4646
)
4747

48-
// updateClusterTestBase is added automatically to the srcDir on all
49-
// tests using runTest, including runTestTerraformAWS, runTestTerraformGCE
50-
const updateClusterTestBase = "../../tests/integration/update_cluster/"
48+
const (
49+
// updateClusterTestBase is added automatically to the srcDir on all
50+
// tests using runTest, including runTestTerraformAWS, runTestTerraformGCE.
51+
updateClusterTestBase = "../../tests/integration/update_cluster/"
52+
53+
// integrationTestKopsVersion must remain pinned to a published release whose nodeup artifacts
54+
// are permanently available. Every integration runner that renders release assets must use it.
55+
integrationTestKopsVersion = "1.37.0-beta.1"
56+
)
5157

5258
type integrationTest struct {
5359
clusterName string
@@ -923,7 +929,7 @@ func (i *integrationTest) runTestTerraformAWS(t *testing.T) {
923929
h := testutils.NewIntegrationTestHarness(t)
924930
defer h.Close()
925931

926-
h.MockKopsVersion("1.34.0-beta.1")
932+
h.MockKopsVersion(integrationTestKopsVersion)
927933
h.SetupMockAWS()
928934

929935
i.runTest(t, ctx, h, "", "", nil)
@@ -936,7 +942,7 @@ func (i *integrationTest) runTestPhase(t *testing.T, phase cloudup.Phase) {
936942
h := testutils.NewIntegrationTestHarness(t)
937943
defer h.Close()
938944

939-
h.MockKopsVersion("1.34.0-beta.1")
945+
h.MockKopsVersion(integrationTestKopsVersion)
940946
h.SetupMockAWS()
941947
phaseName := string(phase)
942948
if phaseName == "" {
@@ -954,7 +960,7 @@ func (i *integrationTest) runTestTerraformGCE(t *testing.T) {
954960
h := testutils.NewIntegrationTestHarness(t)
955961
defer h.Close()
956962

957-
h.MockKopsVersion("1.34.0-beta.1")
963+
h.MockKopsVersion(integrationTestKopsVersion)
958964
h.SetupMockGCE()
959965

960966
i.runTest(t, ctx, h, "", "", nil)
@@ -970,7 +976,7 @@ func (i *integrationTest) runTestTerraformAzure(t *testing.T) {
970976
h := testutils.NewIntegrationTestHarness(t)
971977
defer h.Close()
972978

973-
h.MockKopsVersion("1.34.0-beta.1")
979+
h.MockKopsVersion(integrationTestKopsVersion)
974980
h.SetupMockAzure(i.clusterName)
975981

976982
var stdout bytes.Buffer
@@ -1047,7 +1053,7 @@ func (i *integrationTest) runTestTerraformHetzner(t *testing.T) {
10471053
h := testutils.NewIntegrationTestHarness(t)
10481054
defer h.Close()
10491055

1050-
h.MockKopsVersion("1.34.0-beta.1")
1056+
h.MockKopsVersion(integrationTestKopsVersion)
10511057

10521058
i.runTest(t, ctx, h, "", "", nil)
10531059
}
@@ -1065,7 +1071,7 @@ func (i *integrationTest) runTestTerraformScaleway(t *testing.T) {
10651071
h := testutils.NewIntegrationTestHarness(t)
10661072
defer h.Close()
10671073

1068-
h.MockKopsVersion("1.34.0-beta.1")
1074+
h.MockKopsVersion(integrationTestKopsVersion)
10691075

10701076
i.runTest(t, ctx, h, "", "", nil)
10711077
}

cmd/kops/lifecycle_integration_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,7 @@ func runLifecycleTestAWS(o *LifecycleTestOptions) {
376376
h := testutils.NewIntegrationTestHarness(o.t)
377377
defer h.Close()
378378

379-
h.MockKopsVersion("1.34.0-beta.1")
379+
h.MockKopsVersion(integrationTestKopsVersion)
380380
cloud := h.SetupMockAWS()
381381

382382
var beforeIds []string
@@ -408,7 +408,7 @@ func runLifecycleTestOpenstack(o *LifecycleTestOptions) {
408408
h := testutils.NewIntegrationTestHarness(o.t)
409409
defer h.Close()
410410

411-
h.MockKopsVersion("1.34.0-beta.1")
411+
h.MockKopsVersion(integrationTestKopsVersion)
412412
cloud := testutils.SetupMockOpenstack()
413413

414414
var beforeIds []string
@@ -461,7 +461,7 @@ func runLifecycleTestGCE(o *LifecycleTestOptions) {
461461
h := testutils.NewIntegrationTestHarness(o.t)
462462
defer h.Close()
463463

464-
h.MockKopsVersion("1.34.0-beta.1")
464+
h.MockKopsVersion(integrationTestKopsVersion)
465465

466466
cloud := h.SetupMockGCE()
467467

hack/dev-build.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ KOPS_VERSION=$(kops version --short)
104104
KOPS_BASE_URL="http://${NODEUP_BUCKET}.s3.amazonaws.com/kops/${KOPS_VERSION}/"
105105

106106
echo "KOPS_BASE_URL=${KOPS_BASE_URL}"
107-
echo "NODEUP_URL=${KOPS_BASE_URL}linux/amd64/nodeup"
108107

109108
echo ==========
110109
echo "Deleting cluster ${CLUSTER_NAME}. Elle est finie."
@@ -118,7 +117,7 @@ kops delete cluster \
118117
echo ==========
119118
echo "Creating cluster ${CLUSTER_NAME}"
120119

121-
kops_command="NODEUP_URL=${KOPS_BASE_URL}linux/amd64/nodeup KOPS_BASE_URL=${KOPS_BASE_URL} kops create cluster --name $CLUSTER_NAME --state $KOPS_STATE_STORE --node-count $NODE_COUNT --zones $NODE_ZONES --master-zones $MASTER_ZONES --node-size $NODE_SIZE --master-size $MASTER_SIZE -v $VERBOSITY --image $IMAGE --channel alpha --topology $TOPOLOGY --networking $NETWORKING"
120+
kops_command="KOPS_BASE_URL=${KOPS_BASE_URL} kops create cluster --name $CLUSTER_NAME --state $KOPS_STATE_STORE --node-count $NODE_COUNT --zones $NODE_ZONES --master-zones $MASTER_ZONES --node-size $NODE_SIZE --master-size $MASTER_SIZE -v $VERBOSITY --image $IMAGE --channel alpha --topology $TOPOLOGY --networking $NETWORKING"
122121

123122
if [[ $TOPOLOGY == "private" ]]; then
124123
kops_command+=" --bastion='true'"

hack/release-assets.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,24 @@ declare -A BINARIES=(
3030
["linux/amd64/kops"]="kops-linux-amd64"
3131
["linux/arm64/kops"]="kops-linux-arm64"
3232
["windows/amd64/kops.exe"]="kops-windows-amd64"
33-
["linux/amd64/nodeup"]="nodeup-linux-amd64"
34-
["linux/arm64/nodeup"]="nodeup-linux-arm64"
3533
)
3634

3735
# Binaries that only some kOps releases ship: source-path -> github-name.
38-
# channels was dropped from releases in kOps 1.36 and protokube in kOps 1.37,
39-
# while nodeup.xz was added in kOps 1.37. The scripts still promote and validate older versions.
36+
# channels was dropped from releases in kOps 1.36, protokube in kOps 1.37, and
37+
# uncompressed nodeup after kOps 1.37.0-beta.1. nodeup.xz was added in that release.
4038
declare -A OPTIONAL_BINARIES=(
4139
["linux/amd64/channels"]="channels-linux-amd64"
4240
["linux/arm64/channels"]="channels-linux-arm64"
4341
["linux/amd64/protokube"]="protokube-linux-amd64"
4442
["linux/arm64/protokube"]="protokube-linux-arm64"
43+
["linux/amd64/nodeup"]="nodeup-linux-amd64"
44+
["linux/arm64/nodeup"]="nodeup-linux-arm64"
4545
["linux/amd64/nodeup.xz"]="nodeup-linux-amd64.xz"
4646
["linux/arm64/nodeup.xz"]="nodeup-linux-arm64.xz"
4747
)
4848

4949
# add_optional_binaries <version>: append the optional binaries that exist upstream for
50-
# the given version to BINARIES, so releases that ship them keep full coverage.
50+
# the given version to BINARIES, so releases keep full coverage without version-specific logic.
5151
# Only a confirmed 404 counts as absent; any other probe outcome fails the script,
5252
# so a transient error cannot silently drop an artifact from promotion or validation.
5353
add_optional_binaries() {

hack/set-version

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,20 @@ KOPS_CI_VERSION=`grep 'KOPS_CI_VERSION\s*=' kops-version.go | awk '{print $3}'
5656
echo "KOPS_RELEASE_VERSION ${KOPS_RELEASE_VERSION} -> ${NEW_RELEASE_VERSION}"
5757
echo "KOPS_CI_VERSION ${KOPS_CI_VERSION} -> ${NEW_CI_VERSION}"
5858

59-
sed -i.bak -e "s@${KOPS_RELEASE_VERSION}@${NEW_RELEASE_VERSION}@g" pkg/nodemodel/wellknownassets/kopsassets_test.go
60-
61-
git grep -l registry.k8s.io/kops/dns-controller | xargs -I {} sed -i.bak -e "s@dns-controller:${KOPS_RELEASE_VERSION}@dns-controller:${NEW_RELEASE_VERSION}@g" {}
59+
# Only rewrite goldens that intentionally track the current release. Integration fixtures pin
60+
# published kOps versions so their external assets remain available and reproducible.
61+
RELEASE_GOLDEN_PATHS=(
62+
pkg/model/components
63+
upup/pkg/fi/cloudup/tests/bootstrapchannelbuilder
64+
)
65+
66+
git grep -l "registry.k8s.io/kops/dns-controller:${KOPS_RELEASE_VERSION}" -- "${RELEASE_GOLDEN_PATHS[@]}" | xargs -I {} sed -i.bak -e "s@dns-controller:${KOPS_RELEASE_VERSION}@dns-controller:${NEW_RELEASE_VERSION}@g" {}
6267
git grep -l "version..v${KOPS_RELEASE_VERSION}" upup/models/cloudup/resources/addons/dns-controller.addons.k8s.io/ | xargs -I {} sed -i.bak -e "s@version: v${KOPS_RELEASE_VERSION}@version: v${NEW_RELEASE_VERSION}@g" {}
6368

64-
git grep -l registry.k8s.io/kops/kops-controller | xargs -I {} sed -i.bak -e "s@kops-controller:${KOPS_RELEASE_VERSION}@kops-controller:${NEW_RELEASE_VERSION}@g" {}
69+
git grep -l "registry.k8s.io/kops/kops-controller:${KOPS_RELEASE_VERSION}" -- "${RELEASE_GOLDEN_PATHS[@]}" | xargs -I {} sed -i.bak -e "s@kops-controller:${KOPS_RELEASE_VERSION}@kops-controller:${NEW_RELEASE_VERSION}@g" {}
6570
git grep -l "version..v${KOPS_RELEASE_VERSION}" upup/models/cloudup/resources/addons/kops-controller.addons.k8s.io/ | xargs -I {} sed -i.bak -e "s@version: v${KOPS_RELEASE_VERSION}@version: v${NEW_RELEASE_VERSION}@g" {}
6671

67-
git grep -l registry.k8s.io/kops/kube-apiserver-healthcheck | xargs -I {} sed -i.bak -e "s@kube-apiserver-healthcheck:${KOPS_RELEASE_VERSION}@kube-apiserver-healthcheck:${NEW_RELEASE_VERSION}@g" {}
72+
git grep -l "registry.k8s.io/kops/kube-apiserver-healthcheck:${KOPS_RELEASE_VERSION}" -- "${RELEASE_GOLDEN_PATHS[@]}" | xargs -I {} sed -i.bak -e "s@kube-apiserver-healthcheck:${KOPS_RELEASE_VERSION}@kube-apiserver-healthcheck:${NEW_RELEASE_VERSION}@g" {}
6873

6974
git grep -l "version..${KOPS_RELEASE_VERSION}" upup/pkg/fi/cloudup/tests/bootstrapchannelbuilder/ | xargs -I {} sed -i.bak -e "s@version: ${KOPS_RELEASE_VERSION}@version: ${NEW_RELEASE_VERSION}@g" {}
7075

pkg/assets/builder.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ import (
3838
"k8s.io/kops/util/pkg/vfs"
3939
)
4040

41+
// downloadedFileHashes caches hashes read from checksum files, keyed by resolved URL so
42+
// canonical and mirrored assets do not share entries. Commands can create multiple
43+
// AssetBuilders, but each builder must still remap and register its own assets.
44+
var downloadedFileHashes sync.Map // resolved URL -> *hashing.Hash
45+
4146
// ImageDigestResolver looks up the manifest digest for an image, returning it in the form
4247
// "sha256:...".
4348
type ImageDigestResolver func(image string) (string, error)
@@ -308,11 +313,6 @@ func (a *AssetBuilder) RemapImage(image string) string {
308313
return image + "@" + digest
309314
}
310315

311-
// HasFileRepository reports whether a file repository is configured for asset mirroring.
312-
func (a *AssetBuilder) HasFileRepository() bool {
313-
return a.assetsLocation != nil && a.assetsLocation.FileRepository != nil
314-
}
315-
316316
// RemapFile returns a remapped URL for the file, if AssetsLocation is defined.
317317
// It is returns in a FileAsset, alongside the SHA hash of the file.
318318
// The SHA hash is is knownHash is provided, and otherwise will be found first by
@@ -386,6 +386,11 @@ func (a *AssetBuilder) findHash(file *FileAsset) (*hashing.Hash, error) {
386386
return knownHash, nil
387387
}
388388

389+
if cachedHash, found := downloadedFileHashes.Load(u.String()); found {
390+
klog.V(8).Infof("using cached hash for %q", u)
391+
return cachedHash.(*hashing.Hash), nil
392+
}
393+
389394
klog.V(2).Infof("asset %q is not well-known, downloading hash", file.CanonicalURL)
390395

391396
// We now prefer sha256 hashes
@@ -418,7 +423,14 @@ func (a *AssetBuilder) findHash(file *FileAsset) (*hashing.Hash, error) {
418423
klog.Infof("Hash file was empty %q", hashURL)
419424
continue
420425
}
421-
return hashing.FromString(fields[0])
426+
hash, err := hashing.FromString(fields[0])
427+
if err != nil {
428+
return nil, err
429+
}
430+
431+
downloadedFileHashes.Store(u.String(), hash)
432+
433+
return hash, nil
422434
}
423435
if ext == ".sha256" {
424436
klog.V(2).Infof("Unable to read new sha256 hash file (is this an older/unsupported kubernetes release?)")

pkg/assets/builder_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,22 @@ package assets
1818

1919
import (
2020
"fmt"
21+
"net/http"
22+
"net/http/httptest"
2123
"net/url"
2224
"os"
25+
"path"
2326
"path/filepath"
2427
"strings"
2528
"sync"
29+
"sync/atomic"
2630
"testing"
2731

2832
"k8s.io/kops/pkg/apis/kops"
2933
"k8s.io/kops/pkg/featureflag"
3034
"k8s.io/kops/pkg/testutils/golden"
3135
"k8s.io/kops/util/pkg/hashing"
36+
"k8s.io/kops/util/pkg/vfs"
3237
)
3338

3439
func buildAssetBuilder(t *testing.T) *AssetBuilder {
@@ -339,3 +344,131 @@ func TestAssetBuilderConcurrentCollection(t *testing.T) {
339344
}
340345
}
341346
}
347+
348+
func resetDownloadedFileHashes(t *testing.T) {
349+
t.Helper()
350+
351+
downloadedFileHashes.Clear()
352+
t.Cleanup(downloadedFileHashes.Clear)
353+
}
354+
355+
func hashHandler(assetPath string, hash string, requests *atomic.Int64) http.HandlerFunc {
356+
return func(w http.ResponseWriter, r *http.Request) {
357+
if r.URL.Path != assetPath+".sha256" {
358+
// The VFS retries 404 and 5xx responses.
359+
http.Error(w, "not found", http.StatusForbidden)
360+
return
361+
}
362+
requests.Add(1)
363+
fmt.Fprintf(w, "%s %s\n", hash, path.Base(assetPath))
364+
}
365+
}
366+
367+
func newHashServer(t *testing.T, assetPath string, hash string, requests *atomic.Int64) *httptest.Server {
368+
t.Helper()
369+
370+
server := httptest.NewServer(hashHandler(assetPath, hash, requests))
371+
t.Cleanup(server.Close)
372+
373+
return server
374+
}
375+
376+
func TestFindHashCachesDownloadedHashesByResolvedURL(t *testing.T) {
377+
resetDownloadedFileHashes(t)
378+
379+
const assetPath = "/binaries/example/linux/amd64/example"
380+
const canonicalHash = "2222222222222222222222222222222222222222222222222222222222222222"
381+
const mirroredHash = "3333333333333333333333333333333333333333333333333333333333333333"
382+
383+
var canonicalRequests atomic.Int64
384+
canonicalServer := newHashServer(t, assetPath, canonicalHash, &canonicalRequests)
385+
386+
var mirroredRequests atomic.Int64
387+
mirroredServer := newHashServer(t, assetPath, mirroredHash, &mirroredRequests)
388+
389+
assetURL, err := url.Parse(canonicalServer.URL + assetPath)
390+
if err != nil {
391+
t.Fatalf("error parsing asset url: %v", err)
392+
}
393+
394+
vfsContext := vfs.NewVFSContext()
395+
396+
// Each builder registers the asset, but only the first downloads its checksum.
397+
for i := 0; i < 3; i++ {
398+
builder := NewAssetBuilder(vfsContext, &kops.AssetsSpec{}, false)
399+
400+
asset, err := builder.RemapFile(assetURL, nil)
401+
if err != nil {
402+
t.Fatalf("error remapping file with builder %d: %v", i, err)
403+
}
404+
if actual := asset.SHAValue.Hex(); actual != canonicalHash {
405+
t.Errorf("unexpected hash from builder %d: actual %q, expected %q", i, actual, canonicalHash)
406+
}
407+
if actual := len(builder.FileAssets()); actual != 1 {
408+
t.Errorf("expected builder %d to register 1 file asset, got %d", i, actual)
409+
}
410+
}
411+
412+
// The mirror must not reuse the canonical URL's cached hash.
413+
fileRepository := mirroredServer.URL
414+
mirroredBuilder := NewAssetBuilder(vfsContext, &kops.AssetsSpec{FileRepository: &fileRepository}, false)
415+
mirroredAsset, err := mirroredBuilder.RemapFile(assetURL, nil)
416+
if err != nil {
417+
t.Fatalf("error remapping mirrored file: %v", err)
418+
}
419+
if actual := mirroredAsset.SHAValue.Hex(); actual != mirroredHash {
420+
t.Errorf("unexpected mirrored hash: actual %q, expected %q", actual, mirroredHash)
421+
}
422+
423+
if actual := canonicalRequests.Load(); actual != 1 {
424+
t.Errorf("expected 1 canonical checksum request, got %d", actual)
425+
}
426+
if actual := mirroredRequests.Load(); actual != 1 {
427+
t.Errorf("expected 1 mirrored checksum request, got %d", actual)
428+
}
429+
}
430+
431+
func TestFindHashDoesNotCacheFailures(t *testing.T) {
432+
resetDownloadedFileHashes(t)
433+
434+
const assetPath = "/binaries/example/linux/amd64/example"
435+
const hash = "4444444444444444444444444444444444444444444444444444444444444444"
436+
437+
var published atomic.Bool
438+
var requests atomic.Int64
439+
handler := hashHandler(assetPath, hash, &requests)
440+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
441+
if !published.Load() {
442+
// The VFS retries 404 and 5xx responses.
443+
http.Error(w, "not found", http.StatusForbidden)
444+
return
445+
}
446+
handler.ServeHTTP(w, r)
447+
}))
448+
t.Cleanup(server.Close)
449+
450+
assetURL, err := url.Parse(server.URL + assetPath)
451+
if err != nil {
452+
t.Fatalf("error parsing asset url: %v", err)
453+
}
454+
455+
vfsContext := vfs.NewVFSContext()
456+
builder := NewAssetBuilder(vfsContext, &kops.AssetsSpec{}, false)
457+
458+
if _, err := builder.RemapFile(assetURL, nil); err == nil {
459+
t.Fatal("expected an error while the checksum file is unavailable")
460+
}
461+
462+
published.Store(true)
463+
464+
asset, err := builder.RemapFile(assetURL, nil)
465+
if err != nil {
466+
t.Fatalf("error remapping file after the checksum file was published: %v", err)
467+
}
468+
if actual := asset.SHAValue.Hex(); actual != hash {
469+
t.Errorf("unexpected hash: actual %q, expected %q", actual, hash)
470+
}
471+
if actual := requests.Load(); actual != 1 {
472+
t.Errorf("expected 1 successful checksum request, got %d", actual)
473+
}
474+
}

0 commit comments

Comments
 (0)