Skip to content

Commit

Permalink
feat: --output for oras manifest index update (#1502)
Browse files Browse the repository at this point in the history
Signed-off-by: Xiaoxuan Wang <[email protected]>
  • Loading branch information
wangxiaoxuan273 authored Sep 19, 2024
1 parent 071f060 commit 961e9f8
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 6 deletions.
25 changes: 24 additions & 1 deletion cmd/oras/root/manifest/index/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"encoding/json"
"fmt"
"os"

"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
Expand All @@ -39,11 +40,13 @@ import (
type updateOptions struct {
option.Common
option.Target
option.Pretty

addArguments []string
mergeArguments []string
removeArguments []string
tags []string
outputPath string
}

func updateCmd() *cobra.Command {
Expand All @@ -64,9 +67,18 @@ Example - Merge manifests from the index 'v2-windows' to the index 'v2':
Example - Update an index and tag the updated index as 'v2.1.0' and 'v2':
oras manifest index update localhost:5000/hello@sha256:99e4703fbf30916f549cd6bfa9cdbab614b5392fbe64fdee971359a77073cdf9 --add linux-amd64 --tag "v2.1.0" --tag "v2"
Example - Update an index and save it locally to index.json, auto push will be disabled:
oras manifest index update --output index.json localhost:5000/hello:v2 --add v2-linux-amd64
Example - Update an index and output the index to stdout, auto push will be disabled:
oras manifest index update --output - --pretty localhost:5000/hello:v2 --remove sha256:99e4703fbf30916f549cd6bfa9cdbab614b5392fbe64fdee971359a77073cdf9
`,
Args: oerrors.CheckArgs(argument.Exactly(1), "the target index to update"),
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := oerrors.CheckMutuallyExclusiveFlags(cmd.Flags(), "tag", "output"); err != nil {
return err
}
opts.RawReference = args[0]
for _, manifestRef := range opts.removeArguments {
if !contentutil.IsDigest(manifestRef) {
Expand All @@ -84,6 +96,7 @@ Example - Update an index and tag the updated index as 'v2.1.0' and 'v2':
cmd.Flags().StringArrayVarP(&opts.mergeArguments, "merge", "", nil, "indexes to be merged into the index")
cmd.Flags().StringArrayVarP(&opts.removeArguments, "remove", "", nil, "manifests to remove from the index, must be digests")
cmd.Flags().StringArrayVarP(&opts.tags, "tag", "", nil, "extra tags for the updated index")
cmd.Flags().StringVarP(&opts.outputPath, "output", "o", "", "file `path` to write the created index to, use - for stdout")
return oerrors.Command(cmd, &opts.Target)
}

Expand Down Expand Up @@ -127,7 +140,17 @@ func updateIndex(cmd *cobra.Command, opts updateOptions) error {

printUpdateStatus(status.IndexPromptUpdated, string(desc.Digest), "", opts.Printer)
path := getPushPath(opts.RawReference, opts.Type, opts.Reference, opts.Path)
return pushIndex(ctx, target, desc, indexBytes, opts.Reference, opts.tags, path, opts.Printer)
switch opts.outputPath {
case "":
err = pushIndex(ctx, target, desc, indexBytes, opts.Reference, opts.tags, path, opts.Printer)
case "-":
opts.Println("Digest:", desc.Digest)
err = opts.Output(os.Stdout, indexBytes)
default:
opts.Println("Digest:", desc.Digest)
err = os.WriteFile(opts.outputPath, indexBytes, 0666)
}
return err
}

func fetchIndex(ctx context.Context, target oras.ReadOnlyTarget, opts updateOptions) (ocispec.Index, error) {
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/internal/testdata/multi_arch/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,5 @@ var (

// exported index
var (
CreatedIndex = `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:9d84a5716c66a1d1b9c13f8ed157ba7d1edfe7f9b8766728b8a1f25c0d9c14c1","size":458,"platform":{"architecture":"amd64","os":"linux"}}]}`
OutputIndex = `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:9d84a5716c66a1d1b9c13f8ed157ba7d1edfe7f9b8766728b8a1f25c0d9c14c1","size":458,"platform":{"architecture":"amd64","os":"linux"}}]}`
)
59 changes: 55 additions & 4 deletions test/e2e/suite/command/manifest_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,14 @@ var _ = Describe("1.1 registry users:", func() {
CopyZOTRepo(ImageRepo, testRepo)
filePath := filepath.Join(GinkgoT().TempDir(), "createdIndex")
ORAS("manifest", "index", "create", RegistryRef(ZOTHost, testRepo, ""), string(multi_arch.LinuxAMD64.Digest), "--output", filePath).Exec()
MatchFile(filePath, multi_arch.CreatedIndex, DefaultTimeout)
MatchFile(filePath, multi_arch.OutputIndex, DefaultTimeout)
})

It("should output created index to stdout", func() {
testRepo := indexTestRepo("create", "output-to-stdout")
CopyZOTRepo(ImageRepo, testRepo)
ORAS("manifest", "index", "create", RegistryRef(ZOTHost, testRepo, ""), string(multi_arch.LinuxAMD64.Digest),
"--output", "-").MatchKeyWords(multi_arch.CreatedIndex).Exec()
"--output", "-").MatchKeyWords(multi_arch.OutputIndex).Exec()
})

It("should fail if given a reference that does not exist in the repo", func() {
Expand Down Expand Up @@ -230,6 +230,28 @@ var _ = Describe("1.1 registry users:", func() {
ValidateIndex(content, expectedManifests)
})

It("should output updated index to file", func() {
testRepo := indexTestRepo("update", "output-to-file")
CopyZOTRepo(ImageRepo, testRepo)
filePath := filepath.Join(GinkgoT().TempDir(), "updatedIndex")
// create an index for testing purpose
ORAS("manifest", "index", "create", RegistryRef(ZOTHost, testRepo, "v1")).Exec()
// add a manifest to the index
ORAS("manifest", "index", "update", RegistryRef(ZOTHost, testRepo, "v1"),
"--add", string(multi_arch.LinuxAMD64.Digest), "--output", filePath).Exec()
MatchFile(filePath, multi_arch.OutputIndex, DefaultTimeout)
})

It("should output updated index to stdout", func() {
testRepo := indexTestRepo("update", "output-to-stdout")
CopyZOTRepo(ImageRepo, testRepo)
// create an index for testing purpose
ORAS("manifest", "index", "create", RegistryRef(ZOTHost, testRepo, "v1")).Exec()
// add a manifest to the index
ORAS("manifest", "index", "update", RegistryRef(ZOTHost, testRepo, "v1"),
"--add", string(multi_arch.LinuxAMD64.Digest), "--output", "-").MatchKeyWords(multi_arch.OutputIndex).Exec()
})

It("should tell user nothing to update if no update flags are used", func() {
testRepo := indexTestRepo("update", "no-flags")
CopyZOTRepo(ImageRepo, testRepo)
Expand Down Expand Up @@ -300,6 +322,15 @@ var _ = Describe("1.1 registry users:", func() {
"--remove", string(multi_arch.LinuxARM64.Digest)).ExpectFailure().
MatchErrKeyWords("Error", "does not exist").Exec()
})

It("should fail if --tag is used with --output", func() {
testRepo := indexTestRepo("update", "tag-and-output")
CopyZOTRepo(ImageRepo, testRepo)
// add a manifest to the index
ORAS("manifest", "index", "update", RegistryRef(ZOTHost, testRepo, "v1"),
"--add", string(multi_arch.LinuxAMD64.Digest), "--output", "-", "--tag", "v2").
ExpectFailure().MatchErrKeyWords("--tag, --output cannot be used at the same time").Exec()
})
})
})

Expand Down Expand Up @@ -379,14 +410,14 @@ var _ = Describe("OCI image layout users:", func() {
indexRef := LayoutRef(root, "output-to-file")
filePath := filepath.Join(GinkgoT().TempDir(), "createdIndex")
ORAS("manifest", "index", "create", Flags.Layout, indexRef, string(multi_arch.LinuxAMD64.Digest), "--output", filePath).Exec()
MatchFile(filePath, multi_arch.CreatedIndex, DefaultTimeout)
MatchFile(filePath, multi_arch.OutputIndex, DefaultTimeout)
})

It("should output created index to stdout", func() {
root := PrepareTempOCI(ImageRepo)
indexRef := LayoutRef(root, "output-to-stdout")
ORAS("manifest", "index", "create", Flags.Layout, indexRef, string(multi_arch.LinuxAMD64.Digest),
"--output", "-").MatchKeyWords(multi_arch.CreatedIndex).Exec()
"--output", "-").MatchKeyWords(multi_arch.OutputIndex).Exec()
})

It("should fail if given a reference that does not exist in the repo", func() {
Expand Down Expand Up @@ -454,6 +485,26 @@ var _ = Describe("OCI image layout users:", func() {
ValidateIndex(content, expectedManifests)
})

It("should output updated index to file", func() {
root := PrepareTempOCI(ImageRepo)
filePath := filepath.Join(GinkgoT().TempDir(), "updatedIndex")
// create an index for testing purpose
ORAS("manifest", "index", "create", Flags.Layout, LayoutRef(root, "index01")).Exec()
// add a manifest to the index
ORAS("manifest", "index", "update", Flags.Layout, LayoutRef(root, "index01"),
"--add", string(multi_arch.LinuxAMD64.Digest), "--output", filePath).Exec()
MatchFile(filePath, multi_arch.OutputIndex, DefaultTimeout)
})

It("should output updated index to stdout", func() {
root := PrepareTempOCI(ImageRepo)
// create an index for testing purpose
ORAS("manifest", "index", "create", Flags.Layout, LayoutRef(root, "index01")).Exec()
// add a manifest to the index
ORAS("manifest", "index", "update", Flags.Layout, LayoutRef(root, "index01"),
"--add", string(multi_arch.LinuxAMD64.Digest), "--output", "-").MatchKeyWords(multi_arch.OutputIndex).Exec()
})

It("should tell user nothing to update if no update flags are used", func() {
root := PrepareTempOCI(ImageRepo)
indexRef := LayoutRef(root, "latest")
Expand Down

0 comments on commit 961e9f8

Please sign in to comment.