Check Helm HTTP repositories digest (#919)

Closes #917

Signed-off-by: Sergio Castaño Arteaga <tegioz@icloud.com>
This commit is contained in:
Sergio C. Arteaga 2020-12-02 09:23:10 +01:00 committed by GitHub
parent d62375b7d3
commit 2566110c13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 101 additions and 45 deletions

View File

@ -70,7 +70,7 @@ func GetKindFromName(kind string) (RepositoryKind, error) {
// HelmIndexLoader interface defines the methods a Helm index loader
// implementation should provide.
type HelmIndexLoader interface {
LoadIndex(r *Repository) (*helmrepo.IndexFile, error)
LoadIndex(r *Repository) (*helmrepo.IndexFile, string, error)
}
// OLMRepositoryExporter describes the methods an OLMRepositoryExporter

View File

@ -12,7 +12,7 @@ import (
type HelmIndexLoader struct{}
// LoadIndex downloads and parses the index file of the provided repository.
func (l *HelmIndexLoader) LoadIndex(r *hub.Repository) (*helmrepo.IndexFile, error) {
func (l *HelmIndexLoader) LoadIndex(r *hub.Repository) (*helmrepo.IndexFile, string, error) {
repoConfig := &helmrepo.Entry{
Name: r.Name,
URL: r.URL,
@ -22,15 +22,15 @@ func (l *HelmIndexLoader) LoadIndex(r *hub.Repository) (*helmrepo.IndexFile, err
getters := getter.All(&cli.EnvSettings{})
chartRepository, err := helmrepo.NewChartRepository(repoConfig, getters)
if err != nil {
return nil, err
return nil, "", err
}
path, err := chartRepository.DownloadIndexFile()
indexPath, err := chartRepository.DownloadIndexFile()
if err != nil {
return nil, err
return nil, "", err
}
indexFile, err := helmrepo.LoadIndexFile(path)
indexFile, err := helmrepo.LoadIndexFile(indexPath)
if err != nil {
return nil, err
return nil, "", err
}
return indexFile, nil
return indexFile, indexPath, nil
}

View File

@ -2,6 +2,8 @@ package repo
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@ -164,7 +166,7 @@ func (m *Manager) Add(ctx context.Context, orgName string, r *hub.Repository) er
}
}
if r.Kind == hub.Helm && SchemeIsHTTP(u) {
if _, err := m.helmIndexLoader.LoadIndex(r); err != nil {
if _, _, err := m.helmIndexLoader.LoadIndex(r); err != nil {
return fmt.Errorf("%w: %s: %s", hub.ErrInvalidInput, "invalid url", err.Error())
}
}
@ -481,16 +483,40 @@ func (m *Manager) GetOwnedByUserJSON(ctx context.Context, includeCredentials boo
return util.DBQueryJSON(ctx, m.db, getUserReposDBQ, userID, includeCredentials)
}
// GetRemoteDigest gets the repository's digest available in the remote. In the
// case of git based repositories, the digest corresponds to the hash of the
// last commit. In OCI based repositories, it is the digest of the image the
// repository url points to.
// GetRemoteDigest gets the repository's digest available in the remote.
func (m *Manager) GetRemoteDigest(ctx context.Context, r *hub.Repository) (string, error) {
var digest string
u, _ := url.Parse(r.URL)
switch {
case r.Kind == hub.Helm && SchemeIsHTTP(u):
// Digest is obtained hashing the repository index.yaml file
_, indexPath, err := m.helmIndexLoader.LoadIndex(r)
if err != nil {
return "", err
}
indexBytes, err := ioutil.ReadFile(indexPath)
if err != nil {
return "", err
}
hash := sha256.Sum256(indexBytes)
digest = hex.EncodeToString(hash[:])
case r.Kind == hub.OLM && u.Scheme == "oci":
// Digest is obtained from the index image digest
refName := strings.TrimPrefix(r.URL, hub.RepositoryOCIPrefix)
ref, err := name.ParseReference(refName)
if err != nil {
return digest, err
}
desc, err := remote.Head(ref)
if err != nil {
return digest, err
}
digest = desc.Digest.String()
case SchemeIsHTTP(u) && u.Host == "github.com":
// Digest is obtained from the last commit in the repository
pathParts := strings.Split(strings.TrimPrefix(u.Path, "/"), "/")
if len(pathParts) < 2 {
break
@ -510,17 +536,6 @@ func (m *Manager) GetRemoteDigest(ctx context.Context, r *hub.Repository) (strin
if len(commits) == 1 {
digest = *commits[0].SHA
}
case u.Scheme == "oci":
refName := strings.TrimPrefix(r.URL, hub.RepositoryOCIPrefix)
ref, err := name.ParseReference(refName)
if err != nil {
return digest, err
}
desc, err := remote.Head(ref)
if err != nil {
return digest, err
}
digest = desc.Digest.String()
}
return digest, nil
@ -634,7 +649,7 @@ func (m *Manager) Update(ctx context.Context, r *hub.Repository) error {
}
}
if r.Kind == hub.Helm && SchemeIsHTTP(u) {
if _, err := m.helmIndexLoader.LoadIndex(r); err != nil {
if _, _, err := m.helmIndexLoader.LoadIndex(r); err != nil {
return fmt.Errorf("%w: %s: %s", hub.ErrInvalidInput, "invalid url", err.Error())
}
}

View File

@ -22,10 +22,7 @@ import (
"github.com/stretchr/testify/require"
)
var (
cfg = viper.New()
errFake = errors.New("fake error for tests")
)
var cfg = viper.New()
func TestAdd(t *testing.T) {
ctx := context.WithValue(context.Background(), hub.UserIDKey, "userID")
@ -179,7 +176,7 @@ func TestAdd(t *testing.T) {
t.Run(tc.errMsg, func(t *testing.T) {
l := &HelmIndexLoaderMock{}
if tc.lErr != nil {
l.On("LoadIndex", mock.Anything).Return(nil, tc.lErr)
l.On("LoadIndex", tc.r).Return(nil, "", tc.lErr)
}
m := NewManager(cfg, nil, nil, WithHelmIndexLoader(l))
@ -205,7 +202,7 @@ func TestAdd(t *testing.T) {
Action: hub.AddOrganizationRepository,
}).Return(tests.ErrFake)
l := &HelmIndexLoaderMock{}
l.On("LoadIndex", r).Return(nil, nil)
l.On("LoadIndex", r).Return(nil, "", nil)
m := NewManager(cfg, nil, az, WithHelmIndexLoader(l))
err := m.Add(ctx, "orgName", r)
@ -252,7 +249,7 @@ func TestAdd(t *testing.T) {
Action: hub.AddOrganizationRepository,
}).Return(nil)
l := &HelmIndexLoaderMock{}
l.On("LoadIndex", tc.r).Return(nil, nil)
l.On("LoadIndex", tc.r).Return(nil, "", nil)
m := NewManager(cfg, db, az, WithHelmIndexLoader(l))
err := m.Add(ctx, "orgName", tc.r)
@ -298,7 +295,7 @@ func TestAdd(t *testing.T) {
}).Return(nil)
l := &HelmIndexLoaderMock{}
if tc.r.Kind == hub.Helm {
l.On("LoadIndex", tc.r).Return(nil, nil)
l.On("LoadIndex", tc.r).Return(nil, "", nil)
}
m := NewManager(cfg, db, az, WithHelmIndexLoader(l))
@ -524,11 +521,11 @@ func TestClaimOwnership(t *testing.T) {
rc := &ClonerMock{}
var r *hub.Repository
_ = json.Unmarshal(opaRepoJSON, &r)
rc.On("CloneRepository", ctx, r).Return("", "", errFake)
rc.On("CloneRepository", ctx, r).Return("", "", tests.ErrFake)
m := NewManager(cfg, db, nil, withRepositoryCloner(rc))
err := m.ClaimOwnership(ctx, "repo1", org)
assert.Equal(t, errFake, err)
assert.Equal(t, tests.ErrFake, err)
db.AssertExpectations(t)
})
@ -1141,6 +1138,50 @@ func TestGetOwnedByUserJSON(t *testing.T) {
})
}
func TestGetRemoteDigest(t *testing.T) {
ctx := context.Background()
helmHTTP := &hub.Repository{
Kind: hub.Helm,
Name: "repo1",
URL: "https://myrepo.url",
}
t.Run("helm-http: error loading index", func(t *testing.T) {
l := &HelmIndexLoaderMock{}
l.On("LoadIndex", helmHTTP).Return(nil, "", tests.ErrFake)
m := NewManager(cfg, nil, nil, WithHelmIndexLoader(l))
digest, err := m.GetRemoteDigest(ctx, helmHTTP)
assert.Empty(t, digest)
assert.Equal(t, tests.ErrFake, err)
})
t.Run("helm-http: error reading index file", func(t *testing.T) {
l := &HelmIndexLoaderMock{}
l.On("LoadIndex", helmHTTP).Return(nil, "invalid-path", nil)
m := NewManager(cfg, nil, nil, WithHelmIndexLoader(l))
digest, err := m.GetRemoteDigest(ctx, helmHTTP)
assert.Empty(t, digest)
assert.Error(t, err)
})
t.Run("helm-http: success", func(t *testing.T) {
f, err := ioutil.TempFile("", "")
require.NoError(t, err)
_, _ = f.Write([]byte("indexFileContent"))
require.NoError(t, err)
l := &HelmIndexLoaderMock{}
l.On("LoadIndex", helmHTTP).Return(nil, f.Name(), nil)
m := NewManager(cfg, nil, nil, WithHelmIndexLoader(l))
digest, err := m.GetRemoteDigest(ctx, helmHTTP)
assert.Equal(t, "a1cbe8e02116f43084632fbf313c4ed02772f93af327bbc16989a30bc04ddc89", digest)
assert.Nil(t, err)
})
}
func TestSetLastTrackingResults(t *testing.T) {
ctx := context.Background()
repoID := "00000000-0000-0000-0000-000000000001"
@ -1413,7 +1454,7 @@ func TestUpdate(t *testing.T) {
t.Run(tc.errMsg, func(t *testing.T) {
l := &HelmIndexLoaderMock{}
if tc.lErr != nil {
l.On("LoadIndex", mock.Anything).Return(nil, tc.lErr)
l.On("LoadIndex", tc.r).Return(nil, "", tc.lErr)
}
m := NewManager(cfg, nil, nil, WithHelmIndexLoader(l))
@ -1446,7 +1487,7 @@ func TestUpdate(t *testing.T) {
Action: hub.UpdateOrganizationRepository,
}).Return(tests.ErrFake)
l := &HelmIndexLoaderMock{}
l.On("LoadIndex", r).Return(nil, nil)
l.On("LoadIndex", r).Return(nil, "", nil)
m := NewManager(cfg, db, az, WithHelmIndexLoader(l))
err := m.Update(ctx, r)
@ -1502,7 +1543,7 @@ func TestUpdate(t *testing.T) {
}).Return(nil)
l := &HelmIndexLoaderMock{}
l.On("LoadIndex", tc.r).Return(nil, nil)
l.On("LoadIndex", tc.r).Return(nil, "", nil)
m := NewManager(cfg, db, az, WithHelmIndexLoader(l))
err := m.Update(ctx, tc.r)
@ -1544,7 +1585,7 @@ func TestUpdate(t *testing.T) {
db.On("Exec", ctx, updateRepoDBQ, "userID", mock.Anything).Return(nil)
l := &HelmIndexLoaderMock{}
if tc.r.Kind == hub.Helm {
l.On("LoadIndex", tc.r).Return(nil, nil)
l.On("LoadIndex", tc.r).Return(nil, "", nil)
}
m := NewManager(cfg, db, nil, WithHelmIndexLoader(l))

View File

@ -26,10 +26,10 @@ type HelmIndexLoaderMock struct {
}
// LoadIndex implements the HelmIndexLoader interface.
func (m *HelmIndexLoaderMock) LoadIndex(r *hub.Repository) (*repo.IndexFile, error) {
func (m *HelmIndexLoaderMock) LoadIndex(r *hub.Repository) (*repo.IndexFile, string, error) {
args := m.Called(r)
indexFile, _ := args.Get(0).(*repo.IndexFile)
return indexFile, args.Error(1)
return indexFile, args.String(1), args.Error(2)
}
// ManagerMock is a mock implementation of the RepositoryManager interface.

View File

@ -197,7 +197,7 @@ func (t *Tracker) getCharts() (map[string][]*helmrepo.ChartVersion, error) {
case "http", "https":
// Load repository index file
t.logger.Debug().Msg("loading repository index file")
indexFile, err := t.svc.Il.LoadIndex(t.r)
indexFile, _, err := t.svc.Il.LoadIndex(t.r)
if err != nil {
return nil, fmt.Errorf("error loading repository index file: %w", err)
}

View File

@ -45,7 +45,7 @@ func TestTracker(t *testing.T) {
}
tw := newTrackerWrapper(r)
tw.rm.On("GetPackagesDigest", tw.ctx, r.RepositoryID).Return(nil, nil)
tw.il.On("LoadIndex", r).Return(nil, tests.ErrFake)
tw.il.On("LoadIndex", r).Return(nil, "", tests.ErrFake)
// Run tracker and check expectations
err := tw.t.Track()
@ -314,7 +314,7 @@ func TestTracker(t *testing.T) {
tw := newTrackerWrapper(tc.r)
tw.rm.On("GetPackagesDigest", tw.ctx, tc.r.RepositoryID).
Return(tc.packagesDigest[tc.r.RepositoryID], nil)
tw.il.On("LoadIndex", tc.r).Return(tc.indexFile[tc.r.RepositoryID], nil)
tw.il.On("LoadIndex", tc.r).Return(tc.indexFile[tc.r.RepositoryID], "", nil)
u, _ := url.Parse(tc.r.URL)
u.Path = path.Join(u.Path, hub.RepositoryMetadataFile)
tw.rm.On("GetMetadata", u.String()).Return(&hub.RepositoryMetadata{