diff --git a/cmd/sealer/cmd/alpha/mount.go b/cmd/sealer/cmd/alpha/mount.go index e438af95809..18d09b17871 100644 --- a/cmd/sealer/cmd/alpha/mount.go +++ b/cmd/sealer/cmd/alpha/mount.go @@ -16,11 +16,9 @@ package alpha import ( "fmt" - "os" - "path/filepath" "strings" - "time" + "github.com/containers/buildah" "github.com/containers/storage" "github.com/olekukonko/tablewriter" "github.com/sirupsen/logrus" @@ -28,19 +26,34 @@ import ( "github.com/sealerio/sealer/common" "github.com/sealerio/sealer/pkg/define/options" - "github.com/sealerio/sealer/pkg/imageengine/buildah" + imagebuildah "github.com/sealerio/sealer/pkg/imageengine/buildah" + utilsstrings "github.com/sealerio/sealer/utils/strings" ) var longMountCmdDescription = ` -mount the cluster image to '/var/lib/sealer/data/overlay2' the directory and check whether the contents of the build image and rootfs are consistent in advance +mount the cluster image to '/var/lib/containers/storage/overlay' the directory and check whether the contents of the build image and rootfs are consistent in advance ` var exampleForMountCmd = ` sealer alpha mount(show mount list) sealer alpha mount my-image - sealer alpha mount ba15e47f5969 + sealer alpha mount imageID ` +const ( + tableHeaderMountPath = "MOUNT PATH" + tableHeaderContainerID = "CONTAINER ID" +) + +type MountService struct { + table *tablewriter.Table + engine *imagebuildah.Engine + store storage.Store + images []storage.Image + containers []storage.Container + builders []*buildah.Builder +} + func NewMountCmd() *cobra.Command { mountCmd := &cobra.Command{ Use: "mount", @@ -49,88 +62,142 @@ func NewMountCmd() *cobra.Command { Example: exampleForMountCmd, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - var ( - path string - imageID string - ) - - engine, err := buildah.NewBuildahImageEngine(options.EngineGlobalConfigurations{}) - if err != nil { - return err - } - - store := engine.ImageStore() - images, err := store.Images() + mountInfo, err := NewMountService() if err != nil { return err } //output mount list if len(args) == 0 { - if err := mountList(images); err != nil { + if err := mountInfo.Show(); err != nil { return err } return nil } - for _, i := range images { - for _, name := range i.Names { - if name == args[0] || strings.Contains(i.ID, args[0]) { - imageID = i.ID - path = filepath.Join(common.DefaultLayerDir, imageID) - } - } - } - - cid, err := engine.CreateContainer(&options.FromOptions{ - Image: args[0], - Quiet: false, - }) + _, err = mountInfo.Mount(args[0]) if err != nil { return err } + return nil + }, + } + return mountCmd +} - //too fast to mount. - time.Sleep(time.Second * 1) +func NewMountService() (MountService, error) { + engine, err := imagebuildah.NewBuildahImageEngine(options.EngineGlobalConfigurations{}) + if err != nil { + return MountService{}, err + } + store := engine.ImageStore() + containers, err := store.Containers() + if err != nil { + return MountService{}, err + } + images, err := store.Images() + if err != nil { + return MountService{}, err + } - mounts, err := engine.Mount(&options.MountOptions{Containers: []string{cid}}) - if err != nil { - return err - } + builders, err := buildah.OpenAllBuilders(store) + if err != nil { + return MountService{}, err + } - // remove destination dir if it exists, otherwise the Symlink will fail. - if _, err = os.Stat(path); err == nil { - return fmt.Errorf("destination directionay %s exists, you should remove it first", path) - } + table := tablewriter.NewWriter(common.StdOut) + table.SetHeader([]string{tableHeaderContainerID, tableHeaderMountPath}) + + return MountService{ + table: table, + engine: engine, + store: store, + images: images, + containers: containers, + builders: builders, + }, nil +} - mountPoint := mounts[0].MountPoint - if err := os.Symlink(mountPoint, path); err != nil { - return err +func (m MountService) Show() error { + clients, err := buildah.OpenAllBuilders(m.store) + if err != nil { + return fmt.Errorf("reading build Containers: %w", err) + } + for _, client := range clients { + mounted, err := client.Mounted() + if err != nil { + return err + } + for _, container := range m.containers { + if client.ContainerID == container.ID && mounted { + containerID := imagebuildah.TruncateID(client.ContainerID, true) + m.table.Append([]string{containerID, client.MountPoint}) } + } + } + m.table.Render() + return nil +} - logrus.Infof("mount cluster image %s to %s successful", args[0], path) - return nil - }, +func (m MountService) getMountedImageID(container storage.Container) string { + var imageID string + for _, image := range m.images { + if container.ImageID == image.ID { + imageID = image.ID + } } - return mountCmd + return imageID } -func mountList(images []storage.Image) error { - table := tablewriter.NewWriter(common.StdOut) - table.SetHeader([]string{imageName, "mountpath"}) - for _, i := range images { - for _, name := range i.Names { - err := filepath.Walk(common.DefaultLayerDir, func(path string, f os.FileInfo, err error) error { - if f.Name() == i.ID { - table.Append([]string{name, filepath.Join(common.DefaultLayerDir, i.ID)}) - } - return nil - }) - if err != nil { - return err +func (m MountService) getImageID(name string) string { + for _, image := range m.images { + if strings.HasPrefix(image.ID, name) { + return image.ID + } + for _, n := range image.Names { + if name == n { + return image.ID } } } - table.Render() - return nil + return "" +} + +func (m MountService) Mount(imageNameOrID string) (string, error) { + var imageIDList []string + + for _, builder := range m.builders { + mounted, err := builder.Mounted() + if err != nil { + return "", err + } + for _, container := range m.containers { + if builder.ContainerID == container.ID && mounted { + imageID := m.getMountedImageID(container) + imageIDList = append(imageIDList, imageID) + } + } + } + + imageID := m.getImageID(imageNameOrID) + ok := utilsstrings.IsInSlice(imageID, imageIDList) + if ok { + logrus.Warnf("this image has already been mounted, please do not repeat the operation") + return "", nil + } + cid, err := m.engine.CreateContainer(&options.FromOptions{ + Image: imageID, + Quiet: false, + }) + if err != nil { + return "", err + } + mounts, err := m.engine.Mount(&options.MountOptions{Containers: []string{cid}}) + if err != nil { + return "", err + } + mountPoint := mounts[0].MountPoint + logrus.Infof("mount cluster image %s to %s successful", imageNameOrID, mountPoint) + + return mountPoint, nil } diff --git a/cmd/sealer/cmd/alpha/umount.go b/cmd/sealer/cmd/alpha/umount.go index 0d6716da702..2fe5521d294 100644 --- a/cmd/sealer/cmd/alpha/umount.go +++ b/cmd/sealer/cmd/alpha/umount.go @@ -15,34 +15,27 @@ package alpha import ( + "context" "fmt" - "io/fs" - "io/ioutil" - "os" - "path/filepath" - "strings" - "github.com/sealerio/sealer/common" - imagecommon "github.com/sealerio/sealer/pkg/define/options" - "github.com/sealerio/sealer/pkg/imageengine" - "github.com/sealerio/sealer/pkg/imageengine/buildah" - - "github.com/containers/storage" + "github.com/containers/buildah" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + + imagebuildah "github.com/sealerio/sealer/pkg/imageengine/buildah" ) -var umountAll bool +var ( + umountAll bool -var longUmountCmdDescription = ` + longUmountCmdDescription = ` umount the cluster image and delete the mount directory ` - -var exampleForUmountCmd = ` - sealer alpha umount my-image - sealer alpha umount ba15e47f5969 + exampleForUmountCmd = ` + sealer alpha umount containerID sealer alpha umount --all ` +) func NewUmountCmd() *cobra.Command { umountCmd := &cobra.Command{ @@ -52,97 +45,26 @@ func NewUmountCmd() *cobra.Command { Example: exampleForUmountCmd, Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - var containerID string - var imgName string - if len(args) == 0 && !umountAll { - return fmt.Errorf("you must input imageName Or imageIp") - } - - imageEngine, err := imageengine.NewImageEngine(imagecommon.EngineGlobalConfigurations{}) - if err != nil { - return err - } - - engine, err := buildah.NewBuildahImageEngine(imagecommon.EngineGlobalConfigurations{}) - if err != nil { - return err - } - - store := engine.ImageStore() - containers, err := store.Containers() - if err != nil { - return err + return fmt.Errorf("you must input imageName Or containerID") } - files, err := ioutil.ReadDir(common.DefaultLayerDir) + umountInfo, err := NewMountService() if err != nil { return err } - // umount all cluster image if umountAll { - for _, c := range containers { - if err := imageEngine.RemoveContainer(&imagecommon.RemoveContainerOptions{ - ContainerNamesOrIDs: []string{c.ID}}, - ); err != nil { - return err - } - } - - for _, file := range files { - if err := os.RemoveAll(filepath.Join(common.DefaultLayerDir, file.Name())); err != nil { - return err - } - } - logrus.Infof("umount all cluster image successful") - return nil - } - - images, err := store.Images() - if err != nil { - return err - } - - for _, image := range images { - for _, name := range image.Names { - if strings.Contains(image.ID, args[0]) { - imgName = name - } - } - } - - for _, c := range containers { - if strings.Contains(c.ImageID, args[0]) { - containerID = c.ID - break - } - - id, err := getImageID(images, args[0]) - if err != nil { + if err := umountInfo.UmountAllContainers(); err != nil { return err } - if c.ImageID == id { - containerID = c.ID - imgName = args[0] - } + logrus.Infof("successful to umount all sealer image") + return nil } - if err := imageEngine.RemoveContainer(&imagecommon.RemoveContainerOptions{ - ContainerNamesOrIDs: []string{containerID}}, - ); err != nil { + if err := umountInfo.Umount(args[0]); err != nil { return err } - - for _, file := range files { - for _, image := range images { - if err := removeContainerDir(image, file, imgName); err != nil { - return err - } - } - } - - logrus.Infof("umount cluster image %s successful", args[0]) return nil }, } @@ -150,23 +72,31 @@ func NewUmountCmd() *cobra.Command { return umountCmd } -func getImageID(images []storage.Image, imageName string) (string, error) { - for _, image := range images { - for _, n := range image.Names { - if n == imageName { - return image.ID, nil - } - } +func (m MountService) Umount(containerID string) error { + client, err := imagebuildah.OpenBuilder(context.TODO(), m.store, containerID) + if err != nil { + return fmt.Errorf("failed to reading build container %s: %w", containerID, err) + } + + if err := client.Unmount(); err != nil { + return fmt.Errorf("failed to unmount container %q: %w", client.Container, err) } - return "", fmt.Errorf("failed to get container id") + logrus.Infof("umount %s successful", containerID) + return nil } -func removeContainerDir(image storage.Image, file fs.FileInfo, imageName string) error { - for _, n := range image.Names { - if n == imageName && file.Name() == image.ID { - if err := os.RemoveAll(filepath.Join(common.DefaultLayerDir, file.Name())); err != nil { - return err - } +func (m MountService) UmountAllContainers() error { + clients, err := buildah.OpenAllBuilders(m.store) + if err != nil { + return fmt.Errorf("reading build Containers: %w", err) + } + for _, client := range clients { + if client.MountPoint == "" { + continue + } + + if err := client.Unmount(); err != nil { + return fmt.Errorf("failed to unmount container %q: %w", client.Container, err) } } return nil diff --git a/pkg/imageengine/buildah/commit.go b/pkg/imageengine/buildah/commit.go index 73617f80146..98befff1ce2 100644 --- a/pkg/imageengine/buildah/commit.go +++ b/pkg/imageengine/buildah/commit.go @@ -56,7 +56,7 @@ func (engine *Engine) Commit(opts *options.CommitOptions) (string, error) { ctx := getContext() store := engine.ImageStore() - builder, err := openBuilder(ctx, store, name) + builder, err := OpenBuilder(ctx, store, name) if err != nil { return "", errors.Wrapf(err, "error reading build container %q", name) } diff --git a/pkg/imageengine/buildah/common.go b/pkg/imageengine/buildah/common.go index 6e20163372b..98a19dd9b0c 100644 --- a/pkg/imageengine/buildah/common.go +++ b/pkg/imageengine/buildah/common.go @@ -79,7 +79,7 @@ func getStore(configurations *options.EngineGlobalConfigurations) (storage.Store return store, err } -func openBuilder(ctx context.Context, store storage.Store, name string) (builder *buildah.Builder, err error) { +func OpenBuilder(ctx context.Context, store storage.Store, name string) (builder *buildah.Builder, err error) { if name != "" { builder, err = buildah.OpenBuilder(store, name) if os.IsNotExist(errors.Cause(err)) { diff --git a/pkg/imageengine/buildah/config.go b/pkg/imageengine/buildah/config.go index 651e9312218..dc03da9cc48 100644 --- a/pkg/imageengine/buildah/config.go +++ b/pkg/imageengine/buildah/config.go @@ -31,7 +31,7 @@ func (engine *Engine) Config(opts *options.ConfigOptions) error { ctx := getContext() store := engine.ImageStore() - builder, err := openBuilder(ctx, store, name) + builder, err := OpenBuilder(ctx, store, name) if err != nil { return errors.Wrapf(err, "error reading build container %q", name) } diff --git a/pkg/imageengine/buildah/copy.go b/pkg/imageengine/buildah/copy.go index 89797644dc1..488e62eab9a 100644 --- a/pkg/imageengine/buildah/copy.go +++ b/pkg/imageengine/buildah/copy.go @@ -53,7 +53,7 @@ func (engine *Engine) Copy(opts *options.CopyOptions) error { return errors.Errorf("--ignorefile option requires that you specify a context dir using --contextdir") } - builder, err := openBuilder(getContext(), store, name) + builder, err := OpenBuilder(getContext(), store, name) if err != nil { return errors.Wrapf(err, "error reading build container %q", name) } diff --git a/pkg/imageengine/buildah/images.go b/pkg/imageengine/buildah/images.go index 96fd6ea6d54..cc9c2828a94 100644 --- a/pkg/imageengine/buildah/images.go +++ b/pkg/imageengine/buildah/images.go @@ -149,7 +149,7 @@ func formatImagesJSON(images []*libimage.Image, opts imageOptions) error { Created: created.Unix(), CreatedAt: units.HumanDuration(time.Since(created)) + " ago", Digest: image.Digest().String(), - ID: truncateID(image.ID(), opts.truncate), + ID: TruncateID(image.ID(), opts.truncate), Names: image.Names(), ReadOnly: image.IsReadOnly(), Size: formattedSize(size), @@ -192,7 +192,7 @@ func formatImages(images []*libimage.Image, opts imageOptions) error { outputParam.Created = created.Unix() outputParam.CreatedAt = units.HumanDuration(time.Since(created)) + " ago" outputParam.Digest = image.Digest().String() - outputParam.ID = truncateID(image.ID(), opts.truncate) + outputParam.ID = TruncateID(image.ID(), opts.truncate) outputParam.Size = formattedSize(size) outputParam.ReadOnly = image.IsReadOnly() @@ -238,7 +238,7 @@ func formatHistory(history []string, name, tag string) string { return strings.Join(history, ", ") } -func truncateID(id string, truncate bool) string { +func TruncateID(id string, truncate bool) string { if !truncate { return "sha256:" + id } diff --git a/pkg/imageengine/buildah/mount.go b/pkg/imageengine/buildah/mount.go index 091db03cd0c..4463586377b 100644 --- a/pkg/imageengine/buildah/mount.go +++ b/pkg/imageengine/buildah/mount.go @@ -42,7 +42,7 @@ func (engine *Engine) Mount(opts *options.MountOptions) ([]options.JSONMount, er } for _, name := range containers { - builder, err := openBuilder(getContext(), store, name) + builder, err := OpenBuilder(getContext(), store, name) if err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) diff --git a/pkg/imageengine/buildah/remove_container.go b/pkg/imageengine/buildah/remove_container.go index e185cd5ba31..66f8dbeee4b 100644 --- a/pkg/imageengine/buildah/remove_container.go +++ b/pkg/imageengine/buildah/remove_container.go @@ -53,7 +53,7 @@ func (engine *Engine) RemoveContainer(opts *options.RemoveContainerOptions) erro } } else { for _, name := range opts.ContainerNamesOrIDs { - builder, err := openBuilder(getContext(), store, name) + builder, err := OpenBuilder(getContext(), store, name) if err != nil { lastError = util.WriteError(os.Stderr, errors.Wrapf(err, "%s %q", delContainerErrStr, name), lastError) continue