mirror of
https://github.com/helm/helm.git
synced 2026-06-30 19:57:48 +00:00
Attach annotations to OCI artifacts
Signed-off-by: Andrew Block <andy.block@gmail.com>
This commit is contained in:
@@ -551,7 +551,9 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
|
||||
descriptors = append(descriptors, provDescriptor)
|
||||
}
|
||||
|
||||
manifestData, manifest, err := content.GenerateManifest(&configDescriptor, nil, descriptors...)
|
||||
ociAnnotations := generateOCIAnnotations(meta)
|
||||
|
||||
manifestData, manifest, err := content.GenerateManifest(&configDescriptor, ociAnnotations, descriptors...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
orascontext "oras.land/oras-go/pkg/context"
|
||||
@@ -35,6 +36,11 @@ import (
|
||||
"helm.sh/helm/v3/pkg/chart/loader"
|
||||
)
|
||||
|
||||
var immutableOciAnnotations = []string{
|
||||
ocispec.AnnotationVersion,
|
||||
ocispec.AnnotationTitle,
|
||||
}
|
||||
|
||||
// IsOCI determines whether or not a URL is to be treated as an OCI URL
|
||||
func IsOCI(url string) bool {
|
||||
return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme))
|
||||
@@ -155,3 +161,80 @@ func NewRegistryClientWithTLS(out io.Writer, certFile, keyFile, caFile string, i
|
||||
}
|
||||
return registryClient, nil
|
||||
}
|
||||
|
||||
// generateOCIAnnotations will generate OCI annotations to include within the OCI manifest
|
||||
func generateOCIAnnotations(meta *chart.Metadata) map[string]string {
|
||||
|
||||
// Get annotations from Chart attributes
|
||||
ociAnnotations := generateChartOCIAnnotations(meta)
|
||||
|
||||
// Copy Chart annotations
|
||||
annotations:
|
||||
for chartAnnotationKey, chartAnnotationValue := range meta.Annotations {
|
||||
|
||||
// Avoid overriding key properties
|
||||
for _, immutableOciKey := range immutableOciAnnotations {
|
||||
if immutableOciKey == chartAnnotationKey {
|
||||
continue annotations
|
||||
}
|
||||
}
|
||||
|
||||
// Add chart annotation
|
||||
ociAnnotations[chartAnnotationKey] = chartAnnotationValue
|
||||
}
|
||||
|
||||
return ociAnnotations
|
||||
}
|
||||
|
||||
// getChartOCIAnnotations will generate OCI annotations from the provided chart
|
||||
func generateChartOCIAnnotations(meta *chart.Metadata) map[string]string {
|
||||
chartOCIAnnotations := map[string]string{}
|
||||
|
||||
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationDescription, meta.Description)
|
||||
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationTitle, meta.Name)
|
||||
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationVersion, meta.Version)
|
||||
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationURL, meta.Home)
|
||||
|
||||
if len(meta.Sources) > 0 {
|
||||
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationSource, meta.Sources[0])
|
||||
}
|
||||
|
||||
if meta.Maintainers != nil && len(meta.Maintainers) > 0 {
|
||||
var maintainerSb strings.Builder
|
||||
|
||||
for maintainerIdx, maintainer := range meta.Maintainers {
|
||||
|
||||
if len(maintainer.Name) > 0 {
|
||||
maintainerSb.WriteString(maintainer.Name)
|
||||
}
|
||||
|
||||
if len(maintainer.Email) > 0 {
|
||||
maintainerSb.WriteString(" (")
|
||||
maintainerSb.WriteString(maintainer.Email)
|
||||
maintainerSb.WriteString(")")
|
||||
}
|
||||
|
||||
if maintainerIdx < len(meta.Maintainers)-1 {
|
||||
maintainerSb.WriteString(", ")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationAuthors, maintainerSb.String())
|
||||
|
||||
}
|
||||
|
||||
return chartOCIAnnotations
|
||||
}
|
||||
|
||||
// addToMap takes an existing map and adds an item if the value is not empty
|
||||
func addToMap(inputMap map[string]string, newKey string, newValue string) map[string]string {
|
||||
|
||||
// Add item to map if its
|
||||
if len(strings.TrimSpace(newValue)) > 0 {
|
||||
inputMap[newKey] = newValue
|
||||
}
|
||||
|
||||
return inputMap
|
||||
|
||||
}
|
||||
|
||||
216
pkg/registry/util_test.go
Normal file
216
pkg/registry/util_test.go
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
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 "helm.sh/helm/v3/pkg/registry"
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"helm.sh/helm/v3/pkg/chart"
|
||||
)
|
||||
|
||||
func TestGenerateOCIChartAnnotations(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
chart *chart.Metadata
|
||||
expect map[string]string
|
||||
}{
|
||||
{
|
||||
"Baseline chart",
|
||||
&chart.Metadata{
|
||||
Name: "oci",
|
||||
Version: "0.0.1",
|
||||
},
|
||||
map[string]string{
|
||||
"org.opencontainers.image.title": "oci",
|
||||
"org.opencontainers.image.version": "0.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Simple chart values",
|
||||
&chart.Metadata{
|
||||
Name: "oci",
|
||||
Version: "0.0.1",
|
||||
Description: "OCI Helm Chart",
|
||||
Home: "https://helm.sh",
|
||||
},
|
||||
map[string]string{
|
||||
"org.opencontainers.image.title": "oci",
|
||||
"org.opencontainers.image.version": "0.0.1",
|
||||
"org.opencontainers.image.description": "OCI Helm Chart",
|
||||
"org.opencontainers.image.url": "https://helm.sh",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Maintainer without email",
|
||||
&chart.Metadata{
|
||||
Name: "oci",
|
||||
Version: "0.0.1",
|
||||
Description: "OCI Helm Chart",
|
||||
Home: "https://helm.sh",
|
||||
Maintainers: []*chart.Maintainer{
|
||||
{
|
||||
Name: "John Snow",
|
||||
},
|
||||
},
|
||||
},
|
||||
map[string]string{
|
||||
"org.opencontainers.image.title": "oci",
|
||||
"org.opencontainers.image.version": "0.0.1",
|
||||
"org.opencontainers.image.description": "OCI Helm Chart",
|
||||
"org.opencontainers.image.url": "https://helm.sh",
|
||||
"org.opencontainers.image.authors": "John Snow",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Maintainer with email",
|
||||
&chart.Metadata{
|
||||
Name: "oci",
|
||||
Version: "0.0.1",
|
||||
Description: "OCI Helm Chart",
|
||||
Home: "https://helm.sh",
|
||||
Maintainers: []*chart.Maintainer{
|
||||
{Name: "John Snow", Email: "john@winterfell.com"},
|
||||
},
|
||||
},
|
||||
map[string]string{
|
||||
"org.opencontainers.image.title": "oci",
|
||||
"org.opencontainers.image.version": "0.0.1",
|
||||
"org.opencontainers.image.description": "OCI Helm Chart",
|
||||
"org.opencontainers.image.url": "https://helm.sh",
|
||||
"org.opencontainers.image.authors": "John Snow (john@winterfell.com)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Multiple Maintainers",
|
||||
&chart.Metadata{
|
||||
Name: "oci",
|
||||
Version: "0.0.1",
|
||||
Description: "OCI Helm Chart",
|
||||
Home: "https://helm.sh",
|
||||
Maintainers: []*chart.Maintainer{
|
||||
{Name: "John Snow", Email: "john@winterfell.com"},
|
||||
{Name: "Jane Snow"},
|
||||
},
|
||||
},
|
||||
map[string]string{
|
||||
"org.opencontainers.image.title": "oci",
|
||||
"org.opencontainers.image.version": "0.0.1",
|
||||
"org.opencontainers.image.description": "OCI Helm Chart",
|
||||
"org.opencontainers.image.url": "https://helm.sh",
|
||||
"org.opencontainers.image.authors": "John Snow (john@winterfell.com), Jane Snow",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Chart with Sources",
|
||||
&chart.Metadata{
|
||||
Name: "oci",
|
||||
Version: "0.0.1",
|
||||
Description: "OCI Helm Chart",
|
||||
Sources: []string{
|
||||
"https://github.com/helm/helm",
|
||||
},
|
||||
},
|
||||
map[string]string{
|
||||
"org.opencontainers.image.title": "oci",
|
||||
"org.opencontainers.image.version": "0.0.1",
|
||||
"org.opencontainers.image.description": "OCI Helm Chart",
|
||||
"org.opencontainers.image.source": "https://github.com/helm/helm",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
result := generateChartOCIAnnotations(tt.chart)
|
||||
|
||||
if !reflect.DeepEqual(tt.expect, result) {
|
||||
t.Errorf("%s: expected map %v, got %v", tt.name, tt.expect, result)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateOCIAnnotations(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
chart *chart.Metadata
|
||||
expect map[string]string
|
||||
}{
|
||||
{
|
||||
"Baseline chart",
|
||||
&chart.Metadata{
|
||||
Name: "oci",
|
||||
Version: "0.0.1",
|
||||
},
|
||||
map[string]string{
|
||||
"org.opencontainers.image.title": "oci",
|
||||
"org.opencontainers.image.version": "0.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Simple chart values with custom Annotations",
|
||||
&chart.Metadata{
|
||||
Name: "oci",
|
||||
Version: "0.0.1",
|
||||
Description: "OCI Helm Chart",
|
||||
Annotations: map[string]string{
|
||||
"extrakey": "extravlue",
|
||||
"anotherkey": "anothervalue",
|
||||
},
|
||||
},
|
||||
map[string]string{
|
||||
"org.opencontainers.image.title": "oci",
|
||||
"org.opencontainers.image.version": "0.0.1",
|
||||
"org.opencontainers.image.description": "OCI Helm Chart",
|
||||
"extrakey": "extravlue",
|
||||
"anotherkey": "anothervalue",
|
||||
},
|
||||
},
|
||||
{
|
||||
"Verify Chart Name and Version cannot be overridden from annotations",
|
||||
&chart.Metadata{
|
||||
Name: "oci",
|
||||
Version: "0.0.1",
|
||||
Description: "OCI Helm Chart",
|
||||
Annotations: map[string]string{
|
||||
"org.opencontainers.image.title": "badchartname",
|
||||
"org.opencontainers.image.version": "1.0.0",
|
||||
"extrakey": "extravlue",
|
||||
},
|
||||
},
|
||||
map[string]string{
|
||||
"org.opencontainers.image.title": "oci",
|
||||
"org.opencontainers.image.version": "0.0.1",
|
||||
"org.opencontainers.image.description": "OCI Helm Chart",
|
||||
"extrakey": "extravlue",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
result := generateOCIAnnotations(tt.chart)
|
||||
|
||||
if !reflect.DeepEqual(tt.expect, result) {
|
||||
t.Errorf("%s: expected map %v, got %v", tt.name, tt.expect, result)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -279,12 +279,12 @@ func testPush(suite *TestSuite) {
|
||||
suite.Equal(ref, result.Ref)
|
||||
suite.Equal(meta.Name, result.Chart.Meta.Name)
|
||||
suite.Equal(meta.Version, result.Chart.Meta.Version)
|
||||
suite.Equal(int64(512), result.Manifest.Size)
|
||||
suite.Equal(int64(684), 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:af4c20a1df1431495e673c14ecfa3a2ba24839a7784349d6787cd67957392e83",
|
||||
"sha256:b57e8ffd938c43253f30afedb3c209136288e6b3af3b33473e95ea3b805888e6",
|
||||
result.Manifest.Digest)
|
||||
suite.Equal(
|
||||
"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580",
|
||||
@@ -352,12 +352,12 @@ func testPull(suite *TestSuite) {
|
||||
suite.Equal(ref, result.Ref)
|
||||
suite.Equal(meta.Name, result.Chart.Meta.Name)
|
||||
suite.Equal(meta.Version, result.Chart.Meta.Version)
|
||||
suite.Equal(int64(512), result.Manifest.Size)
|
||||
suite.Equal(int64(684), 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:af4c20a1df1431495e673c14ecfa3a2ba24839a7784349d6787cd67957392e83",
|
||||
"sha256:b57e8ffd938c43253f30afedb3c209136288e6b3af3b33473e95ea3b805888e6",
|
||||
result.Manifest.Digest)
|
||||
suite.Equal(
|
||||
"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580",
|
||||
@@ -368,7 +368,7 @@ func testPull(suite *TestSuite) {
|
||||
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}]}",
|
||||
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.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))
|
||||
|
||||
Reference in New Issue
Block a user