Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: pack OCI image spec v1.1 manifests #1054

Merged
merged 27 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions cmd/oras/internal/option/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import (
)

const (
v1_1 = "v1.1"
v1_0 = "v1.0"
ImageSpecV1_1 = "v1.1"
ImageSpecV1_0 = "v1.0"
// TODO: pending on https://github.com/oras-project/oras-go/issues/568
PackManifestTypeImageV1_0 = 0
)
Expand All @@ -38,9 +38,9 @@ type ImageSpec struct {
// Parse parses flags into the option.
func (opts *ImageSpec) Parse() error {
switch opts.flag {
case v1_1:
case ImageSpecV1_1:
opts.PackType = oras.PackManifestTypeImageV1_1_0_RC4
case v1_0:
case ImageSpecV1_0:
opts.PackType = PackManifestTypeImageV1_0
default:
return fmt.Errorf("unknown image specification flag: %q", opts.flag)
Expand All @@ -50,7 +50,7 @@ func (opts *ImageSpec) Parse() error {

// ApplyFlags applies flags to a command flag set.
func (opts *ImageSpec) ApplyFlags(fs *pflag.FlagSet) {
fs.StringVar(&opts.flag, "image-spec", v1_1, fmt.Sprintf("[Experimental] specify manifest type for building artifact. options: %s, %s", v1_1, v1_0))
fs.StringVar(&opts.flag, "image-spec", ImageSpecV1_1, fmt.Sprintf("[Experimental] specify manifest type for building artifact. options: %s, %s", ImageSpecV1_1, ImageSpecV1_0))
}

// distributionSpec option struct.
Expand Down
5 changes: 4 additions & 1 deletion cmd/oras/root/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras/cmd/oras/internal/display"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/graph"
Expand Down Expand Up @@ -117,7 +118,9 @@ func runCopy(ctx context.Context, opts copyOptions) error {
committed := &sync.Map{}
extendedCopyOptions := oras.DefaultExtendedCopyOptions
extendedCopyOptions.Concurrency = opts.concurrency
extendedCopyOptions.FindPredecessors = graph.FindReferrerPredecessors
extendedCopyOptions.FindPredecessors = func(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
return graph.Referrers(ctx, src, desc, "")
}
shizhMSFT marked this conversation as resolved.
Show resolved Hide resolved
extendedCopyOptions.PreCopy = display.StatusPrinter("Copying", opts.Verbose)
extendedCopyOptions.PostCopy = func(ctx context.Context, desc ocispec.Descriptor) error {
committed.Store(desc.Digest.String(), desc.Annotations[ocispec.AnnotationTitle])
Expand Down
64 changes: 27 additions & 37 deletions internal/graph/graph.go
qweeah marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@ func Successors(ctx context.Context, fetcher content.Fetcher, node ocispec.Descr
}
nodes = manifest.Blobs
subject = manifest.Subject

qweeah marked this conversation as resolved.
Show resolved Hide resolved
case ocispec.MediaTypeImageIndex:
var fetched []byte
fetched, err = content.FetchAll(ctx, fetcher, node)
if err != nil {
return
}
var index ocispec.Index
if err = json.Unmarshal(fetched, &index); err != nil {
return
}
nodes = index.Manifests
subject = index.Subject
default:
nodes, err = content.Successors(ctx, fetcher, node)
}
Expand Down Expand Up @@ -137,53 +150,30 @@ func Referrers(ctx context.Context, target content.ReadOnlyGraphStorage, desc oc
if image.Subject == nil || !content.Equal(*image.Subject, desc) {
continue
}
node.ArtifactType = image.Config.MediaType
node.ArtifactType = image.ArtifactType
if node.ArtifactType == "" {
node.ArtifactType = image.Config.MediaType
}
node.Annotations = image.Annotations
default:
continue
}
if node.ArtifactType != "" && (artifactType == "" || artifactType == node.ArtifactType) {
results = append(results, node)
}
}
return results, nil
}

// FindReferrerPredecessors returns referrer nodes of desc in target.
func FindReferrerPredecessors(ctx context.Context, src content.ReadOnlyGraphStorage, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
var results []ocispec.Descriptor
if repo, ok := src.(registry.ReferrerLister); ok {
// get referrers directly
err := repo.Referrers(ctx, desc, "", func(referrers []ocispec.Descriptor) error {
results = append(results, referrers...)
return nil
})
if err != nil {
return nil, err
}
return results, nil
}
predecessors, err := src.Predecessors(ctx, desc)
if err != nil {
return nil, err
}
for _, node := range predecessors {
switch node.MediaType {
case MediaTypeArtifactManifest, ocispec.MediaTypeImageManifest:
results = append(results, node)
case ocispec.MediaTypeImageIndex:
fetched, err := fetchBytes(ctx, src, node)
fetched, err := fetchBytes(ctx, target, node)
if err != nil {
return nil, err
}
// convert to json
var index ocispec.Index
if err := json.Unmarshal(fetched, &index); err != nil {
return nil, err
}
if index.Subject != nil && content.Equal(*index.Subject, desc) {
results = append(results, node)
if index.Subject == nil || !content.Equal(*index.Subject, desc) {
continue
}
node.ArtifactType = index.ArtifactType
node.Annotations = index.Annotations
default:
continue
}
qweeah marked this conversation as resolved.
Show resolved Hide resolved
if node.ArtifactType != "" && (artifactType == "" || artifactType == node.ArtifactType) {
results = append(results, node)
}
}
return results, nil
Expand Down
97 changes: 0 additions & 97 deletions internal/graph/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,6 @@ func (e *errLister) Referrers(ctx context.Context, desc ocispec.Descriptor, arti
return errors.New("")
}

type errFinder struct {
oras.ReadOnlyGraphTarget
}

func (e *errFinder) Predecessors(ctx context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) {
return nil, errors.New("")
}

type refLister struct {
referrers []ocispec.Descriptor
oras.ReadOnlyGraphTarget
Expand Down Expand Up @@ -265,92 +257,3 @@ func TestSuccessors(t *testing.T) {
})
}
}

func TestFindReferrerPredecessors(t *testing.T) {
ctx := context.Background()
var blobs [][]byte
var descs []ocispec.Descriptor
appendBlob := func(mediaType string, blob []byte) {
blobs = append(blobs, blob)
descs = append(descs, ocispec.Descriptor{
MediaType: mediaType,
Digest: digest.FromBytes(blob),
Size: int64(len(blob)),
})
}
generateImage := func(subject *ocispec.Descriptor, annotations map[string]string, config ocispec.Descriptor, layers ...ocispec.Descriptor) {
manifest := ocispec.Manifest{
Subject: subject,
Config: config,
Layers: layers,
Annotations: annotations,
}
manifestJSON, err := json.Marshal(manifest)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeImageManifest, manifestJSON)
}
generateIndex := func(manifests ...ocispec.Descriptor) {
index := ocispec.Index{
Manifests: manifests,
}
manifestJSON, err := json.Marshal(index)
if err != nil {
t.Fatal(err)
}
appendBlob(ocispec.MediaTypeImageIndex, manifestJSON)
}
const (
subject = iota
imgConfig
image
)
var anno map[string]string
appendBlob(ocispec.MediaTypeImageLayer, []byte("blob"))
imageType := "test.image"
appendBlob(imageType, []byte("config content"))
generateImage(&descs[subject], anno, descs[imgConfig])
imageDesc := descs[image]
imageDesc.Annotations = anno
imageDesc.ArtifactType = imageType
generateIndex(descs[subject])

referrers := []ocispec.Descriptor{descs[image], descs[image]}
memory := memory.New()
for i := range descs {
if err := memory.Push(ctx, descs[i], bytes.NewReader(blobs[i])); err != nil {
t.Errorf("Error pushing %v\n", err)
}
}
finder := &predecessorFinder{Store: memory}
type args struct {
ctx context.Context
src content.ReadOnlyGraphStorage
desc ocispec.Descriptor
}
tests := []struct {
name string
args args
want []ocispec.Descriptor
wantErr bool
}{
{"should failed to get referrers", args{ctx, &errLister{}, ocispec.Descriptor{}}, nil, true},
{"should failed to get predecessor", args{ctx, &errFinder{}, ocispec.Descriptor{}}, nil, true},
{"should return referrers when target is a referrer lister", args{ctx, &refLister{referrers: referrers}, ocispec.Descriptor{}}, referrers, false},
{"should return image for config node", args{ctx, finder, descs[imgConfig]}, []ocispec.Descriptor{descs[image]}, false},
{"should return image for subject node", args{ctx, finder, descs[subject]}, []ocispec.Descriptor{descs[image]}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FindReferrerPredecessors(tt.args.ctx, tt.args.src, tt.args.desc)
if (err != nil) != tt.wantErr {
t.Errorf("FindReferrerPredecessors() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("FindReferrerPredecessors() = %v, want %v", got, tt.want)
}
})
}
}