Aleksei Sviridkin 5 days ago committed by GitHub
commit fa318d9a64
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -38,6 +38,7 @@ type Push struct {
insecureSkipTLSVerify bool
plainHTTP bool
out io.Writer
subject string
}
// PushOpt is a type of function that sets options for a push action.
@ -80,6 +81,14 @@ func WithPushOptWriter(out io.Writer) PushOpt {
}
}
// WithSubject sets the subject digest for OCI Referrers API.
// When set, the pushed chart will be associated with the specified image digest.
func WithSubject(subject string) PushOpt {
return func(p *Push) {
p.subject = subject
}
}
// NewPushWithOpts creates a new push, with configuration options.
func NewPushWithOpts(opts ...PushOpt) *Push {
p := &Push{}
@ -100,6 +109,7 @@ func (p *Push) Run(chartRef string, remote string) (string, error) {
pusher.WithTLSClientConfig(p.certFile, p.keyFile, p.caFile),
pusher.WithInsecureSkipTLSVerify(p.insecureSkipTLSVerify),
pusher.WithPlainHTTP(p.plainHTTP),
pusher.WithSubject(p.subject),
},
}

@ -42,6 +42,7 @@ type registryPushOptions struct {
plainHTTP bool
password string
username string
subject string
}
func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
@ -84,7 +85,8 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
action.WithTLSClientConfig(o.certFile, o.keyFile, o.caFile),
action.WithInsecureSkipTLSVerify(o.insecureSkipTLSVerify),
action.WithPlainHTTP(o.plainHTTP),
action.WithPushOptWriter(out))
action.WithPushOptWriter(out),
action.WithSubject(o.subject))
client.Settings = settings
output, err := client.Run(chartRef, remote)
if err != nil {
@ -103,6 +105,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload")
f.StringVar(&o.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&o.password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&o.subject, "subject", "", "associate chart with a container image via OCI Referrers API (digest format: sha256:...)")
return cmd
}

@ -26,6 +26,9 @@ import (
"strings"
"time"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"helm.sh/helm/v4/internal/tlsutil"
"helm.sh/helm/v4/pkg/chart/v2/loader"
"helm.sh/helm/v4/pkg/registry"
@ -93,6 +96,14 @@ func (pusher *OCIPusher) push(chartRef, href string) error {
chartArchiveFileCreatedTime := stat.ModTime()
pushOpts = append(pushOpts, registry.PushOptCreationTime(chartArchiveFileCreatedTime.Format(time.RFC3339)))
// Add subject for OCI Referrers API if specified
if pusher.opts.subject != "" {
subjectDesc := &ocispec.Descriptor{
Digest: digest.Digest(pusher.opts.subject),
}
pushOpts = append(pushOpts, registry.PushOptSubject(subjectDesc))
}
_, err = client.Push(chartBytes, ref, pushOpts...)
return err
}

@ -34,6 +34,7 @@ type options struct {
caFile string
insecureSkipTLSVerify bool
plainHTTP bool
subject string
}
// Option allows specifying various settings configurable by the user for overriding the defaults
@ -69,6 +70,14 @@ func WithPlainHTTP(plainHTTP bool) Option {
}
}
// WithSubject sets the subject digest for OCI Referrers API.
// When set, the pushed chart will be associated with the specified image digest.
func WithSubject(subject string) Option {
return func(opts *options) {
opts.subject = subject
}
}
// Pusher is an interface to support upload to the specified URL.
type Pusher interface {
// Push file content by url string

@ -577,9 +577,11 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
}
// Use generic client for the pull operation
// Pass ChartArtifactType to enable selection from OCI Image Index
genericClient := c.Generic()
genericResult, err := genericClient.PullGeneric(ref, GenericPullOptions{
AllowedMediaTypes: allowedMediaTypes,
ArtifactType: ChartArtifactType,
})
if err != nil {
return nil, err
@ -637,6 +639,7 @@ type (
provData []byte
strictMode bool
creationTime string
subject *ocispec.Descriptor
}
)
@ -701,7 +704,7 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
ociAnnotations := generateOCIAnnotations(meta, operation.creationTime)
manifestDescriptor, err := c.tagManifest(ctx, memoryStore, configDescriptor,
layers, ociAnnotations, parsedRef)
layers, ociAnnotations, parsedRef, operation.subject)
if err != nil {
return nil, err
}
@ -773,6 +776,13 @@ func PushOptCreationTime(creationTime string) PushOption {
}
}
// PushOptSubject returns a function that sets the subject for Referrers API
func PushOptSubject(subject *ocispec.Descriptor) PushOption {
return func(operation *pushOperation) {
operation.subject = subject
}
}
// Tags provides a sorted list all semver compliant tags for a given repository
func (c *Client) Tags(ref string) ([]string, error) {
parsedReference, err := registry.ParseReference(ref)
@ -908,13 +918,16 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (string, *ur
// 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,
ociAnnotations map[string]string, parsedRef reference) (ocispec.Descriptor, error) {
ociAnnotations map[string]string, parsedRef reference,
subject *ocispec.Descriptor) (ocispec.Descriptor, error) {
manifest := ocispec.Manifest{
Versioned: specs.Versioned{SchemaVersion: 2},
Config: configDescriptor,
Layers: layers,
Annotations: ociAnnotations,
Versioned: specs.Versioned{SchemaVersion: 2},
ArtifactType: ConfigMediaType,
Config: configDescriptor,
Layers: layers,
Annotations: ociAnnotations,
Subject: subject,
}
manifestData, err := json.Marshal(manifest)

@ -45,7 +45,7 @@ func TestTagManifestTransformsReferences(t *testing.T) {
parsedRef, err := newReference(refWithPlus)
require.NoError(t, err)
desc, err := client.tagManifest(ctx, memStore, configDesc, layers, nil, parsedRef)
desc, err := client.tagManifest(ctx, memStore, configDesc, layers, nil, parsedRef, nil)
require.NoError(t, err)
transformedDesc, err := memStore.Resolve(ctx, expectedRef)

@ -34,4 +34,8 @@ const (
// LegacyChartLayerMediaType is the legacy reserved media type for Helm chart package content.
LegacyChartLayerMediaType = "application/tar+gzip"
// ChartArtifactType is the artifact type for Helm charts in OCI v1.1+ Image Index.
// This is used to identify Helm chart manifests within a multi-artifact index.
ChartArtifactType = "application/vnd.cncf.helm.config.v1+json"
)

@ -18,6 +18,8 @@ package registry
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"slices"
@ -56,6 +58,10 @@ type GenericPullOptions struct {
SkipMediaTypes []string
// Custom PreCopy function for filtering
PreCopy func(context.Context, ocispec.Descriptor) error
// ArtifactType to select from OCI Image Index (empty means no filtering).
// When pulling from an Image Index containing multiple manifests,
// this field is used to select the manifest with matching artifactType.
ArtifactType string
}
// GenericPullResult contains the result of a generic pull operation
@ -83,6 +89,67 @@ func NewGenericClient(client *Client) *GenericClient {
}
}
// resolveFromIndex selects a manifest from an OCI Image Index by artifactType.
// It returns the descriptor of the matching manifest, or an error if no match is found.
// If no manifests have artifactType set, it falls back to checking config.mediaType
// of each manifest to find one that matches the expected artifact type.
func (c *GenericClient) resolveFromIndex(ctx context.Context, repo *remote.Repository, indexDesc ocispec.Descriptor, artifactType string) (ocispec.Descriptor, error) {
// Fetch the index manifest
indexData, err := content.FetchAll(ctx, repo, indexDesc)
if err != nil {
return ocispec.Descriptor{}, fmt.Errorf("unable to fetch image index: %w", err)
}
var index ocispec.Index
if err := json.Unmarshal(indexData, &index); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("unable to parse image index: %w", err)
}
// First pass: look for explicit artifactType match
var availableTypes []string
var candidatesWithoutArtifactType []ocispec.Descriptor
for _, manifest := range index.Manifests {
if manifest.ArtifactType == artifactType {
return manifest, nil
}
if manifest.ArtifactType != "" {
availableTypes = append(availableTypes, manifest.ArtifactType)
} else {
// Collect manifests without artifactType for fallback check
candidatesWithoutArtifactType = append(candidatesWithoutArtifactType, manifest)
}
}
// Second pass: if no artifactType matches found, check config.mediaType of each manifest
// This handles the case where artifacts are published without explicit artifactType
for _, candidate := range candidatesWithoutArtifactType {
// Skip manifests with platform (likely container images)
if candidate.Platform != nil {
continue
}
// Fetch the manifest to check its config.mediaType
manifestData, err := content.FetchAll(ctx, repo, candidate)
if err != nil {
continue // Skip manifests we can't fetch
}
var manifest ocispec.Manifest
if err := json.Unmarshal(manifestData, &manifest); err != nil {
continue // Skip malformed manifests
}
// Check if config.mediaType matches our expected artifact type
if manifest.Config.MediaType == artifactType {
return candidate, nil
}
}
return ocispec.Descriptor{}, fmt.Errorf(
"no manifest with artifactType %q found in image index; available types: %v",
artifactType, availableTypes)
}
// PullGeneric performs a generic OCI pull without artifact-specific assumptions
func (c *GenericClient) PullGeneric(ref string, options GenericPullOptions) (*GenericPullResult, error) {
parsedRef, err := newReference(ref)
@ -103,6 +170,26 @@ func (c *GenericClient) PullGeneric(ref string, options GenericPullOptions) (*Ge
ctx := context.Background()
// Resolve the reference to get the manifest descriptor
// This allows us to detect Image Index and select the appropriate manifest
pullRef := parsedRef.String()
if options.ArtifactType != "" {
// Try to resolve the reference to check if it's an Image Index.
// If resolution fails, continue with normal pull - the error will
// manifest during oras.Copy() if there's a real problem.
resolvedDesc, err := repository.Resolve(ctx, pullRef)
if err == nil && resolvedDesc.MediaType == ocispec.MediaTypeImageIndex {
// Select the manifest with matching artifactType from the index
selectedManifest, err := c.resolveFromIndex(ctx, repository, resolvedDesc, options.ArtifactType)
if err != nil {
return nil, err
}
// Use the selected manifest's digest for pulling
pullRef = selectedManifest.Digest.String()
}
// If Resolve() failed or it's not an Image Index, continue with original pullRef
}
// Prepare allowed media types for filtering
var allowedMediaTypes []string
if len(options.AllowedMediaTypes) > 0 {
@ -112,7 +199,7 @@ func (c *GenericClient) PullGeneric(ref string, options GenericPullOptions) (*Ge
}
var mu sync.Mutex
manifest, err := oras.Copy(ctx, repository, parsedRef.String(), memoryStore, "", oras.CopyOptions{
manifest, err := oras.Copy(ctx, repository, pullRef, memoryStore, "", oras.CopyOptions{
CopyGraphOptions: oras.CopyGraphOptions{
PreCopy: func(ctx context.Context, desc ocispec.Descriptor) error {
// Apply a custom PreCopy function if provided

@ -0,0 +1,295 @@
/*
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 registry
import (
"crypto/sha256"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// computeDigest calculates the SHA256 digest of data.
func computeDigest(data []byte) digest.Digest {
h := sha256.Sum256(data)
return digest.NewDigestFromBytes(digest.SHA256, h[:])
}
func TestPullFromImageIndex(t *testing.T) {
// Build chart config and layer with real digests
chartConfigData := []byte(`{"name":"testchart","version":"1.0.0","apiVersion":"v2"}`)
chartConfigDigest := computeDigest(chartConfigData)
// Minimal valid gzipped content
chartLayerData := []byte{0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
chartLayerDigest := computeDigest(chartLayerData)
// Create chart manifest with real digests
chartManifest := ocispec.Manifest{
MediaType: ocispec.MediaTypeImageManifest,
Config: ocispec.Descriptor{
MediaType: ConfigMediaType,
Digest: chartConfigDigest,
Size: int64(len(chartConfigData)),
},
Layers: []ocispec.Descriptor{
{
MediaType: ChartLayerMediaType,
Digest: chartLayerDigest,
Size: int64(len(chartLayerData)),
},
},
}
chartManifestBytes, _ := json.Marshal(chartManifest)
chartManifestDigest := computeDigest(chartManifestBytes)
// Container manifest (we won't actually serve the blobs, just need valid structure)
containerManifestDigest := digest.Digest("sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
// Image Index containing both chart and container manifests
imageIndex := ocispec.Index{
MediaType: ocispec.MediaTypeImageIndex,
Manifests: []ocispec.Descriptor{
{
MediaType: ocispec.MediaTypeImageManifest,
Digest: containerManifestDigest,
Size: 500,
ArtifactType: "application/vnd.oci.image.config.v1+json",
Platform: &ocispec.Platform{
Architecture: "amd64",
OS: "linux",
},
},
{
MediaType: ocispec.MediaTypeImageManifest,
Digest: chartManifestDigest,
Size: int64(len(chartManifestBytes)),
ArtifactType: ChartArtifactType,
},
},
}
imageIndexBytes, _ := json.Marshal(imageIndex)
imageIndexDigest := computeDigest(imageIndexBytes)
// Create test server that serves the Image Index
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case path == "/v2/":
w.WriteHeader(http.StatusOK)
case path == "/v2/testrepo/multichart/manifests/1.0.0":
w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
w.Header().Set("Docker-Content-Digest", imageIndexDigest.String())
w.WriteHeader(http.StatusOK)
_, _ = w.Write(imageIndexBytes)
// Serve Image Index by digest (for resolveFromIndex FetchAll)
case path == fmt.Sprintf("/v2/testrepo/multichart/blobs/%s", imageIndexDigest.String()),
path == fmt.Sprintf("/v2/testrepo/multichart/manifests/%s", imageIndexDigest.String()):
w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
w.Header().Set("Docker-Content-Digest", imageIndexDigest.String())
w.WriteHeader(http.StatusOK)
_, _ = w.Write(imageIndexBytes)
// Serve chart manifest by digest
case path == fmt.Sprintf("/v2/testrepo/multichart/manifests/%s", chartManifestDigest.String()):
w.Header().Set("Content-Type", ocispec.MediaTypeImageManifest)
w.Header().Set("Docker-Content-Digest", chartManifestDigest.String())
w.WriteHeader(http.StatusOK)
_, _ = w.Write(chartManifestBytes)
// Serve chart config blob
case strings.Contains(path, chartConfigDigest.Encoded()):
w.Header().Set("Content-Type", ConfigMediaType)
w.Header().Set("Docker-Content-Digest", chartConfigDigest.String())
w.WriteHeader(http.StatusOK)
_, _ = w.Write(chartConfigData)
// Serve chart layer blob
case strings.Contains(path, chartLayerDigest.Encoded()):
w.Header().Set("Content-Type", ChartLayerMediaType)
w.Header().Set("Docker-Content-Digest", chartLayerDigest.String())
w.WriteHeader(http.StatusOK)
_, _ = w.Write(chartLayerData)
default:
t.Logf("404 for path: %s", path)
w.WriteHeader(http.StatusNotFound)
}
}))
defer s.Close()
u, _ := url.Parse(s.URL)
host := fmt.Sprintf("localhost:%s", u.Port())
ref := fmt.Sprintf("%s/testrepo/multichart:1.0.0", host)
client, err := NewClient(ClientOptPlainHTTP())
require.NoError(t, err)
// Pull should automatically select the chart manifest from the index
result, err := client.Pull(ref)
require.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, "testchart", result.Chart.Meta.Name)
assert.Equal(t, "1.0.0", result.Chart.Meta.Version)
}
func TestPullFromImageIndexNoMatchingArtifactType(t *testing.T) {
// Image Index with only container images, no Helm chart
imageIndex := ocispec.Index{
MediaType: ocispec.MediaTypeImageIndex,
Manifests: []ocispec.Descriptor{
{
MediaType: ocispec.MediaTypeImageManifest,
Digest: "sha256:2222222222222222222222222222222222222222222222222222222222222222",
Size: 500,
ArtifactType: "application/vnd.oci.image.config.v1+json",
Platform: &ocispec.Platform{
Architecture: "amd64",
OS: "linux",
},
},
{
MediaType: ocispec.MediaTypeImageManifest,
Digest: "sha256:3333333333333333333333333333333333333333333333333333333333333333",
Size: 500,
ArtifactType: "application/vnd.oci.image.config.v1+json",
Platform: &ocispec.Platform{
Architecture: "arm64",
OS: "linux",
},
},
},
}
imageIndexBytes, _ := json.Marshal(imageIndex)
imageIndexDigest := computeDigest(imageIndexBytes)
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case path == "/v2/":
w.WriteHeader(http.StatusOK)
case path == "/v2/testrepo/nohelm/manifests/1.0.0":
w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
w.Header().Set("Docker-Content-Digest", imageIndexDigest.String())
w.WriteHeader(http.StatusOK)
_, _ = w.Write(imageIndexBytes)
// Serve Image Index by digest
case path == fmt.Sprintf("/v2/testrepo/nohelm/blobs/%s", imageIndexDigest.String()),
path == fmt.Sprintf("/v2/testrepo/nohelm/manifests/%s", imageIndexDigest.String()):
w.Header().Set("Content-Type", ocispec.MediaTypeImageIndex)
w.Header().Set("Docker-Content-Digest", imageIndexDigest.String())
w.WriteHeader(http.StatusOK)
_, _ = w.Write(imageIndexBytes)
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer s.Close()
u, _ := url.Parse(s.URL)
host := fmt.Sprintf("localhost:%s", u.Port())
ref := fmt.Sprintf("%s/testrepo/nohelm:1.0.0", host)
client, err := NewClient(ClientOptPlainHTTP())
require.NoError(t, err)
_, err = client.Pull(ref)
require.Error(t, err)
assert.Contains(t, err.Error(), "no manifest with artifactType")
assert.Contains(t, err.Error(), ChartArtifactType)
}
func TestPullSingleManifestNotIndex(t *testing.T) {
// Regular manifest (not an Index) should work as before
// Build config and layer with real digests
configData := []byte(`{"name":"singlechart","version":"1.0.0","apiVersion":"v2"}`)
configDigest := computeDigest(configData)
layerData := []byte{0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
layerDigest := computeDigest(layerData)
manifest := ocispec.Manifest{
MediaType: ocispec.MediaTypeImageManifest,
Config: ocispec.Descriptor{
MediaType: ConfigMediaType,
Digest: configDigest,
Size: int64(len(configData)),
},
Layers: []ocispec.Descriptor{
{
MediaType: ChartLayerMediaType,
Digest: layerDigest,
Size: int64(len(layerData)),
},
},
}
manifestBytes, _ := json.Marshal(manifest)
manifestDigest := computeDigest(manifestBytes)
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
switch {
case path == "/v2/":
w.WriteHeader(http.StatusOK)
case path == "/v2/testrepo/singlechart/manifests/1.0.0":
w.Header().Set("Content-Type", ocispec.MediaTypeImageManifest)
w.Header().Set("Docker-Content-Digest", manifestDigest.String())
w.WriteHeader(http.StatusOK)
_, _ = w.Write(manifestBytes)
case strings.Contains(path, configDigest.Encoded()):
w.Header().Set("Content-Type", ConfigMediaType)
w.Header().Set("Docker-Content-Digest", configDigest.String())
w.WriteHeader(http.StatusOK)
_, _ = w.Write(configData)
case strings.Contains(path, layerDigest.Encoded()):
w.Header().Set("Content-Type", ChartLayerMediaType)
w.Header().Set("Docker-Content-Digest", layerDigest.String())
w.WriteHeader(http.StatusOK)
_, _ = w.Write(layerData)
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer s.Close()
u, _ := url.Parse(s.URL)
host := fmt.Sprintf("localhost:%s", u.Port())
ref := fmt.Sprintf("%s/testrepo/singlechart:1.0.0", host)
client, err := NewClient(ClientOptPlainHTTP())
require.NoError(t, err)
result, err := client.Pull(ref)
require.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, "singlechart", result.Chart.Meta.Name)
}

@ -57,6 +57,7 @@ func (c *Client) PullPlugin(ref string, pluginName string, options ...PluginPull
}
// Use generic client for the pull operation with artifact type filtering
// Pass PluginArtifactType to enable selection from OCI Image Index
genericClient := c.Generic()
genericResult, err := genericClient.PullGeneric(ref, GenericPullOptions{
// Allow manifests and all layer types - we'll validate artifact type after download
@ -65,6 +66,7 @@ func (c *Client) PullPlugin(ref string, pluginName string, options ...PluginPull
"application/vnd.oci.image.layer.v1.tar",
"application/vnd.oci.image.layer.v1.tar+gzip",
},
ArtifactType: PluginArtifactType,
})
if err != nil {
return nil, err

@ -278,12 +278,12 @@ func testPush(suite *TestRegistry) {
suite.Equal(ref, result.Ref)
suite.Equal(meta.Name, result.Chart.Meta.Name)
suite.Equal(meta.Version, result.Chart.Meta.Version)
suite.Equal(int64(742), result.Manifest.Size)
suite.Equal(int64(800), result.Manifest.Size)
suite.Equal(int64(99), result.Config.Size)
suite.Equal(int64(973), result.Chart.Size)
suite.Equal(int64(695), result.Prov.Size)
suite.Equal(
"sha256:fbbade96da6050f68f94f122881e3b80051a18f13ab5f4081868dd494538f5c2",
"sha256:bc20397d31b1236b50d506e960b7ea81137712a88d084d3bddeb18a386797af9",
result.Manifest.Digest)
suite.Equal(
"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580",
@ -351,12 +351,12 @@ func testPull(suite *TestRegistry) {
suite.Equal(ref, result.Ref)
suite.Equal(meta.Name, result.Chart.Meta.Name)
suite.Equal(meta.Version, result.Chart.Meta.Version)
suite.Equal(int64(742), result.Manifest.Size)
suite.Equal(int64(800), result.Manifest.Size)
suite.Equal(int64(99), result.Config.Size)
suite.Equal(int64(973), result.Chart.Size)
suite.Equal(int64(695), result.Prov.Size)
suite.Equal(
"sha256:fbbade96da6050f68f94f122881e3b80051a18f13ab5f4081868dd494538f5c2",
"sha256:bc20397d31b1236b50d506e960b7ea81137712a88d084d3bddeb18a386797af9",
result.Manifest.Digest)
suite.Equal(
"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580",
@ -367,7 +367,7 @@ func testPull(suite *TestRegistry) {
suite.Equal(
"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256",
result.Prov.Digest)
suite.Equal("{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580\",\"size\":99},\"layers\":[{\"mediaType\":\"application/vnd.cncf.helm.chart.provenance.v1.prov\",\"digest\":\"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256\",\"size\":695},{\"mediaType\":\"application/vnd.cncf.helm.chart.content.v1.tar+gzip\",\"digest\":\"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\",\"size\":973}],\"annotations\":{\"org.opencontainers.image.created\":\"1977-09-02T22:04:05Z\",\"org.opencontainers.image.description\":\"A Helm chart for Kubernetes\",\"org.opencontainers.image.title\":\"signtest\",\"org.opencontainers.image.version\":\"0.1.0\"}}",
suite.Equal("{\"schemaVersion\":2,\"artifactType\":\"application/vnd.cncf.helm.config.v1+json\",\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580\",\"size\":99},\"layers\":[{\"mediaType\":\"application/vnd.cncf.helm.chart.provenance.v1.prov\",\"digest\":\"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256\",\"size\":695},{\"mediaType\":\"application/vnd.cncf.helm.chart.content.v1.tar+gzip\",\"digest\":\"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\",\"size\":973}],\"annotations\":{\"org.opencontainers.image.created\":\"1977-09-02T22:04:05Z\",\"org.opencontainers.image.description\":\"A Helm chart for Kubernetes\",\"org.opencontainers.image.title\":\"signtest\",\"org.opencontainers.image.version\":\"0.1.0\"}}",
string(result.Manifest.Data))
suite.Equal("{\"name\":\"signtest\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\",\"apiVersion\":\"v1\"}",
string(result.Config.Data))

Loading…
Cancel
Save