Skip to content

Commit

Permalink
refactor: save and load subcommand support multiple images in a singl…
Browse files Browse the repository at this point in the history
…e tar… (#3442)

* refactor: save and load subcommand support multiple images in a single tar

Signed-off-by: fengxsong <fengxsong@outlook.com>

* fix: golangci-lint

Signed-off-by: fengxsong <fengxsong@outlook.com>

* fix(main): fix e2e error

Signed-off-by: cuisongliu <cuisongliu@qq.com>

* fix(main): fix e2e error

Signed-off-by: cuisongliu <cuisongliu@qq.com>

---------

Signed-off-by: fengxsong <fengxsong@outlook.com>
Signed-off-by: cuisongliu <cuisongliu@qq.com>
Co-authored-by: cuisongliu <cuisongliu@qq.com>
  • Loading branch information
2 people authored and sealos-ci-robot committed Jun 30, 2023
1 parent 75231c2 commit 933bc8a
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 48 deletions.
4 changes: 2 additions & 2 deletions pkg/buildah/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func newBuildCommand() *cobra.Command {
fromAndBudResults := buildahcli.FromAndBudResults{}
userNSResults := buildahcli.UserNSResults{}
namespaceResults := buildahcli.NameSpaceResults{}
sopts := saveOptions{}
sopts := saverOptions{}

buildCommand := &cobra.Command{
Use: "build [CONTEXT]",
Expand Down Expand Up @@ -90,7 +90,7 @@ func newBuildCommand() *cobra.Command {
return buildCommand
}

func buildCmd(c *cobra.Command, inputArgs []string, sopts saveOptions, iopts buildahcli.BuildOptions) error {
func buildCmd(c *cobra.Command, inputArgs []string, sopts saverOptions, iopts buildahcli.BuildOptions) error {
if flagChanged(c, "logfile") {
logfile, err := os.OpenFile(iopts.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
if err != nil {
Expand Down
6 changes: 4 additions & 2 deletions pkg/buildah/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ import (
)

const (
OCIArchive string = "oci-archive"
DockerArchive string = "docker-archive"
OCIArchive string = "oci-archive"
OCIManifestDir string = "oci-dir"
DockerArchive string = "docker-archive"
DockerManifestDir string = "docker-dir"
)

var DefaultTransport = OCIArchive
Expand Down
6 changes: 3 additions & 3 deletions pkg/buildah/imagesaver.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ import (
"github.com/labring/sealos/pkg/utils/logger"
)

type saveOptions struct {
type saverOptions struct {
maxPullProcs int
enabled bool
}

func (opts *saveOptions) RegisterFlags(fs *pflag.FlagSet) {
func (opts *saverOptions) RegisterFlags(fs *pflag.FlagSet) {
fs.IntVar(&opts.maxPullProcs, "max-pull-procs", 5, "maximum number of goroutines for pulling")
fs.BoolVar(&opts.enabled, "save-image", true, "store images that parsed from the specific directories")
}

func runSaveImages(contextDir string, platforms []v1.Platform, sys *types.SystemContext, opts *saveOptions) error {
func runSaveImages(contextDir string, platforms []v1.Platform, sys *types.SystemContext, opts *saverOptions) error {
if !opts.enabled {
logger.Warn("save-image is disabled, skip pulling images")
return nil
Expand Down
106 changes: 83 additions & 23 deletions pkg/buildah/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,104 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Mostly copy from github.com/containers/podman

package buildah

import (
"errors"
"fmt"
"runtime"
"io"
"os"
"strings"

"github.com/containers/buildah/pkg/parse"
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/download"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/term"
)

type loadOptions struct {
input string
quiet bool
}

func (o *loadOptions) RegisterFlags(fs *pflag.FlagSet) {
fs.StringVarP(&o.input, "input", "i", "", "load images from specified tar archive file, default(stdin)")
fs.BoolVarP(&o.quiet, "quiet", "q", false, "suppress the output")
}

func newLoadCommand() *cobra.Command {
var (
opts = newDefaultPullOptions()
archiveName string
transport string
)
var opts = &loadOptions{}

loadCommand := &cobra.Command{
Use: "load",
Short: "Load image from archive file",
RunE: func(cmd *cobra.Command, _ []string) error {
if err := ValidateTransport(transport); err != nil {
return err
}
return pullCmd(cmd, []string{fmt.Sprintf("%s:%s", transport, archiveName)}, opts)
Short: "Load image(s) from archive file",
RunE: func(cmd *cobra.Command, args []string) error {
return load(cmd, args, opts)
},
Example: fmt.Sprintf(`%[1]s load -i kubernetes.tar`, rootCmd.CommandPath()),
}
loadCommand.SetUsageTemplate(UsageTemplate())
fs := loadCommand.Flags()
fs.String("os", runtime.GOOS, "prefer `OS` instead of the running OS for choosing images")
fs.String("arch", runtime.GOARCH, "prefer `ARCH` instead of the architecture of the machine for choosing images")
fs.StringSlice("platform", []string{parse.DefaultPlatform()}, "prefer OS/ARCH instead of the current operating system and architecture for choosing images")
fs.String("variant", "", "override the `variant` of the specified image")
fs.StringVarP(&archiveName, "input", "i", "", "load image from tar archive file")
fs.StringVarP(&transport, "transport", "t", OCIArchive,
fmt.Sprintf("load image transport from tar archive file. (available options are %s, %s)", OCIArchive, DockerArchive))
_ = markFlagsHidden(fs, flagsAssociatedWithPlatform()...)
_ = loadCommand.MarkFlagRequired("input")
opts.RegisterFlags(loadCommand.Flags())

return loadCommand
}

func load(cmd *cobra.Command, _ []string, loadOpts *loadOptions) error {
if len(loadOpts.input) > 0 {
// Download the input file if needed.
if strings.HasPrefix(loadOpts.input, "https://") || strings.HasPrefix(loadOpts.input, "http://") {
containerConfig, err := config.Default()
if err != nil {
return err
}
tmpdir, err := containerConfig.ImageCopyTmpDir()
if err != nil {
return err
}
tmpfile, err := download.FromURL(tmpdir, loadOpts.input)
if err != nil {
return err
}
defer os.Remove(tmpfile)
loadOpts.input = tmpfile
}

if _, err := os.Stat(loadOpts.input); err != nil {
return err
}
} else {
if term.IsTerminal(int(os.Stdin.Fd())) {
return errors.New("cannot read from terminal, use command-line redirection or the --input flag")
}
outFile, err := os.CreateTemp("", rootCmd.Name())
if err != nil {
return fmt.Errorf("creating file %v", err)
}
defer os.Remove(outFile.Name())
defer outFile.Close()

_, err = io.Copy(outFile, os.Stdin)
if err != nil {
return fmt.Errorf("copying file %v", err)
}
loadOpts.input = outFile.Name()
}
r, err := getRuntime(cmd)
if err != nil {
return err
}
loadOptions := &libimage.LoadOptions{}
if !loadOpts.quiet {
loadOptions.Writer = os.Stderr
}
loadedImages, err := r.Load(getContext(), loadOpts.input, loadOptions)
if err != nil {
return err
}
fmt.Println("Loaded image: " + strings.Join(loadedImages, "\nLoaded image: "))
return nil
}
2 changes: 1 addition & 1 deletion pkg/buildah/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func newMergeCommand() *cobra.Command {
userNSResults := buildahcli.UserNSResults{}
namespaceResults := buildahcli.NameSpaceResults{}
buildahInfo := &buildah.BuilderInfo{}
sopts := saveOptions{}
sopts := saverOptions{}
mergeCommand := &cobra.Command{
Use: "merge",
Short: "merge multiple images into one",
Expand Down
89 changes: 74 additions & 15 deletions pkg/buildah/save.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,98 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Mostly copy from github.com/containers/podman

package buildah

import (
"errors"
"fmt"
"os"
"strings"

"github.com/containers/common/libimage"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

type saveOptions struct {
compress bool
quiet bool
multiImageArchive bool
ociAcceptUncompressedLayers bool
format string
output string
}

func (o *saveOptions) RegisterFlags(fs *pflag.FlagSet) {
fs.BoolVar(&o.compress, "compress", false, "compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)")
fs.BoolVarP(&o.quiet, "quiet", "q", false, "suppress the output")
fs.BoolVarP(&o.multiImageArchive, "multi-image-archive", "m", false, "interpret additional arguments as images not tags and create a multi-image-archive (only for docker-archive)")
fs.BoolVar(&o.ociAcceptUncompressedLayers, "uncompressed", false, "Accept uncompressed layers when copying OCI images")
fs.StringVar(&o.format, "format", OCIArchive, "save image to oci-archive, oci-dir (directory with oci manifest type), "+
"docker-archive, docker-dir (directory with v2s2 manifest type)")
fs.StringVarP(&o.output, "output", "o", "", "write to a specified file (default: stdout, which must be redirected)")
}

func (o *saveOptions) Validate() error {
if strings.Contains(o.output, ":") {
return fmt.Errorf("invalid filename (should not contain ':') %q", o.output)
}
return nil
}

func newSaveCommand() *cobra.Command {
var (
opts = newDefaultPushOptions()
archiveName string
transport string
)
var opts = &saveOptions{}

saveCommand := &cobra.Command{
Use: "save",
Short: "Save image into archive file",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := ValidateTransport(transport); err != nil {
return err
}
return pushCmd(cmd, []string{
args[0],
fmt.Sprintf("%s:%s:%s", transport, archiveName, args[0]),
}, opts)
return runSave(cmd, args, opts)
},
Example: fmt.Sprintf(`%[1]s save -o kubernetes.tar labring/kubernetes:latest`, rootCmd.CommandPath()),
}
saveCommand.SetUsageTemplate(UsageTemplate())
saveCommand.Flags().StringVarP(&archiveName, "output", "o", "", "save image into tar archive file")
opts.RegisterFlags(saveCommand.Flags())
_ = saveCommand.MarkFlagRequired("output")
saveCommand.Flags().StringVarP(&transport, "transport", "t", OCIArchive,
fmt.Sprintf("save image transport to tar archive file. (available options are %s, %s)", OCIArchive, DockerArchive))

return saveCommand
}

func runSave(cmd *cobra.Command, args []string, saveOpts *saveOptions) error {
var (
tags []string
)
if flagChanged(cmd, "compress") && saveOpts.format != DockerManifestDir {
return errors.New("--compress can only be set when --format is 'docker-dir'")
}

if err := saveOpts.Validate(); err != nil {
return err
}
if len(args) > 1 {
tags = args[1:]
}

r, err := getRuntime(cmd)
if err != nil {
return err
}
saveOptions := &libimage.SaveOptions{}
saveOptions.DirForceCompress = saveOpts.compress
saveOptions.OciAcceptUncompressedLayers = saveOpts.ociAcceptUncompressedLayers

if !saveOpts.quiet {
saveOptions.Writer = os.Stderr
}

names := []string{args[0]}
if saveOpts.multiImageArchive {
names = append(names, tags...)
} else {
saveOptions.AdditionalTags = tags
}
return r.Save(getContext(), names, saveOpts.format, saveOpts.output, saveOptions)
}
12 changes: 12 additions & 0 deletions test/e2e/images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ var _ = Describe("E2E_sealos_images_test", func() {
err = fakeClient.Image.LoadImage("k8s.tar")
utils.CheckErr(err, fmt.Sprintf("failed to load image k8s.tar: %v", err))
})

It("images SaveMultiImage", func() {
err = fakeClient.Image.PullImage("docker.io/labring/kubernetes:v1.20.1", "labring/helm:v3.8.2")
utils.CheckErr(err, fmt.Sprintf("failed to pull images: %v", err))
err = fakeClient.Image.SaveMultiImage("k8s-multi.tar", "docker.io/labring/kubernetes:v1.20.1", "labring/helm:v3.8.2")
utils.CheckErr(err, fmt.Sprintf("failed to SaveMultiImage : %v", err))
})
It("images load multi image", func() {
err = fakeClient.Image.LoadImage("k8s-multi.tar")
utils.CheckErr(err, fmt.Sprintf("failed to load multi image k8s.tar: %v", err))
})

It("images merge image", func() {
err = fakeClient.Image.Merge("new:0.1.0", []string{"docker.io/labring/kubernetes:v1.20.1", "labring/helm:v3.8.2"})
utils.CheckErr(err, fmt.Sprintf("failed to merge image new:0.1.0: %v", err))
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/run_other_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var _ = Describe("E2E_sealos_run_other_test", func() {
utils.CheckErr(err, fmt.Sprintf("failed to Run new cluster for single using tar: %v", err))
err = fakeClient.Cluster.Run("labring/helm:v3.8.2")
utils.CheckErr(err, fmt.Sprintf("failed to running image for helm: %v", err))
newImages := []string{"localhost/labring/kubernetes:v1.25.0", "labring/helm:v3.8.2"}
newImages := []string{"docker.io/labring/kubernetes:v1.25.0", "labring/helm:v3.8.2"}
fakeCheckInterface, err = checkers.NewFakeGroupClient("default", &checkers.FakeOpts{Images: newImages})
utils.CheckErr(err, fmt.Sprintf("failed to get cluster interface: %v", err))
err = fakeCheckInterface.Verify()
Expand Down
4 changes: 4 additions & 0 deletions test/e2e/suites/operators/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ func (f *fakeImageClient) SaveImage(name, file string) error {
return f.SealosCmd.ImageSave(name, file, "")
}

func (f *fakeImageClient) SaveMultiImage(file string, name ...string) error {
return f.SealosCmd.ImageMultiSave(file, name...)
}

func (f *fakeImageClient) LoadImage(file string) error {
return f.SealosCmd.ImageLoad(file)
}
Expand Down
1 change: 1 addition & 0 deletions test/e2e/suites/operators/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type FakeImageInterface interface {
DockerArchiveImage(name string) error
OCIArchiveImage(name string) error
SaveImage(name, file string) error
SaveMultiImage(file string, name ...string) error
TagImage(name, newName string) error
LoadImage(file string) error
Create(name string, short bool) ([]byte, error)
Expand Down
7 changes: 6 additions & 1 deletion test/e2e/testhelper/cmd/sealosCmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,12 @@ func (s *SealosCmd) ImageSave(image string, path string, archive string) error {
if archive == "" {
return s.Executor.AsyncExec(s.BinPath, "save", "-o", path, image)
}
return s.Executor.AsyncExec(s.BinPath, "save", "-o", path, "-t", archive, image)
return s.Executor.AsyncExec(s.BinPath, "save", "-o", path, "--format", archive, image)
}

func (s *SealosCmd) ImageMultiSave(path string, name ...string) error {
param := append([]string{"save", "-m", "--format", "docker-archive", "-o", path}, name...)
return s.Executor.AsyncExec(s.BinPath, param...)
}

func (s *SealosCmd) ImageLoad(path string) error {
Expand Down

0 comments on commit 933bc8a

Please sign in to comment.