Skip to content

Commit 099265b

Browse files
authored
Merge pull request #1633 from dgageot/oci
Add OCI artifact wrapper for spec-compliant manifest with artifactType
2 parents 92187d6 + 7299020 commit 099265b

File tree

3 files changed

+113
-0
lines changed

3 files changed

+113
-0
lines changed

pkg/content/artifact.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package content
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
v1 "github.com/google/go-containerregistry/pkg/v1"
8+
"github.com/google/go-containerregistry/pkg/v1/partial"
9+
)
10+
11+
// artifactImage wraps a v1.Image so its serialized manifest includes an
12+
// "artifactType" field. The underlying config and layers are preserved
13+
// unchanged, which is required for tarball round-trips (the Docker tarball
14+
// format relies on DiffIDs in the config to map layers).
15+
//
16+
// This is necessary because go-containerregistry's v1.Manifest struct does not
17+
// have an artifactType field.
18+
//
19+
// See https://github.com/opencontainers/image-spec/blob/v1.1.1/manifest.md#guidelines-for-artifact-usage
20+
type artifactImage struct {
21+
v1.Image
22+
artifactType string
23+
}
24+
25+
// NewArtifactImage wraps an image so its serialized manifest includes the
26+
// given artifactType.
27+
func NewArtifactImage(base v1.Image, artifactType string) v1.Image {
28+
return &artifactImage{Image: base, artifactType: artifactType}
29+
}
30+
31+
// RawManifest returns the manifest with artifactType injected.
32+
func (a *artifactImage) RawManifest() ([]byte, error) {
33+
raw, err := a.Image.RawManifest()
34+
if err != nil {
35+
return nil, fmt.Errorf("getting raw manifest: %w", err)
36+
}
37+
38+
var manifest map[string]json.RawMessage
39+
if err := json.Unmarshal(raw, &manifest); err != nil {
40+
return nil, fmt.Errorf("unmarshaling manifest: %w", err)
41+
}
42+
43+
at, err := json.Marshal(a.artifactType)
44+
if err != nil {
45+
return nil, fmt.Errorf("marshaling artifactType: %w", err)
46+
}
47+
manifest["artifactType"] = at
48+
49+
return json.Marshal(manifest)
50+
}
51+
52+
// Digest returns the sha256 of the modified manifest.
53+
func (a *artifactImage) Digest() (v1.Hash, error) { return partial.Digest(a) }
54+
55+
// Manifest parses the modified raw manifest into a v1.Manifest.
56+
func (a *artifactImage) Manifest() (*v1.Manifest, error) { return partial.Manifest(a) }
57+
58+
// Size returns the size of the modified manifest.
59+
func (a *artifactImage) Size() (int64, error) { return partial.Size(a) }

pkg/content/artifact_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package content
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/google/go-containerregistry/pkg/v1/empty"
8+
"github.com/google/go-containerregistry/pkg/v1/mutate"
9+
"github.com/google/go-containerregistry/pkg/v1/static"
10+
"github.com/google/go-containerregistry/pkg/v1/types"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestNewArtifactImage(t *testing.T) {
16+
t.Parallel()
17+
18+
const testArtifactType = "application/vnd.test.artifact+json"
19+
20+
layer := static.NewLayer([]byte("test content"), "application/yaml")
21+
base, err := mutate.AppendLayers(empty.Image, layer)
22+
require.NoError(t, err)
23+
base = mutate.MediaType(base, types.OCIManifestSchema1)
24+
25+
artifact := NewArtifactImage(base, testArtifactType)
26+
27+
// Manifest must contain artifactType.
28+
raw, err := artifact.RawManifest()
29+
require.NoError(t, err)
30+
31+
var manifest map[string]json.RawMessage
32+
require.NoError(t, json.Unmarshal(raw, &manifest))
33+
34+
var got string
35+
require.Contains(t, manifest, "artifactType")
36+
require.NoError(t, json.Unmarshal(manifest["artifactType"], &got))
37+
assert.Equal(t, testArtifactType, got)
38+
39+
// Config must be preserved from the base image (not replaced with {}).
40+
rawConfig, err := artifact.RawConfigFile()
41+
require.NoError(t, err)
42+
baseConfig, err := base.RawConfigFile()
43+
require.NoError(t, err)
44+
assert.Equal(t, baseConfig, rawConfig)
45+
46+
// Layers must still be accessible.
47+
layers, err := artifact.Layers()
48+
require.NoError(t, err)
49+
assert.Len(t, layers, 1)
50+
}

pkg/remote/push.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ func Push(reference string) error {
3636
img = mutate.Annotations(img, metadata.Annotations).(v1.Image)
3737
}
3838

39+
// Wrap as a spec-compliant OCI artifact so the pushed manifest includes
40+
// artifactType and an empty config descriptor.
41+
img = content.NewArtifactImage(img, "application/vnd.docker.cagent.config.v1+json")
42+
3943
ref, err := name.ParseReference(reference)
4044
if err != nil {
4145
return fmt.Errorf("parsing registry reference %s: %w", reference, err)

0 commit comments

Comments
 (0)