From 2f0bdeda9955baad5818a9acb7c10a7f6b72065b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Foray?= Date: Fri, 8 Jul 2022 18:37:48 +0200 Subject: [PATCH] Publish OCI Annotations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #11062 Signed-off-by: Jérôme Foray --- pkg/chart/metadata.go | 7 +++++++ pkg/registry/client.go | 29 ++++++++++++++++++++++++++++- pkg/registry/client_test.go | 18 +++++++++++++----- pkg/registry/util.go | 7 +++++++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/pkg/chart/metadata.go b/pkg/chart/metadata.go index 1925e45ac..1cef5a457 100644 --- a/pkg/chart/metadata.go +++ b/pkg/chart/metadata.go @@ -16,6 +16,7 @@ limitations under the License. package chart import ( + "fmt" "strings" "unicode" @@ -32,6 +33,12 @@ type Maintainer struct { URL string `json:"url,omitempty"` } +var _ fmt.Stringer = &Maintainer{} + +func (m *Maintainer) String() string { + return fmt.Sprintf("%s (%s)", m.Name, m.Email) +} + // Validate checks valid data and sanitizes string characters. func (m *Maintainer) Validate() error { m.Name = sanitizeString(m.Name) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index c1004f956..c46ac568d 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -25,6 +25,7 @@ import ( "net/http" "sort" "strings" + "time" "github.com/Masterminds/semver/v3" "github.com/containerd/containerd/remotes" @@ -527,7 +528,33 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu descriptors = append(descriptors, provDescriptor) } - manifestData, manifest, err := content.GenerateManifest(&configDescriptor, nil, descriptors...) + annotations := make(map[string]string) + + annotations[ocispec.AnnotationCreated] = Timestamper().Format(time.RFC3339) + if len(meta.Maintainers) > 0 { + authors := make([]string, len(meta.Maintainers)) + for i, m := range meta.Maintainers { + authors[i] = m.String() + } + annotations[ocispec.AnnotationAuthors] = strings.Join(authors, ", ") + } + if len(meta.Sources) > 0 { + annotations[ocispec.AnnotationSource] = meta.Sources[0] + } + if len(meta.Home) > 0 { + annotations[ocispec.AnnotationURL] = meta.Home + } + if len(meta.Version) > 0 { + annotations[ocispec.AnnotationVersion] = meta.Version + } + if len(meta.Name) > 0 { + annotations[ocispec.AnnotationTitle] = meta.Name + } + if len(meta.Description) > 0 { + annotations[ocispec.AnnotationDescription] = meta.Description + } + + manifestData, manifest, err := content.GenerateManifest(&configDescriptor, annotations, descriptors...) if err != nil { return nil, err } diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go index 138dd4245..dcffe672a 100644 --- a/pkg/registry/client_test.go +++ b/pkg/registry/client_test.go @@ -39,6 +39,8 @@ import ( "github.com/phayes/freeport" "github.com/stretchr/testify/suite" "golang.org/x/crypto/bcrypt" + + helmtime "helm.sh/helm/v3/pkg/time" ) var ( @@ -57,6 +59,12 @@ type RegistryClientTestSuite struct { RegistryClient *Client } +func testTimestamper() helmtime.Time { return helmtime.Unix(242085845, 0).UTC() } + +func init() { + Timestamper = testTimestamper +} + func (suite *RegistryClientTestSuite) SetupSuite() { suite.WorkspaceDir = testWorkspaceDir os.RemoveAll(suite.WorkspaceDir) @@ -198,12 +206,12 @@ func (suite *RegistryClientTestSuite) Test_1_Push() { 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(742), 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:fbbade96da6050f68f94f122881e3b80051a18f13ab5f4081868dd494538f5c2", result.Manifest.Digest) suite.Equal( "sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580", @@ -271,12 +279,12 @@ func (suite *RegistryClientTestSuite) Test_2_Pull() { 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(742), 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:fbbade96da6050f68f94f122881e3b80051a18f13ab5f4081868dd494538f5c2", result.Manifest.Digest) suite.Equal( "sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580", @@ -287,7 +295,7 @@ func (suite *RegistryClientTestSuite) Test_2_Pull() { 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.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)) diff --git a/pkg/registry/util.go b/pkg/registry/util.go index 47eed267f..a38fb03a4 100644 --- a/pkg/registry/util.go +++ b/pkg/registry/util.go @@ -31,8 +31,15 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/time" ) +// Timestamper is a function capable of producing a timestamp.Timestamper. +// +// By default, this is a time.Time function from the Helm time package. This can +// be overridden for testing though, so that timestamps are predictable. +var Timestamper = time.Now + // 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))