Skip to content

Commit c2e1bd2

Browse files
fix: make sure that helm downloader does not overwrite chart with provenance data (#1481)
<!-- markdownlint-disable MD041 --> #### What this PR does / why we need it in the helm downloader, there was a case when the chart data was overwritten with the provenance data. if the target path passed to the downloader was a path such as `path.tgz` then this path was reused for writing the provenance file. This was now fixed and there will be: 1. A download path pointing to the .tgz => chart.tgz 2. A provenance file (if it exists) => chart.prov #### Which issue(s) this PR fixes <!-- Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`. --> Note that before, due to buggy behavior the output provenance file was just called `chart` because the .prov file extension was not properly added. this has been fixed as well. additionally if the file path was `tar.gz` instead of `tgz`, the file ending removal was incorrect. this has been fixed. This was also the last reason why the ocm download resource command returned incorrect data in the tgz when downloading a resource with accessType helm. fix #1469
1 parent 0f4f602 commit c2e1bd2

3 files changed

Lines changed: 100 additions & 11 deletions

File tree

api/ocm/extensions/download/handlers/helm/download_test.go

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import (
44
. "github.com/mandelsoft/goutils/testutils"
55
. "github.com/onsi/ginkgo/v2"
66
. "github.com/onsi/gomega"
7-
8-
. "ocm.software/ocm/api/helper/builder"
7+
"helm.sh/helm/v3/pkg/registry"
98

109
"github.com/mandelsoft/filepath/pkg/filepath"
1110
"github.com/mandelsoft/vfs/pkg/vfs"
1211

12+
. "ocm.software/ocm/api/helper/builder"
1313
envhelper "ocm.software/ocm/api/helper/env"
1414
"ocm.software/ocm/api/oci/artdesc"
1515
"ocm.software/ocm/api/oci/extensions/repositories/artifactset"
@@ -21,6 +21,7 @@ import (
2121
"ocm.software/ocm/api/ocm/selectors"
2222
"ocm.software/ocm/api/utils/accessio"
2323
"ocm.software/ocm/api/utils/accessobj"
24+
"ocm.software/ocm/api/utils/blobaccess/dirtree"
2425
"ocm.software/ocm/api/utils/tarutils"
2526
)
2627

@@ -30,10 +31,11 @@ const (
3031
Version = "v1.0.0"
3132
OCIResource = "helm"
3233

33-
ArtifactType = "NotHelmChart"
34-
SpecialOCIResource = "specialhelm"
35-
UnusualOCIResource = "unusualhelm"
36-
LegacyOCIResource = "legacyhelm"
34+
ArtifactType = "NotHelmChart"
35+
SpecialOCIResource = "specialhelm"
36+
UnusualOCIResource = "unusualhelm"
37+
LegacyOCIResource = "legacyhelm"
38+
HelmViaChartMediaType = "helmviaaccess"
3739
)
3840

3941
var _ = Describe("upload", func() {
@@ -57,6 +59,9 @@ var _ = Describe("upload", func() {
5759
env.Resource(LegacyOCIResource, Version, resourcetypes.HELM_CHART, v1.LocalRelation, func() {
5860
env.BlobFromFile(artifactset.MediaType(artdesc.MediaTypeImageManifest), filepath.Join("/testdata/legacy-pre-hip-helm-chart.tgz"))
5961
})
62+
env.Resource(HelmViaChartMediaType, Version, resourcetypes.HELM_CHART, v1.LocalRelation, func() {
63+
env.BlobFromDirTree("/testdata/test-chart", dirtree.WithMimeType(registry.ChartLayerMediaType), dirtree.WithPreserveDir(true), dirtree.WithCompressWithGzip(true))
64+
})
6065
})
6166
})
6267
})
@@ -85,21 +90,47 @@ var _ = Describe("upload", func() {
8590

8691
It("successfully download unusual artifacts with non-defacto helm chart order", func() {
8792
MustBeSuccessful(download.RegisterHandlerByName(env, helm.PATH, nil, download.ForArtifactType(ArtifactType)))
88-
8993
repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, CTFPath, 0o777, env))
9094
cv := Must(repo.LookupComponentVersion(Component, Version))
9195
path := Must(download.DownloadResource(env.OCMContext(), Must(cv.SelectResources(selectors.Identity(v1.Identity{"name": UnusualOCIResource})))[0], "/resource", download.WithFileSystem(env.FileSystem())))
9296
MustBeSuccessful(tarutils.ExtractArchiveToFs(env.FileSystem(), path, env.FileSystem()))
9397
Expect(Must(vfs.FileExists(env.FileSystem(), "/postgresql/Chart.yaml"))).To(BeTrue())
9498
})
9599

96-
It("successfully download artifacts with helm chart content with legacy content type", func() {
100+
It("successfully download unusual artifacts with non-defacto helm chart order and path given to tgz", func() {
97101
MustBeSuccessful(download.RegisterHandlerByName(env, helm.PATH, nil, download.ForArtifactType(ArtifactType)))
102+
repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, CTFPath, 0o777, env))
103+
cv := Must(repo.LookupComponentVersion(Component, Version))
104+
path := Must(download.DownloadResource(env.OCMContext(), Must(cv.SelectResources(selectors.Identity(v1.Identity{"name": UnusualOCIResource})))[0], "/path.tgz", download.WithFileSystem(env.FileSystem())))
105+
MustBeSuccessful(tarutils.ExtractArchiveToFs(env.FileSystem(), path, env.FileSystem()))
106+
Expect(Must(vfs.FileExists(env.FileSystem(), "/postgresql/Chart.yaml"))).To(BeTrue())
107+
})
98108

109+
It("successfully download unusual artifacts with non-defacto helm chart order and path given to tar.gz", func() {
110+
MustBeSuccessful(download.RegisterHandlerByName(env, helm.PATH, nil, download.ForArtifactType(ArtifactType)))
111+
repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, CTFPath, 0o777, env))
112+
cv := Must(repo.LookupComponentVersion(Component, Version))
113+
path := Must(download.DownloadResource(env.OCMContext(), Must(cv.SelectResources(selectors.Identity(v1.Identity{"name": UnusualOCIResource})))[0], "/path.tar.gz", download.WithFileSystem(env.FileSystem())))
114+
MustBeSuccessful(tarutils.ExtractArchiveToFs(env.FileSystem(), path, env.FileSystem()))
115+
Expect(Must(vfs.FileExists(env.FileSystem(), "/postgresql/Chart.yaml"))).To(BeTrue())
116+
})
117+
118+
It("successfully download unusual artifacts with helm media type", func() {
119+
MustBeSuccessful(download.RegisterHandlerByName(env, helm.PATH, nil, download.ForArtifactType(ArtifactType)))
120+
repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, CTFPath, 0o777, env))
121+
cv := Must(repo.LookupComponentVersion(Component, Version))
122+
path := Must(download.DownloadResource(env.OCMContext(), Must(cv.SelectResources(selectors.Identity(v1.Identity{"name": HelmViaChartMediaType})))[0], "/resource", download.WithFileSystem(env.FileSystem())))
123+
MustBeSuccessful(tarutils.ExtractArchiveToFs(env.FileSystem(), path, env.FileSystem()))
124+
Expect(Must(vfs.FileExists(env.FileSystem(), "test-chart/Chart.yaml"))).To(BeTrue())
125+
})
126+
127+
It("successfully download artifacts with helm chart content with legacy content type", func() {
128+
MustBeSuccessful(download.RegisterHandlerByName(env, helm.PATH, nil, download.ForArtifactType(ArtifactType)))
99129
repo := Must(ctf.Open(env.OCMContext(), accessobj.ACC_READONLY, CTFPath, 0o777, env))
100130
cv := Must(repo.LookupComponentVersion(Component, Version))
101131
path := Must(download.DownloadResource(env.OCMContext(), Must(cv.SelectResources(selectors.Identity(v1.Identity{"name": LegacyOCIResource})))[0], "/resource", download.WithFileSystem(env.FileSystem())))
102132
MustBeSuccessful(tarutils.ExtractArchiveToFs(env.FileSystem(), path, env.FileSystem()))
103133
Expect(Must(vfs.FileExists(env.FileSystem(), "/ingress-nginx/Chart.yaml"))).To(BeTrue())
104134
})
135+
105136
})

api/ocm/extensions/download/handlers/helm/handler.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"io"
7+
"path/filepath"
78
"strings"
89

910
"github.com/mandelsoft/goutils/finalizer"
@@ -155,9 +156,8 @@ func download(p common.Printer, art oci.ArtifactAccess, path string, fs vfs.File
155156
return "", "", err
156157
}
157158
finalize.Close(provBlob)
158-
159-
prov = chart[:len(chart)-3] + "prov"
160-
if err := write(p, provBlob, path, fs); err != nil {
159+
prov = trimExtN(chart, 2) + ".prov"
160+
if err := write(p, provBlob, prov, fs); err != nil {
161161
return "", "", err
162162
}
163163
} else if !errors.Is(err, ErrNoMatchingLayer) { // Ignore if no provenance layer is found, because its optional.
@@ -167,6 +167,19 @@ func download(p common.Printer, art oci.ArtifactAccess, path string, fs vfs.File
167167
return chart, prov, nil
168168
}
169169

170+
// trimExtN trims the file extension from the path, at most n times.
171+
// This is useful for removing the ".tgz" or ".tar.gz" suffix from a file name.
172+
func trimExtN(path string, n int) string {
173+
for i := 0; i < n; i++ {
174+
trimmed := strings.TrimSuffix(path, filepath.Ext(path))
175+
if trimmed == path {
176+
break
177+
}
178+
path = trimmed
179+
}
180+
return path
181+
}
182+
170183
func findLayer(layers []ocispec.Descriptor, mediaType string) (*ocispec.Descriptor, error) {
171184
var candidates []*ocispec.Descriptor
172185

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package helm
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestAssureArchiveSuffix(t *testing.T) {
8+
tests := []struct {
9+
input string
10+
expected string
11+
}{
12+
{"chart", "chart.tgz"},
13+
{"chart.tgz", "chart.tgz"},
14+
{"chart.tar.gz", "chart.tar.gz"},
15+
{"archive.zip", "archive.zip.tgz"},
16+
}
17+
18+
for _, tt := range tests {
19+
result := AssureArchiveSuffix(tt.input)
20+
if result != tt.expected {
21+
t.Errorf("AssureArchiveSuffix(%q) = %q; want %q", tt.input, result, tt.expected)
22+
}
23+
}
24+
}
25+
26+
func TestTrimExtN(t *testing.T) {
27+
tests := []struct {
28+
input string
29+
n int
30+
expected string
31+
}{
32+
{"chart.tgz", 1, "chart"},
33+
{"chart.tar.gz", 1, "chart.tar"},
34+
{"chart.tar.gz", 2, "chart"},
35+
{"archive.zip", 1, "archive"},
36+
{"archive", 1, "archive"},
37+
}
38+
39+
for _, tt := range tests {
40+
result := trimExtN(tt.input, tt.n)
41+
if result != tt.expected {
42+
t.Errorf("trimExtN(%q, %d) = %q; want %q", tt.input, tt.n, result, tt.expected)
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)