fix: Display oci annotations or created date if annotation exists in helm sho

Fixes helm/helm#30787

Signed-off-by: Arnav Nagzirkar <113314200+arnavnagzirkar@users.noreply.github.com>
pull/32198/head
Arnav Nagzirkar 1 month ago
parent 609e1ca72c
commit 544e3ba3d4

@ -61,7 +61,10 @@ type Show struct {
Devel bool
OutputFormat ShowOutputFormat
JSONPathTemplate string
chart *chart.Chart // for testing
// OCIRef is the original OCI reference (without oci:// scheme) used to fetch
// manifest annotations when showing an OCI chart. Leave empty for non-OCI charts.
OCIRef string
chart *chart.Chart // for testing
}
// NewShow creates a new Show object with the given configuration.
@ -96,6 +99,16 @@ func (s *Show) Run(chartpath string) (string, error) {
var out strings.Builder
if s.OutputFormat == ShowChart || s.OutputFormat == ShowAll {
fmt.Fprintf(&out, "%s\n", cf)
if s.OCIRef != "" && s.registryClient != nil {
annotations, err := s.registryClient.GetManifestAnnotations(s.OCIRef)
if err == nil && len(annotations) > 0 {
annotationsYAML, marshalErr := yaml.Marshal(annotations)
if marshalErr == nil {
fmt.Fprintln(&out, "---")
fmt.Fprintf(&out, "%s\n", annotationsYAML)
}
}
}
}
if (s.OutputFormat == ShowValues || s.OutputFormat == ShowAll) && s.chart.Values != nil {

@ -17,10 +17,15 @@ limitations under the License.
package action
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"helm.sh/helm/v4/pkg/chart/common"
chart "helm.sh/helm/v4/pkg/chart/v2"
@ -180,3 +185,72 @@ func TestShowSetRegistryClient(t *testing.T) {
client.SetRegistryClient(registryClient)
assert.Equal(t, registryClient, client.registryClient)
}
func TestShowOCIAnnotations(t *testing.T) {
const manifestJSON = `{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.cncf.helm.config.v1+json",
"digest": "sha256:abc123",
"size": 100
},
"layers": [],
"annotations": {
"org.opencontainers.image.created": "2025-04-11T20:12:25Z"
}
}`
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/v2/" {
w.WriteHeader(http.StatusOK)
return
}
if strings.Contains(r.URL.Path, "/manifests/") {
w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(manifestJSON))
return
}
http.NotFound(w, r)
}))
defer srv.Close()
host := strings.TrimPrefix(srv.URL, "http://")
registryClient, err := registry.NewClient(
registry.ClientOptWriter(io.Discard),
registry.ClientOptPlainHTTP(),
)
require.NoError(t, err)
config := actionConfigFixture(t)
client := NewShow(ShowChart, config)
client.SetRegistryClient(registryClient)
client.OCIRef = host + "/test/chart:1.0.0"
client.chart = &chart.Chart{
Metadata: &chart.Metadata{Name: "test-chart", Version: "1.0.0"},
}
output, err := client.Run("")
require.NoError(t, err)
assert.Contains(t, output, "---")
assert.Contains(t, output, "org.opencontainers.image.created")
assert.Contains(t, output, "2025-04-11T20:12:25Z")
}
func TestShowOCIAnnotationsNoRegistry(t *testing.T) {
config := actionConfigFixture(t)
client := NewShow(ShowChart, config)
// OCIRef set but no registry client - should not fail, just omit annotations
client.OCIRef = "host/test/chart:1.0.0"
client.chart = &chart.Chart{
Metadata: &chart.Metadata{Name: "test-chart", Version: "1.0.0"},
}
output, err := client.Run("")
require.NoError(t, err)
// No annotations section, no "---" separator
assert.NotContains(t, output, "---")
}

@ -21,11 +21,14 @@ import (
"io"
"log"
"log/slog"
"path"
"strings"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/registry"
)
const showDesc = `
@ -218,6 +221,14 @@ func runShow(args []string, client *action.Show) (string, error) {
client.Version = ">0.0.0-0"
}
if registry.IsOCI(args[0]) {
ref := strings.TrimPrefix(args[0], registry.OCIScheme+"://")
if client.Version != "" && !strings.Contains(path.Base(ref), ":") {
ref = fmt.Sprintf("%s:%s", ref, client.Version)
}
client.OCIRef = ref
}
cp, err := client.LocateChart(args[0], settings)
if err != nil {
return "", err

@ -909,6 +909,36 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (string, *ur
return "", u, err
}
// GetManifestAnnotations fetches the OCI manifest annotations for the given reference.
// It returns nil, nil when no annotations are present. The caller should treat errors as
// non-fatal and proceed without annotations.
func (c *Client) GetManifestAnnotations(ref string) (map[string]string, error) {
parsedRef, err := newReference(ref)
if err != nil {
return nil, err
}
remoteRepository, err := remote.NewRepository(parsedRef.String())
if err != nil {
return nil, err
}
remoteRepository.PlainHTTP = c.plainHTTP
remoteRepository.Client = c.authorizer
ctx := context.Background()
_, rc, err := remoteRepository.FetchReference(ctx, parsedRef.String())
if err != nil {
return nil, err
}
defer rc.Close()
var manifest ocispec.Manifest
if err := json.NewDecoder(rc).Decode(&manifest); err != nil {
return nil, err
}
return manifest.Annotations, nil
}
// tagManifest prepares and tags a manifest in memory storage
func (c *Client) tagManifest(ctx context.Context, memoryStore *memory.Store,
configDescriptor ocispec.Descriptor, layers []ocispec.Descriptor,

Loading…
Cancel
Save