feat: Support OCI values

Signed-off-by: George Jenkins <gvjenkins@gmail.com>
pull/17084/head
George Jenkins 8 months ago
parent 841f4b1256
commit 3dfbf4db8b

@ -6,6 +6,30 @@
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3131/badge)](https://bestpractices.coreinfrastructure.org/projects/3131) [![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) [![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. Helm is a tool for managing Charts. Charts are packages of pre-configured Kubernetes resources.
Use Helm to: Use Helm to:

@ -131,7 +131,8 @@ func readFile(filePath string, p getter.Providers) ([]byte, error) {
if err != nil { if err != nil {
return os.ReadFile(filePath) 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 { if err != nil {
return nil, err return nil, err
} }

@ -47,6 +47,7 @@ type options struct {
registryClient *registry.Client registryClient *registry.Client
timeout time.Duration timeout time.Duration
transport *http.Transport transport *http.Transport
target string
} }
// Option allows specifying various settings configurable by the user for overriding the defaults // 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. // Getter is an interface to support GET to the specified URL.
type Getter interface { type Getter interface {
// Get file content by url string // Get file content by url string

@ -39,6 +39,7 @@ type OCIGetter struct {
// Get performs a Get from repo.Getter and returns the body. // Get performs a Get from repo.Getter and returns the body.
func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) {
g.opts.target = "chart"
for _, opt := range options { for _, opt := range options {
opt(&g.opts) opt(&g.opts)
} }
@ -76,12 +77,19 @@ func (g *OCIGetter) get(href string) (*bytes.Buffer, error) {
return nil, err return nil, err
} }
switch g.opts.target {
case "values":
return bytes.NewBuffer(result.Values.Data), nil
case "chart":
if requestingProv { if requestingProv {
return bytes.NewBuffer(result.Prov.Data), nil 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 // NewOCIGetter constructs a valid http/https client as a Getter
func NewOCIGetter(ops ...Option) (Getter, error) { func NewOCIGetter(ops ...Option) (Getter, error) {
var client OCIGetter var client OCIGetter

@ -399,6 +399,7 @@ type (
Chart *DescriptorPullSummaryWithMeta `json:"chart"` Chart *DescriptorPullSummaryWithMeta `json:"chart"`
Prov *DescriptorPullSummary `json:"prov"` Prov *DescriptorPullSummary `json:"prov"`
Ref string `json:"ref"` Ref string `json:"ref"`
Values *DescriptorPullSummary `json:"values"`
} }
DescriptorPullSummary struct { DescriptorPullSummary struct {
@ -436,24 +437,23 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
return nil, errors.New( return nil, errors.New(
"must specify at least one layer to pull (chart/prov)") "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()) repository, err := remote.NewRepository(parsedRef.String())
if err != nil { if err != nil {
@ -464,16 +464,17 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
ctx := context.Background() ctx := context.Background()
sort.Strings(allowedMediaTypes) //sort.Strings(allowedMediaTypes)
var mu sync.Mutex var mu sync.Mutex
var layers []ocispec.Descriptor
manifest, err := oras.Copy(ctx, repository, parsedRef.String(), memoryStore, "", oras.CopyOptions{ manifest, err := oras.Copy(ctx, repository, parsedRef.String(), memoryStore, "", oras.CopyOptions{
CopyGraphOptions: oras.CopyGraphOptions{ CopyGraphOptions: oras.CopyGraphOptions{
PreCopy: func(_ context.Context, desc ocispec.Descriptor) error { PreCopy: func(_ context.Context, desc ocispec.Descriptor) error {
mediaType := desc.MediaType //mediaType := desc.MediaType
if i := sort.SearchStrings(allowedMediaTypes, mediaType); i >= len(allowedMediaTypes) || allowedMediaTypes[i] != 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) // return errors.Errorf("media type %q is not allowed, found in descriptor with digest: %q", mediaType, desc.Digest)
} //}
mu.Lock() mu.Lock()
layers = append(layers, desc) layers = append(layers, desc)
@ -486,17 +487,19 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
return nil, err return nil, err
} }
var descriptors []ocispec.Descriptor
descriptors = append(descriptors, manifest) descriptors = append(descriptors, manifest)
descriptors = append(descriptors, layers...) descriptors = append(descriptors, layers...)
numDescriptors := len(descriptors) if manifest.MediaType != "application/vnd.oci.image.manifest.v1+json" {
if numDescriptors < minNumDescriptors { return nil, fmt.Errorf("unexpected reference mediatype: %s", manifest.MediaType)
return nil, fmt.Errorf("manifest does not contain minimum number of descriptors (%d), descriptors found: %d",
minNumDescriptors, numDescriptors)
} }
var configDescriptor *ocispec.Descriptor var configDescriptor *ocispec.Descriptor
var chartDescriptor *ocispec.Descriptor var chartDescriptor *ocispec.Descriptor
var provDescriptor *ocispec.Descriptor var provDescriptor *ocispec.Descriptor
var valuesDescriptor *ocispec.Descriptor
for _, descriptor := range descriptors { for _, descriptor := range descriptors {
d := descriptor d := descriptor
switch d.MediaType { switch d.MediaType {
@ -506,11 +509,30 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
chartDescriptor = &d chartDescriptor = &d
case ProvLayerMediaType: case ProvLayerMediaType:
provDescriptor = &d provDescriptor = &d
case ValuesMediaType:
valuesDescriptor = &d
case LegacyChartLayerMediaType: case LegacyChartLayerMediaType:
chartDescriptor = &d chartDescriptor = &d
fmt.Fprintf(c.out, "Warning: chart media type %s is deprecated\n", LegacyChartLayerMediaType) 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 { if configDescriptor == nil {
return nil, fmt.Errorf("could not load config with mediatype %s", ConfigMediaType) return nil, fmt.Errorf("could not load config with mediatype %s", ConfigMediaType)
} }
@ -539,6 +561,7 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
Chart: &DescriptorPullSummaryWithMeta{}, Chart: &DescriptorPullSummaryWithMeta{},
Prov: &DescriptorPullSummary{}, Prov: &DescriptorPullSummary{},
Ref: parsedRef.String(), Ref: parsedRef.String(),
Values: nil,
} }
result.Manifest.Data, err = content.FetchAll(ctx, memoryStore, manifest) result.Manifest.Data, err = content.FetchAll(ctx, memoryStore, manifest)

@ -32,6 +32,9 @@ const (
// ProvLayerMediaType is the reserved media type for Helm chart provenance files // ProvLayerMediaType is the reserved media type for Helm chart provenance files
ProvLayerMediaType = "application/vnd.cncf.helm.chart.provenance.v1.prov" 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 is the legacy reserved media type for Helm chart package content.
LegacyChartLayerMediaType = "application/tar+gzip" LegacyChartLayerMediaType = "application/tar+gzip"
) )

Loading…
Cancel
Save