-
Notifications
You must be signed in to change notification settings - Fork 181
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: enhance manifest fetch #541
Changes from 6 commits
190aae9
8a3cfcd
7e265c9
58ab6b6
71b3601
6f4f8c4
fc06db4
369e988
6def1b6
424c05b
672c357
5287bf1
b68cfae
0952ea9
ec9e733
e3d4aff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,26 +16,30 @@ limitations under the License. | |
package manifest | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"errors" | ||
"os" | ||
|
||
ocispec "github.com/opencontainers/image-spec/specs-go/v1" | ||
"github.com/spf13/cobra" | ||
"oras.land/oras-go/v2" | ||
"oras.land/oras-go/v2/content/oci" | ||
oerrors "oras.land/oras/cmd/oras/internal/errors" | ||
"oras.land/oras/cmd/oras/internal/option" | ||
"oras.land/oras/internal/cas" | ||
"oras.land/oras/internal/cache" | ||
) | ||
|
||
type fetchOptions struct { | ||
option.Common | ||
option.Descriptor | ||
option.Remote | ||
option.Platform | ||
option.Pretty | ||
|
||
targetRef string | ||
pretty bool | ||
mediaTypes []string | ||
fetchDescriptor bool | ||
cacheRoot string | ||
mediaTypes []string | ||
outputPath string | ||
targetRef string | ||
} | ||
|
||
func fetchCmd() *cobra.Command { | ||
|
@@ -44,6 +48,7 @@ func fetchCmd() *cobra.Command { | |
Use: "fetch [flags] <name:tag|name@digest>", | ||
Short: "[Preview] Fetch manifest of the target artifact", | ||
Long: `[Preview] Fetch manifest of the target artifact | ||
|
||
** This command is in preview and under development. ** | ||
|
||
Example - Fetch raw manifest: | ||
|
@@ -63,6 +68,11 @@ Example - Fetch manifest with prettified json result: | |
`, | ||
Args: cobra.ExactArgs(1), | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
if opts.outputPath == "-" && opts.OutputDescriptor { | ||
return errors.New("`--output -` cannot be used with `--descriptor` at the same time") | ||
} | ||
|
||
opts.cacheRoot = os.Getenv("ORAS_CACHE") | ||
return opts.ReadPassword() | ||
}, | ||
Aliases: []string{"get"}, | ||
|
@@ -72,46 +82,93 @@ Example - Fetch manifest with prettified json result: | |
}, | ||
} | ||
|
||
cmd.Flags().BoolVarP(&opts.pretty, "pretty", "", false, "output prettified manifest") | ||
cmd.Flags().BoolVarP(&opts.fetchDescriptor, "descriptor", "", false, "fetch a descriptor of the manifest") | ||
cmd.Flags().StringSliceVarP(&opts.mediaTypes, "media-type", "", nil, "accepted media types") | ||
cmd.Flags().StringVarP(&opts.outputPath, "output", "o", "", "output file path") | ||
option.ApplyFlags(&opts, cmd.Flags()) | ||
return cmd | ||
} | ||
|
||
func fetchManifest(opts fetchOptions) error { | ||
func fetchManifest(opts fetchOptions) (fetchErr error) { | ||
ctx, _ := opts.SetLoggerLevel() | ||
targetPlatform, err := opts.Parse() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
repo, err := opts.NewRepository(opts.targetRef, opts.Common) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if repo.Reference.Reference == "" { | ||
return oerrors.NewErrInvalidReference(repo.Reference) | ||
} | ||
repo.ManifestMediaTypes = opts.mediaTypes | ||
|
||
// Fetch and output | ||
var content []byte | ||
if opts.fetchDescriptor { | ||
content, err = cas.FetchDescriptor(ctx, repo, opts.targetRef, targetPlatform) | ||
} else { | ||
content, err = cas.FetchManifest(ctx, repo, opts.targetRef, targetPlatform) | ||
} | ||
targetPlatform, err := opts.Parse() | ||
if err != nil { | ||
return err | ||
} | ||
if opts.pretty { | ||
buf := bytes.NewBuffer(nil) | ||
if err = json.Indent(buf, content, "", " "); err != nil { | ||
return fmt.Errorf("failed to prettify: %w", err) | ||
|
||
var src oras.ReadOnlyTarget = repo.Manifests() | ||
if opts.cacheRoot != "" { | ||
ociStore, err := oci.New(opts.cacheRoot) | ||
if err != nil { | ||
return err | ||
} | ||
buf.WriteByte('\n') | ||
content = buf.Bytes() | ||
src = cache.New(src, ociStore) | ||
} | ||
_, err = os.Stdout.Write(content) | ||
return err | ||
|
||
var desc ocispec.Descriptor | ||
var content []byte | ||
yuehaoliang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if opts.OutputDescriptor && opts.outputPath == "" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This if block can be merged with the if block at the end:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't really follow your idea here. In the case of fetching manifest content, there still might be a need to output descriptor, eg. Current logic:
In the logic below, for
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This can be avoided by checking if the desc is an empty struct. |
||
// fetch manifest descriptor only | ||
desc, err = oras.Resolve(ctx, src, opts.targetRef, oras.DefaultResolveOptions) | ||
if err != nil { | ||
return err | ||
} | ||
} else { | ||
// fetch manifest content | ||
desc, content, err = oras.FetchBytes(ctx, src, opts.targetRef, oras.FetchBytesOptions{ | ||
FetchOptions: oras.FetchOptions{ | ||
ResolveOptions: oras.ResolveOptions{ | ||
TargetPlatform: targetPlatform, | ||
}, | ||
}, | ||
MaxBytes: 0, | ||
}) | ||
yuehaoliang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
// outputs manifest content | ||
yuehaoliang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if opts.outputPath == "" || opts.outputPath == "-" { | ||
return opts.Output(os.Stdout, content) | ||
} | ||
|
||
// save manifest content into the local file if the output path is provided | ||
file, err := os.Create(opts.outputPath) | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { | ||
if err := file.Close(); fetchErr == nil { | ||
fetchErr = err | ||
} | ||
}() | ||
|
||
if _, err = file.Write(content); err != nil { | ||
return err | ||
} | ||
yuehaoliang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// output manifest's descriptor if `--descriptor` is used | ||
if opts.OutputDescriptor { | ||
descBytes, err := json.Marshal(desc) | ||
if err != nil { | ||
return err | ||
} | ||
err = opts.Output(os.Stdout, descBytes) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code block appears everywhere if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're discussing about this change offline. |
||
|
||
return nil | ||
} |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we refactor it into the
option
package?/cc @qweeah
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created a option
Cache
.