From efaf3d6b5c86c488938ee209a44c3893fb9e7f96 Mon Sep 17 00:00:00 2001 From: "Zheao.Li" Date: Mon, 9 Jan 2023 11:58:04 +0800 Subject: [PATCH] [Refactor] Refactor the load command flagging process Signed-off-by: Zheao.Li --- cmd/nerdctl/load.go | 112 +++++---------------------------- pkg/api/types/load_types.go | 27 ++++++++ pkg/cmd/load/load.go | 119 ++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 97 deletions(-) create mode 100644 pkg/api/types/load_types.go create mode 100644 pkg/cmd/load/load.go diff --git a/cmd/nerdctl/load.go b/cmd/nerdctl/load.go index 28c3d0de9f1..b2fb4753930 100644 --- a/cmd/nerdctl/load.go +++ b/cmd/nerdctl/load.go @@ -17,19 +17,8 @@ package main import ( - "errors" - "fmt" - "io" - "os" - - "github.com/containerd/containerd" - "github.com/containerd/containerd/archive/compression" - "github.com/containerd/containerd/images" - "github.com/containerd/containerd/images/archive" - "github.com/containerd/containerd/platforms" "github.com/containerd/nerdctl/pkg/api/types" - "github.com/containerd/nerdctl/pkg/clientutil" - "github.com/containerd/nerdctl/pkg/platformutil" + "github.com/containerd/nerdctl/pkg/cmd/load" "github.com/spf13/cobra" ) @@ -56,106 +45,35 @@ func newLoadCommand() *cobra.Command { return loadCommand } -func loadAction(cmd *cobra.Command, _ []string) error { - in := cmd.InOrStdin() +func processLoadCommandFlags(cmd *cobra.Command) (types.LoadCommandOptions, error) { input, err := cmd.Flags().GetString("input") if err != nil { - return err + return types.LoadCommandOptions{}, err } globalOptions, err := processRootCmdFlags(cmd) if err != nil { - return err - } - if input != "" { - f, err := os.Open(input) - if err != nil { - return err - } - defer f.Close() - in = f - } else { - // check if stdin is empty. - stdinStat, err := os.Stdin.Stat() - if err != nil { - return err - } - if stdinStat.Size() == 0 && (stdinStat.Mode()&os.ModeNamedPipe) == 0 { - return errors.New("stdin is empty and input flag is not specified") - } - } - decompressor, err := compression.DecompressStream(in) - if err != nil { - return err + return types.LoadCommandOptions{}, err } - allPlatforms, err := cmd.Flags().GetBool("all-platforms") if err != nil { - return err + return types.LoadCommandOptions{}, err } platform, err := cmd.Flags().GetStringSlice("platform") if err != nil { - return err + return types.LoadCommandOptions{}, err } - platMC, err := platformutil.NewMatchComparer(allPlatforms, platform) - if err != nil { - return err - } - - return loadImage(decompressor, cmd, globalOptions, platMC, false) + return types.LoadCommandOptions{ + GOptions: *globalOptions, + Input: input, + Platform: platform, + AllPlatforms: allPlatforms, + }, nil } -func loadImage(in io.Reader, cmd *cobra.Command, globalOptions *types.GlobalCommandOptions, platMC platforms.MatchComparer, quiet bool) error { - // In addition to passing WithImagePlatform() to client.Import(), we also need to pass WithDefaultPlatform() to NewClient(). - // Otherwise unpacking may fail. - - client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), globalOptions.Namespace, globalOptions.Address, containerd.WithDefaultPlatform(platMC)) - if err != nil { - return err - } - defer cancel() - - r := &readCounter{Reader: in} - imgs, err := client.Import(ctx, r, containerd.WithDigestRef(archive.DigestTranslator(globalOptions.Snapshotter)), containerd.WithSkipDigestRef(func(name string) bool { return name != "" }), containerd.WithImportPlatform(platMC)) +func loadAction(cmd *cobra.Command, _ []string) error { + options, err := processLoadCommandFlags(cmd) if err != nil { - if r.N == 0 { - // Avoid confusing "unrecognized image format" - return errors.New("no image was built") - } - if errors.Is(err, images.ErrEmptyWalk) { - err = fmt.Errorf("%w (Hint: set `--platform=PLATFORM` or `--all-platforms`)", err) - } return err } - for _, img := range imgs { - image := containerd.NewImageWithPlatform(client, img, platMC) - - // TODO: Show unpack status - if !quiet { - fmt.Fprintf(cmd.OutOrStdout(), "unpacking %s (%s)...\n", img.Name, img.Target.Digest) - } - err = image.Unpack(ctx, globalOptions.Snapshotter) - if err != nil { - return err - } - if quiet { - fmt.Fprintln(cmd.OutOrStdout(), img.Target.Digest) - } else { - fmt.Fprintf(cmd.OutOrStdout(), "Loaded image: %s\n", img.Name) - } - } - - return nil -} - -type readCounter struct { - io.Reader - N int -} - -func (r *readCounter) Read(p []byte) (int, error) { - n, err := r.Reader.Read(p) - if n > 0 { - r.N += n - } - return n, err + return load.Load(cmd.Context(), cmd.InOrStdin(), cmd.OutOrStdout(), options) } diff --git a/pkg/api/types/load_types.go b/pkg/api/types/load_types.go new file mode 100644 index 00000000000..c0b77f8ae80 --- /dev/null +++ b/pkg/api/types/load_types.go @@ -0,0 +1,27 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package types + +type LoadCommandOptions struct { + GOptions GlobalCommandOptions + // Input read from tar archive file, instead of STDIN + Input string + // Platform import content for a specific platform + Platform []string + // AllPlatforms import content for all platforms + AllPlatforms bool +} diff --git a/pkg/cmd/load/load.go b/pkg/cmd/load/load.go new file mode 100644 index 00000000000..09c0d918c37 --- /dev/null +++ b/pkg/cmd/load/load.go @@ -0,0 +1,119 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package load + +import ( + "context" + "errors" + "fmt" + "io" + "os" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/archive/compression" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/images/archive" + "github.com/containerd/containerd/platforms" + "github.com/containerd/nerdctl/pkg/api/types" + "github.com/containerd/nerdctl/pkg/clientutil" + "github.com/containerd/nerdctl/pkg/platformutil" +) + +type readCounter struct { + io.Reader + N int +} + +func (r *readCounter) Read(p []byte) (int, error) { + n, err := r.Reader.Read(p) + if n > 0 { + r.N += n + } + return n, err +} + +func Load(ctx context.Context, stdin io.Reader, stdout io.Writer, options types.LoadCommandOptions) error { + if options.Input != "" { + f, err := os.Open(options.Input) + if err != nil { + return err + } + defer f.Close() + stdin = f + } else { + // check if stdin is empty. + stdinStat, err := os.Stdin.Stat() + if err != nil { + return err + } + if stdinStat.Size() == 0 && (stdinStat.Mode()&os.ModeNamedPipe) == 0 { + return errors.New("stdin is empty and input flag is not specified") + } + } + decompressor, err := compression.DecompressStream(stdin) + if err != nil { + return err + } + platMC, err := platformutil.NewMatchComparer(options.AllPlatforms, options.Platform) + if err != nil { + return err + } + return loadImage(ctx, decompressor, stdout, options, platMC, false) +} + +func loadImage(ctx context.Context, in io.Reader, stdout io.Writer, options types.LoadCommandOptions, platMC platforms.MatchComparer, quiet bool) error { + // In addition to passing WithImagePlatform() to client.Import(), we also need to pass WithDefaultPlatform() to NewClient(). + // Otherwise unpacking may fail. + + client, ctx, cancel, err := clientutil.NewClient(ctx, options.GOptions.Namespace, options.GOptions.Address, containerd.WithDefaultPlatform(platMC)) + if err != nil { + return err + } + defer cancel() + + r := &readCounter{Reader: in} + imgs, err := client.Import(ctx, r, containerd.WithDigestRef(archive.DigestTranslator(options.GOptions.Snapshotter)), containerd.WithSkipDigestRef(func(name string) bool { return name != "" }), containerd.WithImportPlatform(platMC)) + if err != nil { + if r.N == 0 { + // Avoid confusing "unrecognized image format" + return errors.New("no image was built") + } + if errors.Is(err, images.ErrEmptyWalk) { + err = fmt.Errorf("%w (Hint: set `--platform=PLATFORM` or `--all-platforms`)", err) + } + return err + } + for _, img := range imgs { + image := containerd.NewImageWithPlatform(client, img, platMC) + + // TODO: Show unpack status + if !quiet { + fmt.Fprintf(stdout, "unpacking %s (%s)...\n", img.Name, img.Target.Digest) + } + err = image.Unpack(ctx, options.GOptions.Snapshotter) + if err != nil { + return err + } + if quiet { + fmt.Fprintln(stdout, img.Target.Digest) + } else { + fmt.Fprintf(stdout, "Loaded image: %s\n", img.Name) + } + } + + return nil +}