pull: log resolved version and digest for repo/non-OCI pulls

When users run `helm pull` without a version, OCI pulls already print
“Pulled:” and “Digest:”. HTTP/repo pulls do not. Some providers (e.g.
Bitnami) resolve --repo pulls to `oci://` but skip the registry client
summary, so nothing is printed.

- Now we print a summary for `--repo` and direct HTTP pulls after
download.
- Keep direct oci:// behavior unchanged (avoid duplicate output).
- Mirror OCI format:
    ```
    Pulled: <chart>:<version>
    Digest: sha256:<digest>
    ```

For HTTP/repo, the digest is the archive SHA-256 (.tgz).
For OCI, the digest is the manifest digest (unchanged).

I tested with command like:

```sh
helm pull ingress-nginx --repo=https://kubernetes.github.io/ingress-nginx --version 4.10.1
helm pull oci://registry-1.docker.io/bitnamicharts/nginx --version '20.0.7'
helm/bin/helm pull redis --repo=https://charts.bitnami.com/bitnami --version='22.0.4'
```

Signed-off-by: Benoit Tigeot <benoit.tigeot@lifen.fr>
pull/31206/head
Benoit Tigeot 4 weeks ago
parent 934f761e08
commit 3ae2f48d02
No known key found for this signature in database
GPG Key ID: 8E6D4FC8AEBDA62C

@ -17,6 +17,7 @@ limitations under the License.
package action
import (
"crypto/sha256"
"fmt"
"os"
"path/filepath"
@ -138,6 +139,37 @@ func (p *Pull) Run(chartRef string) (string, error) {
return out.String(), err
}
// Print a pull summary for repo mode and non-OCI downloads.
// Some repos (e.g., Bitnami) resolve --repo pulls to oci:// refs, but that
// path does not go through the registry client's summary printer, so we emit
// a consistent summary here. We mirror the OCI output format:
//
// Pulled: <chart>:<version>
// Digest: sha256:<digest>
//
// For HTTP/repo pulls, the digest is the SHA-256 of the downloaded .tgz archive.
// For direct OCI pulls, the registry client already prints "Pulled:" and
// "Digest:" (manifest digest), so we do not print here to avoid duplicates.
if p.RepoURL != "" || !registry.IsOCI(downloadSourceRef) {
base := strings.TrimSuffix(filepath.Base(saved), ".tgz")
chart := base
ver := ""
if i := strings.LastIndex(base, "-"); i > 0 {
chart = base[:i]
ver = base[i+1:]
}
if ver != "" {
fmt.Fprintf(&out, "Pulled: %s:%s\n", chart, ver)
} else {
fmt.Fprintf(&out, "Pulled: %s\n", chart)
}
if f, err := os.ReadFile(saved); err == nil {
sum := sha256.Sum256(f)
fmt.Fprintf(&out, "Digest: sha256:%x\n", sum)
}
}
if p.Verify {
for name := range v.SignedBy.Identities {
fmt.Fprintf(&out, "Signed by: %v\n", name)

@ -0,0 +1,129 @@
/*
Copyright The Helm Authors.
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 action
import (
"crypto/sha256"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/pkg/cli"
)
// helm pull testchart --repo=http://127.0.0.1:<port> --version 1.2.3
func TestPull_PrintsSummary_ForHTTPRepo(t *testing.T) {
t.Parallel()
// Minimal chart payload; verification is off so a plain byte buffer is fine.
chartBytes := []byte("dummy-chart-content")
sum := sha256.Sum256(chartBytes)
wantDigest := fmt.Sprintf("sha256:%x", sum)
// Serve a valid index.yaml and the chart archive.
mux := http.NewServeMux()
mux.HandleFunc("/index.yaml", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "text/yaml")
// Valid index entry requires both name and version.
fmt.Fprintf(w, `apiVersion: v1
entries:
testchart:
- name: testchart
version: 1.2.3
urls:
- testchart-1.2.3.tgz
created: "2020-01-01T00:00:00Z"
`)
})
mux.HandleFunc("/testchart-1.2.3.tgz", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/gzip")
_, _ = w.Write(chartBytes)
})
srv := httptest.NewServer(mux)
defer srv.Close()
// Isolate Helm env/cache to temp dirs for deterministic tests.
settings := cli.New()
settings.RepositoryCache = t.TempDir()
settings.RepositoryConfig = filepath.Join(t.TempDir(), "repositories.yaml")
settings.ContentCache = t.TempDir()
cfg := &Configuration{} // minimal config; no K8s or registry for HTTP path
p := NewPull(WithConfig(cfg))
p.Settings = settings
p.DestDir = t.TempDir()
p.RepoURL = srv.URL
p.Version = "1.2.3"
out, err := p.Run("testchart")
require.NoError(t, err, "Pull.Run() should succeed. Output:\n%s", out)
assert.Contains(t, out, "Pulled: testchart:1.2.3", "expected Pulled summary in output")
assert.Contains(t, out, "Digest: "+wantDigest, "expected archive digest in output")
// Ensure the chart file was saved.
_, statErr := os.Stat(filepath.Join(p.DestDir, "testchart-1.2.3.tgz"))
require.NoError(t, statErr, "expected chart archive to be saved")
}
// helm pull http://127.0.0.1:<port>/directchart-9.9.9.tgz
func TestPull_PrintsSummary_ForDirectHTTPURL(t *testing.T) {
t.Parallel()
chartBytes := []byte("another-dummy-chart")
sum := sha256.Sum256(chartBytes)
wantDigest := fmt.Sprintf("sha256:%x", sum)
mux := http.NewServeMux()
mux.HandleFunc("/directchart-9.9.9.tgz", func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/gzip")
_, _ = w.Write(chartBytes)
})
srv := httptest.NewServer(mux)
defer srv.Close()
settings := cli.New()
settings.RepositoryCache = t.TempDir()
settings.RepositoryConfig = filepath.Join(t.TempDir(), "repositories.yaml")
settings.ContentCache = t.TempDir()
cfg := &Configuration{}
p := NewPull(WithConfig(cfg))
p.Settings = settings
p.DestDir = t.TempDir()
// Direct HTTP URL (absolute URL). Version is ignored for absolute URLs.
chartURL := srv.URL + "/directchart-9.9.9.tgz"
out, err := p.Run(chartURL)
require.NoError(t, err, "Pull.Run() should succeed. Output:\n%s", out)
// Output should reflect name-version.tgz from the URL.
assert.Contains(t, out, "Pulled: directchart:9.9.9", "expected Pulled summary in output")
assert.Contains(t, out, "Digest: "+wantDigest, "expected archive digest in output")
// Ensure the chart file was saved.
_, statErr := os.Stat(filepath.Join(p.DestDir, "directchart-9.9.9.tgz"))
require.NoError(t, statErr, "expected chart archive to be saved")
}
Loading…
Cancel
Save