Skip to content

Commit

Permalink
feat(cmd/cp): add oci-layout-path flag (#1507)
Browse files Browse the repository at this point in the history
Signed-off-by: Mauricio Vásquez <[email protected]>
Co-authored-by: Terry Howe <[email protected]>
  • Loading branch information
mauriciovasquezbernal and TerryHowe authored Nov 3, 2024
1 parent b6ac8e9 commit 9a83394
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 3 deletions.
9 changes: 9 additions & 0 deletions cmd/oras/internal/option/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func (opts *Target) AnnotatedReference() string {
func (opts *Target) applyFlagsWithPrefix(fs *pflag.FlagSet, prefix, description string) {
flagPrefix, notePrefix := applyPrefix(prefix, description)
fs.BoolVarP(&opts.IsOCILayout, flagPrefix+"oci-layout", "", false, "set "+notePrefix+"target as an OCI image layout")
fs.StringVar(&opts.Path, flagPrefix+"oci-layout-path", "", "set the path for the "+notePrefix+"OCI image layout target")
}

// ApplyFlagsWithPrefix applies flags to a command flag set with a prefix string.
Expand All @@ -96,13 +97,21 @@ func (opts *Target) ApplyFlagsWithPrefix(fs *pflag.FlagSet, prefix, description

// Parse gets target options from user input.
func (opts *Target) Parse(cmd *cobra.Command) error {
if err := oerrors.CheckMutuallyExclusiveFlags(cmd.Flags(), opts.flagPrefix+"oci-layout-path", opts.flagPrefix+"oci-layout"); err != nil {
return err
}

switch {
case opts.IsOCILayout:
opts.Type = TargetTypeOCILayout
if len(opts.headerFlags) != 0 {
return errors.New("custom header flags cannot be used on an OCI image layout target")
}
return opts.parseOCILayoutReference()
case opts.Path != "":
opts.Type = TargetTypeOCILayout
opts.Reference = opts.RawReference
return nil
default:
opts.Type = TargetTypeRemote
if ref, err := registry.ParseReference(opts.RawReference); err != nil {
Expand Down
42 changes: 40 additions & 2 deletions cmd/oras/internal/option/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"net/http"
"net/url"
"reflect"
"strings"
"testing"

"github.com/spf13/cobra"
Expand All @@ -28,9 +29,26 @@ import (
oerrors "oras.land/oras/cmd/oras/internal/errors"
)

func TestTarget_Parse_oci_path(t *testing.T) {
opts := Target{
Path: "foo",
RawReference: "mocked/test",
}
cmd := &cobra.Command{}
ApplyFlags(&opts, cmd.Flags())
if err := opts.Parse(cmd); err != nil {
t.Errorf("Target.Parse() error = %v", err)
}
if opts.Type != TargetTypeOCILayout {
t.Errorf("Target.Parse() failed, got %q, want %q", opts.Type, TargetTypeOCILayout)
}
}

func TestTarget_Parse_oci(t *testing.T) {
opts := Target{IsOCILayout: true}
err := opts.Parse(nil)
cmd := &cobra.Command{}
ApplyFlags(&opts, cmd.Flags())
err := opts.Parse(cmd)
if !errors.Is(err, errdef.ErrInvalidReference) {
t.Errorf("Target.Parse() error = %v, expect %v", err, errdef.ErrInvalidReference)
}
Expand All @@ -39,6 +57,24 @@ func TestTarget_Parse_oci(t *testing.T) {
}
}

func TestTarget_Parse_oci_and_oci_path(t *testing.T) {
opts := Target{}
cmd := &cobra.Command{}
opts.ApplyFlags(cmd.Flags())
cmd.SetArgs([]string{"--oci-layout", "foo", "--oci-layout-path", "foo"})
if err := cmd.Execute(); err != nil {
t.Errorf("cmd.Execute() error = %v", err)
}
err := opts.Parse(cmd)
if err == nil {
t.Errorf("expect Target.Parse() to fail but not")
}
if !strings.Contains(err.Error(), "cannot be used at the same time") {
t.Errorf("expect error message to contain 'cannot be used at the same time' but not")
}

}

func TestTarget_Parse_remote(t *testing.T) {
opts := Target{
RawReference: "mocked/test",
Expand All @@ -59,7 +95,9 @@ func TestTarget_Parse_remote_err(t *testing.T) {
RawReference: "/test",
IsOCILayout: false,
}
if err := opts.Parse(nil); err == nil {
cmd := &cobra.Command{}
ApplyFlags(&opts, cmd.Flags())
if err := opts.Parse(cmd); err == nil {
t.Errorf("expect Target.Parse() to fail but not")
}
}
Expand Down
4 changes: 4 additions & 0 deletions test/e2e/internal/utils/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ var (
Layout string
FromLayout string
ToLayout string
FromLayoutPath string
ToLayoutPath string
DistributionSpec string
ImageSpec string
}{
"--oci-layout",
"--from-oci-layout",
"--to-oci-layout",
"--from-oci-layout-path",
"--to-oci-layout-path",
"--distribution-spec",
"--image-spec",
}
Expand Down
87 changes: 86 additions & 1 deletion test/e2e/suite/command/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ var _ = Describe("OCI layout users:", func() {

It("should copy an image from a registry to an OCI image layout via digest", func() {
dstDir := GinkgoT().TempDir()
src := RegistryRef(ZOTHost, ImageRepo, foobar.Tag)
src := RegistryRef(ZOTHost, ImageRepo, foobar.Digest)
ORAS("cp", src, dstDir, "-v", Flags.ToLayout).MatchStatus(foobarStates, true, len(foobarStates)).Exec()
// validate
srcManifest := ORAS("manifest", "fetch", src).WithDescription("fetch from source to validate").Exec().Out.Contents()
Expand Down Expand Up @@ -625,6 +625,91 @@ var _ = Describe("OCI layout users:", func() {
Expect(len(index.Manifests)).To(Equal(1))
Expect(index.Manifests[0].Digest.String()).To(Equal(ma.LinuxAMD64Referrer.Digest.String()))
})

// oci-layout-path tests

It("should copy an image from a registry to an OCI image layout via tag using --oci-layout-path", func() {
layoutDir := GinkgoT().TempDir()
src := RegistryRef(ZOTHost, ImageRepo, foobar.Tag)
ref := "copied"
dst := LayoutRef(layoutDir, ref)
ORAS("cp", src, ref, "-v", Flags.ToLayoutPath, layoutDir).MatchStatus(foobarStates, true, len(foobarStates)).Exec()
// validate
srcManifest := ORAS("manifest", "fetch", src).WithDescription("fetch from source to validate").Exec().Out.Contents()
dstManifest := ORAS("manifest", "fetch", dst, Flags.Layout).WithDescription("fetch from destination to validate").Exec().Out.Contents()
Expect(srcManifest).To(Equal(dstManifest))
})

It("should copy an image from an OCI image layout to a registry via tag using --oci-layout-path", func() {
layoutDir := GinkgoT().TempDir()
ref := "copied"
src := LayoutRef(layoutDir, ref)
dst := RegistryRef(ZOTHost, cpTestRepo("from-layout-tag-path"), foobar.Tag)
// prepare
ORAS("cp", RegistryRef(ZOTHost, ImageRepo, foobar.Tag), src, Flags.ToLayout).Exec()
// test
ORAS("cp", ref, dst, "-v", Flags.FromLayoutPath, layoutDir).MatchStatus(foobarStates, true, len(foobarStates)).Exec()
// validate
srcManifest := ORAS("manifest", "fetch", src, Flags.Layout).WithDescription("fetch from source to validate").Exec().Out.Contents()
dstManifest := ORAS("manifest", "fetch", dst).WithDescription("fetch from destination to validate").Exec().Out.Contents()
Expect(srcManifest).To(Equal(dstManifest))
})

It("should copy an image between OCI image layouts via tag using --oci-layout-path", func() {
srcDir := GinkgoT().TempDir()
toDir := GinkgoT().TempDir()
srcRef := "from"
dstRef := "to"
src := LayoutRef(srcDir, srcRef)
dst := LayoutRef(toDir, dstRef)
// prepare
ORAS("cp", RegistryRef(ZOTHost, ImageRepo, foobar.Tag), src, Flags.ToLayout).Exec()
// test
ORAS("cp", srcRef, dstRef, "-v", Flags.FromLayoutPath, srcDir, Flags.ToLayoutPath, toDir).MatchStatus(foobarStates, true, len(foobarStates)).Exec()
// validate
srcManifest := ORAS("manifest", "fetch", src, Flags.Layout).WithDescription("fetch from source to validate").Exec().Out.Contents()
dstManifest := ORAS("manifest", "fetch", dst, Flags.Layout).WithDescription("fetch from destination to validate").Exec().Out.Contents()
Expect(srcManifest).To(Equal(dstManifest))
})

It("should copy an image from a registry to an OCI image layout via digest using --oci-layout-path", func() {
dstDir := GinkgoT().TempDir()
src := RegistryRef(ZOTHost, ImageRepo, foobar.Digest)
ORAS("cp", src, foobar.Digest, "-v", Flags.ToLayoutPath, dstDir).MatchStatus(foobarStates, true, len(foobarStates)).Exec()
// validate
srcManifest := ORAS("manifest", "fetch", src).WithDescription("fetch from source to validate").Exec().Out.Contents()
dstManifest := ORAS("manifest", "fetch", LayoutRef(dstDir, foobar.Digest), Flags.Layout).WithDescription("fetch from destination to validate").Exec().Out.Contents()
Expect(srcManifest).To(Equal(dstManifest))
})

It("should copy an image from an OCI image layout to a registry via digest using --oci-layout-path", func() {
layoutDir := GinkgoT().TempDir()
src := LayoutRef(layoutDir, foobar.Digest)
dst := RegistryRef(ZOTHost, cpTestRepo("from-layout-digest-path"), "copied")
// prepare
ORAS("cp", RegistryRef(ZOTHost, ImageRepo, foobar.Tag), layoutDir, Flags.ToLayout).Exec()
// test
ORAS("cp", foobar.Digest, dst, "-v", Flags.FromLayoutPath, layoutDir).MatchStatus(foobarStates, true, len(foobarStates)).Exec()
// validate
srcManifest := ORAS("manifest", "fetch", src, Flags.Layout).WithDescription("fetch from source to validate").Exec().Out.Contents()
dstManifest := ORAS("manifest", "fetch", dst).WithDescription("fetch from destination to validate").Exec().Out.Contents()
Expect(srcManifest).To(Equal(dstManifest))
})

It("should copy an image between OCI image layouts via digest", func() {
srcDir := GinkgoT().TempDir()
toDir := GinkgoT().TempDir()
src := LayoutRef(srcDir, foobar.Digest)
dst := LayoutRef(toDir, foobar.Digest)
// prepare
ORAS("cp", RegistryRef(ZOTHost, ImageRepo, foobar.Tag), srcDir, Flags.ToLayout).Exec()
// test
ORAS("cp", foobar.Digest, foobar.Digest, "-v", Flags.FromLayoutPath, srcDir, Flags.ToLayoutPath, toDir).MatchStatus(foobarStates, true, len(foobarStates)).Exec()
// validate
srcManifest := ORAS("manifest", "fetch", src, Flags.Layout).WithDescription("fetch from source to validate").Exec().Out.Contents()
dstManifest := ORAS("manifest", "fetch", dst, Flags.Layout).WithDescription("fetch from destination to validate").Exec().Out.Contents()
Expect(srcManifest).To(Equal(dstManifest))
})
})
})

Expand Down

0 comments on commit 9a83394

Please sign in to comment.