diff --git a/README.md b/README.md index cf177aa4b..efd3718e7 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,30 @@ [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/helm/helm/badge)](https://scorecard.dev/viewer/?uri=github.com/helm/helm) +## PR + +Run a registry: +``` +docker run -d -p 5000:5000 --restart always --name registry docker.io/registry:3.0.0-rc.2 +``` + +Create a chart and values, push values to registry: +``` +helm create oci-values-demo +$ cat value-overides.yaml +--- +replicaCount: 3 +$ oras push localhost:5000/oci-values-demo-values:demo value-overides.yaml:application/vnd.cncf.helm.values.v1 +``` + +Install the chart, pulling values from OCI: +``` +make && ./bin/helm upgrade --install oci-values-demo-release oci-values-demo/ -f oci://localhost:5000/oci-values-demo-values:demo +``` + + + + Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources. Use Helm to: diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go index 2c3706b84..ad1c6ef69 100644 --- a/pkg/cli/values/options.go +++ b/pkg/cli/values/options.go @@ -131,7 +131,8 @@ func readFile(filePath string, p getter.Providers) ([]byte, error) { if err != nil { return os.ReadFile(filePath) } - data, err := g.Get(filePath, getter.WithURL(filePath)) + + data, err := g.Get(filePath, getter.WithURL(filePath), getter.WithTarget("values")) if err != nil { return nil, err } diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go index 5014784bc..5f5d360bf 100644 --- a/pkg/getter/getter.go +++ b/pkg/getter/getter.go @@ -47,6 +47,7 @@ type options struct { registryClient *registry.Client timeout time.Duration transport *http.Transport + target string } // Option allows specifying various settings configurable by the user for overriding the defaults @@ -143,6 +144,13 @@ func WithTransport(transport *http.Transport) Option { } } +// WithTransport sets the http.Transport to allow overwriting the HTTPGetter default. +func WithTarget(target string) Option { + return func(opts *options) { + opts.target = target + } +} + // Getter is an interface to support GET to the specified URL. type Getter interface { // Get file content by url string diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go index 2a611e13a..ed6d200fe 100644 --- a/pkg/getter/ocigetter.go +++ b/pkg/getter/ocigetter.go @@ -39,6 +39,7 @@ type OCIGetter struct { // Get performs a Get from repo.Getter and returns the body. func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { + g.opts.target = "chart" for _, opt := range options { opt(&g.opts) } @@ -76,10 +77,17 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) { return nil, err } - if requestingProv { - return bytes.NewBuffer(result.Prov.Data), nil + switch g.opts.target { + case "values": + return bytes.NewBuffer(result.Values.Data), nil + case "chart": + if requestingProv { + return bytes.NewBuffer(result.Prov.Data), nil + } + return bytes.NewBuffer(result.Chart.Data), nil } - return bytes.NewBuffer(result.Chart.Data), nil + + return nil, fmt.Errorf("unknown get target: %s", g.opts.target) } // NewOCIGetter constructs a valid http/https client as a Getter diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 01e5dff7b..b97df223c 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -399,6 +399,7 @@ type ( Chart *DescriptorPullSummaryWithMeta `json:"chart"` Prov *DescriptorPullSummary `json:"prov"` Ref string `json:"ref"` + Values *DescriptorPullSummary `json:"values"` } DescriptorPullSummary struct { @@ -436,24 +437,23 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { return nil, errors.New( "must specify at least one layer to pull (chart/prov)") } - memoryStore := memory.New() - allowedMediaTypes := []string{ - ocispec.MediaTypeImageManifest, - ConfigMediaType, - } - minNumDescriptors := 1 // 1 for the config - if operation.withChart { - minNumDescriptors++ - allowedMediaTypes = append(allowedMediaTypes, ChartLayerMediaType, LegacyChartLayerMediaType) - } - if operation.withProv { - if !operation.ignoreMissingProv { - minNumDescriptors++ - } - allowedMediaTypes = append(allowedMediaTypes, ProvLayerMediaType) - } - var descriptors, layers []ocispec.Descriptor + memoryStore := memory.New() + //allowedMediaTypes := []string{ + // ocispec.MediaTypeImageManifest, + // ConfigMediaType, + //} + //minNumDescriptors := 1 // 1 for the config + //if operation.withChart { + // minNumDescriptors++ + // allowedMediaTypes = append(allowedMediaTypes, ChartLayerMediaType, LegacyChartLayerMediaType) + //} + //if operation.withProv { + // if !operation.ignoreMissingProv { + // minNumDescriptors++ + // } + // allowedMediaTypes = append(allowedMediaTypes, ProvLayerMediaType) + //} repository, err := remote.NewRepository(parsedRef.String()) if err != nil { @@ -464,16 +464,17 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { ctx := context.Background() - sort.Strings(allowedMediaTypes) + //sort.Strings(allowedMediaTypes) var mu sync.Mutex + var layers []ocispec.Descriptor manifest, err := oras.Copy(ctx, repository, parsedRef.String(), memoryStore, "", oras.CopyOptions{ CopyGraphOptions: oras.CopyGraphOptions{ PreCopy: func(_ context.Context, desc ocispec.Descriptor) error { - mediaType := desc.MediaType - if i := sort.SearchStrings(allowedMediaTypes, mediaType); i >= len(allowedMediaTypes) || allowedMediaTypes[i] != mediaType { - return errors.Errorf("media type %q is not allowed, found in descriptor with digest: %q", mediaType, desc.Digest) - } + //mediaType := desc.MediaType + //if i := sort.SearchStrings(allowedMediaTypes, mediaType); i >= len(allowedMediaTypes) || allowedMediaTypes[i] != mediaType { + // return errors.Errorf("media type %q is not allowed, found in descriptor with digest: %q", mediaType, desc.Digest) + //} mu.Lock() layers = append(layers, desc) @@ -486,17 +487,19 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { return nil, err } + var descriptors []ocispec.Descriptor descriptors = append(descriptors, manifest) descriptors = append(descriptors, layers...) - numDescriptors := len(descriptors) - if numDescriptors < minNumDescriptors { - return nil, fmt.Errorf("manifest does not contain minimum number of descriptors (%d), descriptors found: %d", - minNumDescriptors, numDescriptors) + if manifest.MediaType != "application/vnd.oci.image.manifest.v1+json" { + return nil, fmt.Errorf("unexpected reference mediatype: %s", manifest.MediaType) } + var configDescriptor *ocispec.Descriptor var chartDescriptor *ocispec.Descriptor var provDescriptor *ocispec.Descriptor + var valuesDescriptor *ocispec.Descriptor + for _, descriptor := range descriptors { d := descriptor switch d.MediaType { @@ -506,11 +509,30 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { chartDescriptor = &d case ProvLayerMediaType: provDescriptor = &d + case ValuesMediaType: + valuesDescriptor = &d case LegacyChartLayerMediaType: chartDescriptor = &d fmt.Fprintf(c.out, "Warning: chart media type %s is deprecated\n", LegacyChartLayerMediaType) } } + + if valuesDescriptor != nil { + data, err := content.FetchAll(ctx, memoryStore, *valuesDescriptor) + if err != nil { + return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", valuesDescriptor.Digest, err) + } + + return &PullResult{ + Ref: parsedRef.String(), + Values: &DescriptorPullSummary{ + Digest: valuesDescriptor.Digest.String(), + Size: valuesDescriptor.Size, + Data: data, + }, + }, nil + } + if configDescriptor == nil { return nil, fmt.Errorf("could not load config with mediatype %s", ConfigMediaType) } @@ -536,9 +558,10 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { Digest: configDescriptor.Digest.String(), Size: configDescriptor.Size, }, - Chart: &DescriptorPullSummaryWithMeta{}, - Prov: &DescriptorPullSummary{}, - Ref: parsedRef.String(), + Chart: &DescriptorPullSummaryWithMeta{}, + Prov: &DescriptorPullSummary{}, + Ref: parsedRef.String(), + Values: nil, } result.Manifest.Data, err = content.FetchAll(ctx, memoryStore, manifest) diff --git a/pkg/registry/constants.go b/pkg/registry/constants.go index c455cf314..2478748b5 100644 --- a/pkg/registry/constants.go +++ b/pkg/registry/constants.go @@ -32,6 +32,9 @@ const ( // ProvLayerMediaType is the reserved media type for Helm chart provenance files ProvLayerMediaType = "application/vnd.cncf.helm.chart.provenance.v1.prov" + // ValuesMediaType is the reserved media type for the Helm chart values + ValuesMediaType = "application/vnd.cncf.helm.values.v1" + // LegacyChartLayerMediaType is the legacy reserved media type for Helm chart package content. LegacyChartLayerMediaType = "application/tar+gzip" )