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: --output for oras manifest index update #1502

Merged
merged 3 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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:
shizhMSFT marked this conversation as resolved.
Show resolved Hide resolved
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
Loading