|
|
|
@ -16,16 +16,26 @@ limitations under the License.
|
|
|
|
package downloader
|
|
|
|
package downloader
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
import (
|
|
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
|
|
"context"
|
|
|
|
"crypto/sha256"
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/hex"
|
|
|
|
"encoding/hex"
|
|
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"net/http/httptest"
|
|
|
|
"os"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"path/filepath"
|
|
|
|
"testing"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
godigest "github.com/opencontainers/go-digest"
|
|
|
|
|
|
|
|
specs "github.com/opencontainers/image-spec/specs-go"
|
|
|
|
|
|
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
|
|
|
"oras.land/oras-go/v2/registry/remote"
|
|
|
|
|
|
|
|
"oras.land/oras-go/v2/registry/remote/auth"
|
|
|
|
|
|
|
|
|
|
|
|
"helm.sh/helm/v4/internal/test/ensure"
|
|
|
|
"helm.sh/helm/v4/internal/test/ensure"
|
|
|
|
"helm.sh/helm/v4/pkg/cli"
|
|
|
|
"helm.sh/helm/v4/pkg/cli"
|
|
|
|
@ -511,3 +521,318 @@ func TestStripDigestAlgorithm(t *testing.T) {
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// writeRepoCacheIndex publishes the generated index under the name the repo
|
|
|
|
|
|
|
|
// cache looks for. repotest.Server.LinkIndices symlinks instead of copying,
|
|
|
|
|
|
|
|
// which needs a privilege Windows does not grant by default.
|
|
|
|
|
|
|
|
func writeRepoCacheIndex(t *testing.T, root string) {
|
|
|
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
idx, err := os.ReadFile(filepath.Join(root, "index.yaml"))
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.NoError(t, os.WriteFile(filepath.Join(root, "test-index.yaml"), idx, 0o644))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// tamperedChartServer serves a repository whose index records the real digest
|
|
|
|
|
|
|
|
// of signtest-0.1.0.tgz while the archive itself has been replaced. It returns
|
|
|
|
|
|
|
|
// the server, a downloader pointed at it, and the bytes now being served.
|
|
|
|
|
|
|
|
func tamperedChartServer(t *testing.T, contentCache string) (*repotest.Server, *ChartDownloader, []byte) {
|
|
|
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
srv := repotest.NewTempServer(t, repotest.WithChartSourceGlob("testdata/*.tgz*"))
|
|
|
|
|
|
|
|
t.Cleanup(srv.Stop)
|
|
|
|
|
|
|
|
require.NoError(t, srv.CreateIndex())
|
|
|
|
|
|
|
|
writeRepoCacheIndex(t, srv.Root())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
served := filepath.Join(srv.Root(), "signtest-0.1.0.tgz")
|
|
|
|
|
|
|
|
original, err := os.ReadFile(served)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
tampered := append(append([]byte(nil), original...), []byte("appended by a rewritten mirror")...)
|
|
|
|
|
|
|
|
require.NoError(t, os.WriteFile(served, tampered, 0o644))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
repoFile := filepath.Join(srv.Root(), "repositories.yaml")
|
|
|
|
|
|
|
|
c := &ChartDownloader{
|
|
|
|
|
|
|
|
Out: os.Stderr,
|
|
|
|
|
|
|
|
Verify: VerifyNever,
|
|
|
|
|
|
|
|
RepositoryConfig: repoFile,
|
|
|
|
|
|
|
|
RepositoryCache: srv.Root(),
|
|
|
|
|
|
|
|
ContentCache: contentCache,
|
|
|
|
|
|
|
|
Getters: getter.All(&cli.EnvSettings{
|
|
|
|
|
|
|
|
RepositoryConfig: repoFile,
|
|
|
|
|
|
|
|
RepositoryCache: srv.Root(),
|
|
|
|
|
|
|
|
ContentCache: contentCache,
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
Cache: &DiskCache{Root: contentCache},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return srv, c, tampered
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TestDownloadTo_RejectsChartNotMatchingIndexDigest(t *testing.T) {
|
|
|
|
|
|
|
|
contentCache := t.TempDir()
|
|
|
|
|
|
|
|
dest := t.TempDir()
|
|
|
|
|
|
|
|
_, c, _ := tamperedChartServer(t, contentCache)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_, _, err := c.DownloadTo("test/signtest", "0.1.0", dest)
|
|
|
|
|
|
|
|
require.Error(t, err, "a chart that does not match the index digest must not be accepted")
|
|
|
|
|
|
|
|
assert.Contains(t, err.Error(), "does not match the digest recorded for it in the repository index")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Nothing may be left behind in dest for a later step to pick up.
|
|
|
|
|
|
|
|
entries, err := os.ReadDir(dest)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Empty(t, entries, "rejected chart must not be written to the destination")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TestDownloadToCache_RejectsChartNotMatchingIndexDigest(t *testing.T) {
|
|
|
|
|
|
|
|
contentCache := t.TempDir()
|
|
|
|
|
|
|
|
_, c, _ := tamperedChartServer(t, contentCache)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
digestString, _, err := c.ResolveChartVersion("test/signtest", "0.1.0")
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
digestBytes, err := hex.DecodeString(stripDigestAlgorithm(digestString))
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
var want [sha256.Size]byte
|
|
|
|
|
|
|
|
copy(want[:], digestBytes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_, _, err = c.DownloadToCache("test/signtest", "0.1.0")
|
|
|
|
|
|
|
|
require.Error(t, err, "a chart that does not match the index digest must not be accepted")
|
|
|
|
|
|
|
|
assert.Contains(t, err.Error(), "does not match the digest recorded for it in the repository index")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The rejected bytes must not have been filed in the content cache under
|
|
|
|
|
|
|
|
// the digest they failed to match.
|
|
|
|
|
|
|
|
_, err = c.Cache.Get(want, CacheChart)
|
|
|
|
|
|
|
|
assert.Error(t, err, "rejected chart must not be written to the content cache")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TestDownloadToCache_DiscardsCacheEntryNotMatchingItsDigest(t *testing.T) {
|
|
|
|
|
|
|
|
srv := repotest.NewTempServer(t, repotest.WithChartSourceGlob("testdata/*.tgz*"))
|
|
|
|
|
|
|
|
defer srv.Stop()
|
|
|
|
|
|
|
|
require.NoError(t, srv.CreateIndex())
|
|
|
|
|
|
|
|
writeRepoCacheIndex(t, srv.Root())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
repoFile := filepath.Join(srv.Root(), "repositories.yaml")
|
|
|
|
|
|
|
|
contentCache := t.TempDir()
|
|
|
|
|
|
|
|
c := ChartDownloader{
|
|
|
|
|
|
|
|
Out: os.Stderr,
|
|
|
|
|
|
|
|
Verify: VerifyNever,
|
|
|
|
|
|
|
|
RepositoryConfig: repoFile,
|
|
|
|
|
|
|
|
RepositoryCache: srv.Root(),
|
|
|
|
|
|
|
|
ContentCache: contentCache,
|
|
|
|
|
|
|
|
Getters: getter.All(&cli.EnvSettings{
|
|
|
|
|
|
|
|
RepositoryConfig: repoFile,
|
|
|
|
|
|
|
|
RepositoryCache: srv.Root(),
|
|
|
|
|
|
|
|
ContentCache: contentCache,
|
|
|
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
Cache: &DiskCache{Root: contentCache},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
digestString, _, err := c.ResolveChartVersion("test/signtest", "0.1.0")
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
digestBytes, err := hex.DecodeString(stripDigestAlgorithm(digestString))
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
var want [sha256.Size]byte
|
|
|
|
|
|
|
|
copy(want[:], digestBytes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Poison the content cache the way an older Helm could have: content that
|
|
|
|
|
|
|
|
// does not hash to the key it is stored under.
|
|
|
|
|
|
|
|
poison := []byte("not the chart this digest names")
|
|
|
|
|
|
|
|
_, err = c.Cache.Put(want, bytes.NewBuffer(poison), CacheChart)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pth, _, err := c.DownloadToCache("test/signtest", "0.1.0")
|
|
|
|
|
|
|
|
require.NoError(t, err, "a bad cache entry should be replaced by a fresh download, not returned")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
got, err := os.ReadFile(pth)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.NotEqual(t, poison, got, "poisoned cache entry must not be served")
|
|
|
|
|
|
|
|
assert.Equal(t, want, sha256.Sum256(got), "served chart must hash to the index digest")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TestIndexDigestVerificationScope(t *testing.T) {
|
|
|
|
|
|
|
|
good := []byte("chart bytes")
|
|
|
|
|
|
|
|
want := sha256.Sum256(good)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
t.Run("mismatch is rejected", func(t *testing.T) {
|
|
|
|
|
|
|
|
err := verifyIndexDigest("ref", hex.EncodeToString(want[:]), want, []byte("other bytes"))
|
|
|
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("match is accepted", func(t *testing.T) {
|
|
|
|
|
|
|
|
err := verifyIndexDigest("ref", hex.EncodeToString(want[:]), want, good)
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("no index digest is a no-op", func(t *testing.T) {
|
|
|
|
|
|
|
|
// A chart referenced by a bare URL has no index entry to check against.
|
|
|
|
|
|
|
|
err := verifyIndexDigest("ref", "", [sha256.Size]byte{}, []byte("anything"))
|
|
|
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ociChartDownloader pushes testdata/signtest-0.1.0.tgz to an in-process
|
|
|
|
|
|
|
|
// registry and returns a downloader wired to it, the chart reference pinned to
|
|
|
|
|
|
|
|
// the pushed manifest digest, the push result and the registry.
|
|
|
|
|
|
|
|
func ociChartDownloader(t *testing.T, contentCache string) (*ChartDownloader, string, *registry.PushResult, *repotest.OCIServer) {
|
|
|
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
|
|
|
srv, err := repotest.NewOCIServer(t, dir)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
go srv.ListenAndServe()
|
|
|
|
|
|
|
|
dialer := &net.Dialer{Timeout: time.Second}
|
|
|
|
|
|
|
|
require.Eventually(t, func() bool {
|
|
|
|
|
|
|
|
conn, err := dialer.DialContext(t.Context(), "tcp", srv.RegistryURL)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
conn.Close()
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
}, 30*time.Second, 20*time.Millisecond)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
client, err := registry.NewClient(
|
|
|
|
|
|
|
|
registry.ClientOptCredentialsFile(filepath.Join(dir, "config.json")),
|
|
|
|
|
|
|
|
registry.ClientOptPlainHTTP(),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.NoError(t, client.Login(srv.RegistryURL,
|
|
|
|
|
|
|
|
registry.LoginOptBasicAuth(srv.TestUsername, srv.TestPassword),
|
|
|
|
|
|
|
|
registry.LoginOptInsecure(true),
|
|
|
|
|
|
|
|
registry.LoginOptPlainText(true)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
archive, err := os.ReadFile("testdata/signtest-0.1.0.tgz")
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
pushed, err := client.Push(archive, srv.RegistryURL+"/u/ocitestuser/signtest:0.1.0")
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
settings := &cli.EnvSettings{ContentCache: contentCache}
|
|
|
|
|
|
|
|
c := &ChartDownloader{
|
|
|
|
|
|
|
|
Out: os.Stderr,
|
|
|
|
|
|
|
|
Verify: VerifyNever,
|
|
|
|
|
|
|
|
ContentCache: contentCache,
|
|
|
|
|
|
|
|
Getters: getter.All(settings),
|
|
|
|
|
|
|
|
Options: []getter.Option{getter.WithRegistryClient(client)},
|
|
|
|
|
|
|
|
RegistryClient: client,
|
|
|
|
|
|
|
|
Cache: &DiskCache{Root: contentCache},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ref := "oci://" + srv.RegistryURL + "/u/ocitestuser/signtest@" + pushed.Manifest.Digest
|
|
|
|
|
|
|
|
return c, ref, pushed, srv
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func digestKey(t *testing.T, d string) [sha256.Size]byte {
|
|
|
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
b, err := hex.DecodeString(stripDigestAlgorithm(d))
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
require.Len(t, b, sha256.Size)
|
|
|
|
|
|
|
|
var k [sha256.Size]byte
|
|
|
|
|
|
|
|
copy(k[:], b)
|
|
|
|
|
|
|
|
return k
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TestDownloadToCache_OCIKeyedByChartLayerDigest(t *testing.T) {
|
|
|
|
|
|
|
|
contentCache := t.TempDir()
|
|
|
|
|
|
|
|
c, ref, pushed, _ := ociChartDownloader(t, contentCache)
|
|
|
|
|
|
|
|
layer := digestKey(t, pushed.Chart.Digest)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pth, _, err := c.DownloadToCache(ref, "0.1.0")
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
got, err := os.ReadFile(pth)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, layer, sha256.Sum256(got), "cached chart must hash to its chart layer digest")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The entry must be filed under the chart layer digest, where it can be
|
|
|
|
|
|
|
|
// checked, and not under the manifest digest, where it could not.
|
|
|
|
|
|
|
|
layerPath, err := c.Cache.Get(layer, CacheChart)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, layerPath, pth)
|
|
|
|
|
|
|
|
_, err = c.Cache.Get(digestKey(t, pushed.Manifest.Digest), CacheChart)
|
|
|
|
|
|
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TestDownloadToCache_OCIDiscardsCacheEntryNotMatchingItsDigest(t *testing.T) {
|
|
|
|
|
|
|
|
contentCache := t.TempDir()
|
|
|
|
|
|
|
|
c, ref, pushed, _ := ociChartDownloader(t, contentCache)
|
|
|
|
|
|
|
|
layer := digestKey(t, pushed.Chart.Digest)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
poison := []byte("not the chart this digest names")
|
|
|
|
|
|
|
|
_, err := c.Cache.Put(layer, bytes.NewBuffer(poison), CacheChart)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pth, _, err := c.DownloadToCache(ref, "0.1.0")
|
|
|
|
|
|
|
|
require.NoError(t, err, "a bad cache entry should be replaced by a fresh download, not returned")
|
|
|
|
|
|
|
|
got, err := os.ReadFile(pth)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, layer, sha256.Sum256(got), "served chart must hash to its chart layer digest")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TestDownloadTo_OCIIgnoresCacheEntryNotMatchingItsDigest(t *testing.T) {
|
|
|
|
|
|
|
|
contentCache := t.TempDir()
|
|
|
|
|
|
|
|
dest := t.TempDir()
|
|
|
|
|
|
|
|
c, ref, pushed, _ := ociChartDownloader(t, contentCache)
|
|
|
|
|
|
|
|
layer := digestKey(t, pushed.Chart.Digest)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Before this change an OCI chart was cached under its manifest digest.
|
|
|
|
|
|
|
|
// Neither an entry like that nor a bad one under the layer digest may be
|
|
|
|
|
|
|
|
// served in place of the chart.
|
|
|
|
|
|
|
|
poison := []byte("not the chart this digest names")
|
|
|
|
|
|
|
|
_, err := c.Cache.Put(digestKey(t, pushed.Manifest.Digest), bytes.NewBuffer(poison), CacheChart)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = c.Cache.Put(layer, bytes.NewBuffer(poison), CacheChart)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
saved, _, err := c.DownloadTo(ref, "0.1.0", dest)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
got, err := os.ReadFile(saved)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, layer, sha256.Sum256(got), "saved chart must hash to its chart layer digest")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TestDownloadTo_OCIDigestOnlyRef(t *testing.T) {
|
|
|
|
|
|
|
|
c, ref, pushed, _ := ociChartDownloader(t, t.TempDir())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// With no version the registry client resolves no digest for the ref, so
|
|
|
|
|
|
|
|
// the cache is not consulted and the chart is downloaded directly.
|
|
|
|
|
|
|
|
saved, _, err := c.DownloadTo(ref, "", t.TempDir())
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
got, err := os.ReadFile(saved)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, digestKey(t, pushed.Chart.Digest), sha256.Sum256(got))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TestDownloadToCache_OCIImageIndexRoot(t *testing.T) {
|
|
|
|
|
|
|
|
contentCache := t.TempDir()
|
|
|
|
|
|
|
|
c, _, pushed, srv := ociChartDownloader(t, contentCache)
|
|
|
|
|
|
|
|
layer := digestKey(t, pushed.Chart.Digest)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Wrap the chart manifest in an image index and move the tag onto it.
|
|
|
|
|
|
|
|
repo, err := remote.NewRepository(srv.RegistryURL + "/u/ocitestuser/signtest")
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
repo.PlainHTTP = true
|
|
|
|
|
|
|
|
repo.Client = &auth.Client{Credential: auth.StaticCredential(srv.RegistryURL, auth.Credential{
|
|
|
|
|
|
|
|
Username: srv.TestUsername,
|
|
|
|
|
|
|
|
Password: srv.TestPassword,
|
|
|
|
|
|
|
|
})}
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
manifest, err := repo.Resolve(ctx, "0.1.0")
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
index, err := json.Marshal(ocispec.Index{
|
|
|
|
|
|
|
|
Versioned: specs.Versioned{SchemaVersion: 2},
|
|
|
|
|
|
|
|
MediaType: ocispec.MediaTypeImageIndex,
|
|
|
|
|
|
|
|
Manifests: []ocispec.Descriptor{manifest},
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
indexDesc := ocispec.Descriptor{
|
|
|
|
|
|
|
|
MediaType: ocispec.MediaTypeImageIndex,
|
|
|
|
|
|
|
|
Digest: godigest.FromBytes(index),
|
|
|
|
|
|
|
|
Size: int64(len(index)),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
require.NoError(t, repo.PushReference(ctx, indexDesc, bytes.NewReader(index), "0.1.0"))
|
|
|
|
|
|
|
|
ref := "oci://" + srv.RegistryURL + "/u/ocitestuser/signtest@" + indexDesc.Digest.String()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pth, _, err := c.DownloadToCache(ref, "0.1.0")
|
|
|
|
|
|
|
|
require.NoError(t, err, "a chart behind an image index must still download")
|
|
|
|
|
|
|
|
got, err := os.ReadFile(pth)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, layer, sha256.Sum256(got))
|
|
|
|
|
|
|
|
_, err = c.Cache.Get(digestKey(t, indexDesc.Digest.String()), CacheChart)
|
|
|
|
|
|
|
|
require.ErrorIs(t, err, os.ErrNotExist, "chart must not be cached under the index digest")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
saved, _, err := c.DownloadTo(ref, "0.1.0", t.TempDir())
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
got, err = os.ReadFile(saved)
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, layer, sha256.Sum256(got))
|
|
|
|
|
|
|
|
}
|
|
|
|
|