diff --git a/build/kubefile/parser/image_engine_test.go b/build/kubefile/parser/image_engine_test.go index 39d48735e84..931bdc6433c 100644 --- a/build/kubefile/parser/image_engine_test.go +++ b/build/kubefile/parser/image_engine_test.go @@ -15,6 +15,8 @@ package parser import ( + "github.com/containers/common/libimage" + "github.com/opencontainers/go-digest" v12 "github.com/sealerio/sealer/pkg/define/application/v1" "github.com/sealerio/sealer/pkg/define/application/version" v1 "github.com/sealerio/sealer/pkg/define/image/v1" @@ -63,7 +65,7 @@ func (testImageEngine) Copy(opts *options.CopyOptions) error { panic("implement me") } -func (testImageEngine) Commit(opts *options.CommitOptions) error { +func (testImageEngine) Commit(opts *options.CommitOptions) (string, error) { //TODO implement me panic("implement me") } @@ -139,3 +141,31 @@ func (testImageEngine) GetSealerImageExtension(opts *options.GetImageAnnoOptions } return testExtensionWithApp, nil } + +func (testImageEngine) LookupManifest(name string) (*libimage.ManifestList, error) { + panic("implement me") +} + +func (testImageEngine) CreateManifest(name string, opts *options.ManifestCreateOpts) error { + panic("implement me") +} + +func (testImageEngine) DeleteManifests(names []string, opts *options.ManifestDeleteOpts) error { + panic("implement me") +} + +func (testImageEngine) InspectManifest(name string, opts *options.ManifestInspectOpts) error { + panic("implement me") +} + +func (testImageEngine) PushManifest(name, destSpec string, opts *options.PushOptions) error { + panic("implement me") +} + +func (testImageEngine) AddToManifest(name, imageSpec string, opts *options.ManifestAddOpts) error { + panic("implement me") +} + +func (testImageEngine) RemoveFromManifest(name string, instanceDigest digest.Digest, opts *options.ManifestRemoveOpts) error { + panic("implement me") +} diff --git a/cmd/sealer/cmd/cluster/run.go b/cmd/sealer/cmd/cluster/run.go index d7493008d72..3e0269ea792 100644 --- a/cmd/sealer/cmd/cluster/run.go +++ b/cmd/sealer/cmd/cluster/run.go @@ -223,7 +223,8 @@ func NewRunCmd() *cobra.Command { func loadPluginsFromImage(imageMountInfo []imagedistributor.ClusterImageMountInfo) (plugins []v1.Plugin, err error) { for _, info := range imageMountInfo { - if info.Platform.ToString() == platform.GetDefaultPlatform().ToString() { + defaultPlatform := platform.GetDefaultPlatform() + if info.Platform.ToString() == defaultPlatform.ToString() { plugins, err = clusterruntime.LoadPluginsFromFile(filepath.Join(info.MountDir, "plugins")) if err != nil { return diff --git a/cmd/sealer/cmd/image/build.go b/cmd/sealer/cmd/image/build.go index 5e6a7a0777a..ecf85837d21 100644 --- a/cmd/sealer/cmd/image/build.go +++ b/cmd/sealer/cmd/image/build.go @@ -15,6 +15,8 @@ package image import ( + "context" + "crypto/rand" "fmt" "io/ioutil" "os" @@ -35,7 +37,6 @@ import ( "github.com/sealerio/sealer/version" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/util/json" ) @@ -46,20 +47,18 @@ It organizes the specified Kubefile and input building context, and builds a brand new ClusterImage.` var exampleNewBuildCmd = `the current path is the context path, default build type is lite and use build cache - build: sealer build -f Kubefile -t my-kubernetes:1.19.8 . - build without cache: sealer build -f Kubefile -t my-kubernetes:1.19.8 --no-cache . - build with args: sealer build -f Kubefile -t my-kubernetes:1.19.8 --build-arg MY_ARG=abc,PASSWORD=Sealer123 . - build with image type: sealer build -f Kubefile -t my-kubernetes:1.19.8 --type=app-installer . sealer build -f Kubefile -t my-kubernetes:1.19.8 --type=kube-installer(default) . app-installer type image will not install kubernetes. +build multi-platform image: + sealer build -f Kubefile -t my-kubernetes:1.19.8 --platform linux/amd64,linux/arm64 ` // NewBuildCmd buildCmd represents the build command @@ -71,6 +70,10 @@ func NewBuildCmd() *cobra.Command { Args: cobra.MaximumNArgs(1), Example: exampleNewBuildCmd, RunE: func(cmd *cobra.Command, args []string) error { + if len(buildFlags.Tag) == 0 { + return errors.New("--tag should be specified") + } + if len(args) > 0 { buildFlags.ContextDir = args[0] } @@ -78,24 +81,17 @@ func NewBuildCmd() *cobra.Command { }, } buildCmd.Flags().StringVarP(&buildFlags.Kubefile, "file", "f", "Kubefile", "Kubefile filepath") + buildCmd.Flags().StringVarP(&buildFlags.Tag, "tag", "t", "", "specify a name for ClusterImage") //todo we can support imageList Flag to download extra container image rather than copy it to rootfs buildCmd.Flags().StringVar(&buildFlags.ImageList, "image-list", "filepath", "`pathname` of imageList filepath, if set, sealer will read its content and download extra container") buildCmd.Flags().StringVar(&buildFlags.ImageListWithAuth, "image-list-with-auth", "", "`pathname` of imageListWithAuth.yaml filepath, if set, sealer will read its content and download extra container images to rootfs(not usually used)") - buildCmd.Flags().StringVar(&buildFlags.Platform, "platform", parse.DefaultPlatform(), "set the target platform, like linux/amd64 or linux/amd64/v7") buildCmd.Flags().StringVar(&buildFlags.PullPolicy, "pull", "ifnewer", "pull policy. Allow for --pull, --pull=true, --pull=false, --pull=never, --pull=always, --pull=ifnewer") - buildCmd.Flags().BoolVar(&buildFlags.NoCache, "no-cache", false, "do not use existing cached images for building. Build from the start with a new set of cached layers.") buildCmd.Flags().StringVar(&buildFlags.ImageType, "type", v12.KubeInstaller, fmt.Sprintf("specify the image type, --type=%s, --type=%s, default is %s", v12.KubeInstaller, v12.AppInstaller, v12.KubeInstaller)) - buildCmd.Flags().StringSliceVarP(&buildFlags.Tags, "tag", "t", []string{}, "specify a name for ClusterImage") + buildCmd.Flags().StringSliceVar(&buildFlags.Platforms, "platform", []string{parse.DefaultPlatform()}, "set the target platform, --platform=linux/amd64 or --platform=linux/amd64/v7. Multi-platform will be like --platform=linux/amd64,linux/amd64/v7") buildCmd.Flags().StringSliceVar(&buildFlags.BuildArgs, "build-arg", []string{}, "set custom build args") buildCmd.Flags().StringSliceVar(&buildFlags.Annotations, "annotation", []string{}, "add annotations for image. Format like --annotation key=[value]") buildCmd.Flags().StringSliceVar(&buildFlags.Labels, "label", []string{getSealerLabel()}, "add labels for image. Format like --label key=[value]") - - requiredFlags := []string{"tag"} - for _, flag := range requiredFlags { - if err := buildCmd.MarkFlagRequired(flag); err != nil { - logrus.Fatal(err) - } - } + buildCmd.Flags().BoolVar(&buildFlags.NoCache, "no-cache", false, "do not use existing cached images for building. Build from the start with a new set of cached layers.") supportedImageType := map[string]struct{}{v12.KubeInstaller: {}, v12.AppInstaller: {}} if _, ok := supportedImageType[buildFlags.ImageType]; !ok { @@ -106,14 +102,9 @@ func NewBuildCmd() *cobra.Command { } func buildSealerImage() error { - _os, arch, variant, err := parse.Platform(buildFlags.Platform) - if err != nil { - return err - } - engine, err := imageengine.NewImageEngine(bc.EngineGlobalConfigurations{}) if err != nil { - return errors.Wrap(err, "failed to initiate a builder") + return errors.Wrap(err, "failed to initiate image engine") } kubefileParser := parser.NewParser(rootfs.GlobalManager.App().Root(), buildFlags, engine) @@ -121,7 +112,7 @@ func buildSealerImage() error { if err != nil { return err } - logrus.Debugf("the result of kubefile parse as follows:\n %+v \n", &result) + logrus.Debugf("the result of kubefile parse as follows:\n %+v \n", result) defer func() { if err2 := result.CleanLegacyContext(); err2 != nil { logrus.Warnf("error in cleaning legacy in build sealer image: %v", err2) @@ -144,6 +135,23 @@ func buildSealerImage() error { return errors.Wrap(err, "failed to marshal image extension") } + var ( + repoTag = buildFlags.Tag + randomStr = getRandomString(8) + // use temp tag to do temp image build, because after build, + // we need to download some container data loaded from rootfs to it. + tempTag = repoTag + randomStr + ) + + isMultiPlatform := len(buildFlags.Platforms) > 1 + if isMultiPlatform { + buildFlags.Manifest = tempTag + buildFlags.Tag = "" + } else { + buildFlags.Tag = tempTag + } + + // add annotations to image. Store some sealer specific information buildFlags.Kubefile = dockerfilePath buildFlags.Annotations = append(buildFlags.Annotations, fmt.Sprintf("%s=%s", v12.SealerImageExtension, string(iejson))) iid, err := engine.Build(&buildFlags) @@ -152,15 +160,76 @@ func buildSealerImage() error { } defer func() { - // the above image is intermediate image, we need to remove it when the build ends. - if err := engine.RemoveImage(&bc.RemoveImageOptions{ - ImageNamesOrIDs: []string{iid}, - Force: true, - }); err != nil { - logrus.Warnf("failed to remove image %s, you need to remove it manually: %v", iid, err) + for _, m := range []string{tempTag} { + // the above image is intermediate image, we need to remove it when the build ends. + if err := engine.RemoveImage(&bc.RemoveImageOptions{ + ImageNamesOrIDs: []string{m}, + Force: true, + }); err != nil { + logrus.Debugf("failed to remove image %s, you need to remove it manually: %v", m, err) + } } }() + if isMultiPlatform { + return commitMultiPlatformImage(tempTag, repoTag, engine) + } + + return commitSingleImage(iid, repoTag, engine) +} + +type platformedImage struct { + platform v1.Platform + imageNameOrID string +} + +func commitMultiPlatformImage(tempTag, manifest string, engine imageengine.Interface) error { + var platformedImages []platformedImage + manifestList, err := engine.LookupManifest(tempTag) + if err != nil { + return errors.Wrap(err, "failed to lookup manifest") + } + + for _, p := range buildFlags.Platforms { + _os, arch, variant, err := parse.Platform(p) + if err != nil { + return errors.Wrap(err, "failed to parse platform") + } + + img, err := manifestList.LookupInstance(context.TODO(), arch, _os, variant) + if err != nil { + return err + } + + platformedImages = append(platformedImages, + platformedImage{imageNameOrID: img.ID(), + platform: v1.Platform{OS: _os, Architecture: arch, Variant: variant}}) + } + + for _, pi := range platformedImages { + if err := applyRegistryToImage(pi.imageNameOrID, "", manifest, pi.platform, engine); err != nil { + return errors.Wrap(err, "error in apply registry data into image") + } + } + + return nil +} + +func commitSingleImage(iid string, tag string, engine imageengine.Interface) error { + _os, arch, variant, err := parse.Platform(buildFlags.Platforms[0]) + if err != nil { + return errors.Wrap(err, "failed to parse platform") + } + + if err := applyRegistryToImage(iid, tag, "", v1.Platform{OS: _os, Architecture: arch, Variant: variant}, engine); err != nil { + return errors.Wrap(err, "error in apply registry data into image") + } + + return nil +} + +func applyRegistryToImage(imageID, tag, manifest string, platform v1.Platform, engine imageengine.Interface) error { + _os, arch, variant := platform.OS, platform.Architecture, platform.Variant // this temporary file is used to execute image pull, and save it to /registry. // engine.BuildRootfs will generate an image rootfs, and link the rootfs to temporary dir(temp sealer rootfs). tmpDir, err := os.MkdirTemp("", "sealer") @@ -177,11 +246,11 @@ func buildSealerImage() error { tmpDirForLink := filepath.Join(tmpDir, "tmp-rootfs") cid, err := engine.CreateWorkingContainer(&bc.BuildRootfsOptions{ - ImageNameOrID: iid, + ImageNameOrID: imageID, DestDir: tmpDirForLink, }) if err != nil { - return err + return errors.Wrapf(err, "failed to create working container, imageid: %s", imageID) } differ := buildimage.NewRegistryDiffer(v1.Platform{ @@ -192,7 +261,7 @@ func buildSealerImage() error { // TODO optimize the differ. if err = differ.Process(tmpDirForLink, tmpDirForLink); err != nil { - return err + return errors.Wrap(err, "failed to download container images") } // download container image form `imageListWithAuth.yaml` @@ -204,13 +273,22 @@ func buildSealerImage() error { return err } - if err = engine.Commit(&bc.CommitOptions{ + id, err := engine.Commit(&bc.CommitOptions{ Format: cli.DefaultFormat(), Rm: true, ContainerID: cid, - Image: buildFlags.Tags[0], - }); err != nil { - return err + Image: tag, + Manifest: manifest, + }) + + if err != nil { + return errors.Wrapf(err, "failed to commit image tag: %s, manifest: %s", tag, manifest) + } + + if len(manifest) > 0 { + logrus.Infof("image(%s) committed to manifest %s, id: %s", platform.ToString(), manifest, id) + } else { + logrus.Infof("image(%s) named as %s, id: %s", platform.ToString(), tag, id) } return nil @@ -316,3 +394,9 @@ func getContextDir(cxtDir string) (string, error) { func getSealerLabel() string { return "io.sealer.version=" + version.Get().GitVersion } + +func getRandomString(n int) string { + randBytes := make([]byte, n/2) + _, _ = rand.Read(randBytes) + return fmt.Sprintf("%x", randBytes) +} diff --git a/cmd/sealer/cmd/image/image.go b/cmd/sealer/cmd/image/image.go index 19b5ed7fd04..0cd4b962ba7 100644 --- a/cmd/sealer/cmd/image/image.go +++ b/cmd/sealer/cmd/image/image.go @@ -20,7 +20,7 @@ import ( func NewImageCommands() []*cobra.Command { var imageCommands []*cobra.Command - imageCommands = append(imageCommands, NewBuildCmd(), NewListCmd(), NewInspectCmd(), NewLoadCmd(), + imageCommands = append(imageCommands, NewManifestCmd(), NewBuildCmd(), NewListCmd(), NewInspectCmd(), NewLoadCmd(), NewLoginCmd(), NewLogoutCmd(), NewPullCmd(), NewPushCmd(), NewRmiCmd(), NewSaveCmd(), NewSearchCmd(), NewTagCmd()) return imageCommands } diff --git a/cmd/sealer/cmd/image/manifest.go b/cmd/sealer/cmd/image/manifest.go new file mode 100644 index 00000000000..dc26ae550f1 --- /dev/null +++ b/cmd/sealer/cmd/image/manifest.go @@ -0,0 +1,292 @@ +// Copyright © 2021 Alibaba Group Holding Ltd. +// +// 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 image + +import ( + "errors" + "fmt" + + "github.com/containers/common/pkg/auth" + digest "github.com/opencontainers/go-digest" + "github.com/sealerio/sealer/pkg/define/options" + "github.com/sealerio/sealer/pkg/imageengine" + "github.com/spf13/cobra" +) + +var ( + manifestDescription = "\n Creates, modifies, and pushes manifest lists" + manifestCreateDescription = "\n Creates manifest lists." + manifestAddDescription = "\n Adds an image to a manifest list." + manifestRemoveDescription = "\n Removes an image from a manifest list." + manifestInspectDescription = "\n Display the contents of a manifest list." + manifestPushDescription = "\n Pushes manifest lists to registries." + manifestDeleteDescription = "\n Remove one or more manifest lists from local storage." + createManifestOpts options.ManifestCreateOpts + addManifestOpts options.ManifestAddOpts + removeManifestOpts options.ManifestRemoveOpts + deleteManifestOpts options.ManifestDeleteOpts + inspectManifestOpts options.ManifestInspectOpts + pushManifestOpts options.PushOptions +) + +func NewManifestCmd() *cobra.Command { + manifestCommand := &cobra.Command{ + Use: "manifest", + Short: "manipulate manifest lists", + Long: manifestDescription, + Example: `sealer manifest create localhost/my-manifest + sealer manifest add localhost/my-manifest localhost/image + sealer manifest inspect localhost/my-manifest + sealer manifest push localhost/my-manifest transport:destination + sealer manifest remove localhost/my-manifest sha256:entryManifestDigest + sealer manifest delete localhost/my-manifest`, + } + + manifestCommand.AddCommand(manifestCreateCommand()) + manifestCommand.AddCommand(manifestAddCommand()) + manifestCommand.AddCommand(manifestRemoveCommand()) + manifestCommand.AddCommand(manifestInspectCommand()) + manifestCommand.AddCommand(manifestDeleteCommand()) + manifestCommand.AddCommand(manifestPushCommand()) + return manifestCommand +} + +func manifestCreateCommand() *cobra.Command { + createCommand := &cobra.Command{ + Use: "create", + Short: "Create manifest list", + Long: manifestCreateDescription, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("at least a name must be specified for the manifest list") + } + engine, err := imageengine.NewImageEngine(options.EngineGlobalConfigurations{}) + if err != nil { + return err + } + + return engine.CreateManifest(args[0], &createManifestOpts) + }, + Example: `sealer manifest create mylist:v1.11`, + Args: cobra.MinimumNArgs(1), + } + + return createCommand +} + +func manifestAddCommand() *cobra.Command { + addCommand := &cobra.Command{ + Use: "add", + Short: "Add images to a manifest list", + Long: manifestAddDescription, + RunE: func(cmd *cobra.Command, args []string) error { + var ( + name, imageSpec string + ) + + switch len(args) { + case 0, 1: + return errors.New("at least a manifest name and an image name to add must be specified") + case 2: + name = args[0] + if name == "" { + return fmt.Errorf(`invalid manifest name "%s" `, args[0]) + } + imageSpec = args[1] + if imageSpec == "" { + return fmt.Errorf(`invalid image name "%s" `, args[1]) + } + default: + return errors.New("at least two arguments are necessary: manifest name and an image name to add to list ") + } + + engine, err := imageengine.NewImageEngine(options.EngineGlobalConfigurations{}) + if err != nil { + return err + } + + return engine.AddToManifest(name, imageSpec, &addManifestOpts) + }, + Example: `sealer manifest add mylist:v1.11 image:v1.11-amd64 + sealer manifest add mylist:v1.11 transport:imageName`, + Args: cobra.MinimumNArgs(2), + } + + flags := addCommand.Flags() + flags.StringVar(&addManifestOpts.Os, "os", "", "override the `OS` of the specified image") + flags.StringVar(&addManifestOpts.Arch, "arch", "", "override the `architecture` of the specified image") + flags.StringVar(&addManifestOpts.Variant, "variant", "", "override the `variant` of the specified image") + flags.StringVar(&addManifestOpts.OsVersion, "os-version", "", "override the OS `version` of the specified image") + flags.StringSliceVar(&addManifestOpts.OsFeatures, "os-features", nil, "override the OS `features` of the specified image") + flags.StringSliceVar(&addManifestOpts.Annotations, "annotation", nil, "set an `annotation` for the specified image") + flags.BoolVar(&addManifestOpts.All, "all", false, "add all of the list's images if the image is a list") + + return addCommand +} + +func manifestRemoveCommand() *cobra.Command { + removeCommand := &cobra.Command{ + Use: "remove", + Short: "Remove an entry from a manifest list", + Long: manifestRemoveDescription, + RunE: func(cmd *cobra.Command, args []string) error { + var ( + name string + instanceDigest digest.Digest + ) + + engine, err := imageengine.NewImageEngine(options.EngineGlobalConfigurations{}) + if err != nil { + return err + } + + switch len(args) { + case 0, 1: + return errors.New("at least a list image and one or more instance digests must be specified ") + case 2: + name = args[0] + if name == "" { + return fmt.Errorf(`invalid image name "%s" `, args[0]) + } + instanceSpec := args[1] + if instanceSpec == "" { + return fmt.Errorf(`invalid instance "%s" `, args[1]) + } + d, err := digest.Parse(instanceSpec) + if err != nil { + return fmt.Errorf(`invalid instance "%s": %v `, args[1], err) + } + instanceDigest = d + default: + return errors.New("at least two arguments are necessary: list and digest of instance to remove from list ") + } + + return engine.RemoveFromManifest(name, instanceDigest, &removeManifestOpts) + }, + Example: `sealer manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`, + Args: cobra.MinimumNArgs(2), + } + + return removeCommand +} + +func manifestInspectCommand() *cobra.Command { + inspectCommand := &cobra.Command{ + Use: "inspect", + Short: "Display the contents of a manifest list", + Long: manifestInspectDescription, + RunE: func(cmd *cobra.Command, args []string) error { + var ( + name string + ) + switch len(args) { + case 0: + return errors.New("at least a source list ID must be specified") + case 1: + name = args[0] + if name == "" { + return fmt.Errorf(`invalid manifest name "%s" `, name) + } + default: + return errors.New("only one argument is necessary for inspect: an manifest name") + } + + engine, err := imageengine.NewImageEngine(options.EngineGlobalConfigurations{}) + if err != nil { + return err + } + + return engine.InspectManifest(name, &inspectManifestOpts) + }, + Example: `sealer manifest inspect mylist:v1.11`, + Args: cobra.MinimumNArgs(1), + } + return inspectCommand +} + +func manifestDeleteCommand() *cobra.Command { + deleteCommand := &cobra.Command{ + Use: "delete", + Short: "Delete manifest list", + Long: manifestDeleteDescription, + RunE: func(cmd *cobra.Command, args []string) error { + engine, err := imageengine.NewImageEngine(options.EngineGlobalConfigurations{}) + if err != nil { + return err + } + + return engine.DeleteManifests(args, &deleteManifestOpts) + }, + Example: `sealer manifest delete mylist:v1.11`, + Args: cobra.MinimumNArgs(1), + } + return deleteCommand +} + +func manifestPushCommand() *cobra.Command { + pushCommand := &cobra.Command{ + Use: "push", + Short: "Push a manifest list to a registry", + Long: manifestPushDescription, + RunE: func(cmd *cobra.Command, args []string) error { + if err := auth.CheckAuthFile(pushManifestOpts.Authfile); err != nil { + return err + } + + var ( + name, destSpec string + ) + + switch len(args) { + case 0: + return errors.New("at least a source list ID must be specified ") + case 1: + name = args[0] + destSpec = args[0] + case 2: + name = args[0] + destSpec = args[1] + if name == "" { + return fmt.Errorf(`invalid manifest name "%s"`, name) + } + if destSpec == "" { + return fmt.Errorf(`invalid image name "%s"`, destSpec) + } + default: + return errors.New("need one Or two arguments are necessary to push: source and destination ") + } + + engine, err := imageengine.NewImageEngine(options.EngineGlobalConfigurations{}) + if err != nil { + return err + } + + return engine.PushManifest(name, destSpec, &pushManifestOpts) + }, + Example: `sealer manifest push mylist:v1.11 transport:imageName`, + Args: cobra.MinimumNArgs(1), + } + + flags := pushCommand.Flags() + flags.BoolVar(&pushManifestOpts.Rm, "rm", false, "remove the manifest list if push succeeds") + flags.BoolVar(&pushManifestOpts.All, "all", false, "also push the images in the list") + flags.StringVar(&pushManifestOpts.Authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&pushManifestOpts.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry") + flags.StringVarP(&pushManifestOpts.Format, "format", "f", "", "manifest type (oci or v2s2) to attempt to use when pushing the manifest list (default is manifest type of source)") + flags.BoolVar(&pushManifestOpts.TLSVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") + flags.BoolVarP(&pushManifestOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists") + + return pushCommand +} diff --git a/cmd/sealer/cmd/image/push.go b/cmd/sealer/cmd/image/push.go index 27ca6643cfb..759cdef86cc 100644 --- a/cmd/sealer/cmd/image/push.go +++ b/cmd/sealer/cmd/image/push.go @@ -52,5 +52,6 @@ func NewPushCmd() *cobra.Command { // tls-verify is not working currently pushCmd.Flags().BoolVar(&pushOpts.TLSVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry. (not work currently)") pushCmd.Flags().BoolVarP(&pushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing images") + pushCmd.Flags().BoolVar(&pushOpts.All, "all", false, "also push the images in the list") return pushCmd } diff --git a/pkg/define/options/options.go b/pkg/define/options/options.go index c6c53d397b0..a0dc50cff9e 100644 --- a/pkg/define/options/options.go +++ b/pkg/define/options/options.go @@ -17,16 +17,19 @@ package options // BuildOptions should be out of buildah scope. type BuildOptions struct { Kubefile string - Tags []string - NoCache bool - Base bool - BuildArgs []string - Platform string ContextDir string PullPolicy string + ImageType string + Manifest string + Tag string + BuildArgs []string + Platforms []string Labels []string Annotations []string - ImageType string + NoCache bool + Base bool + Tags []string + Platform string ImageList string ImageListWithAuth string } @@ -82,14 +85,15 @@ type LogoutOptions struct { } type PushOptions struct { - Authfile string - CertDir string - Format string - Rm bool - Quiet bool - TLSVerify bool - Image string - All bool + Authfile string + CertDir string + Format string + Image string + Destination string + Rm bool + Quiet bool + TLSVerify bool + All bool } type PullOptions struct { @@ -161,3 +165,25 @@ type TagOptions struct { ImageNameOrID string Tags []string } + +type ManifestCreateOpts struct { +} + +type ManifestInspectOpts struct { +} + +type ManifestDeleteOpts struct { +} + +type ManifestAddOpts struct { + Os string + Arch string + Variant string + OsVersion string + OsFeatures []string + Annotations []string + All bool +} + +type ManifestRemoveOpts struct { +} diff --git a/pkg/imagedistributor/mount.go b/pkg/imagedistributor/mount.go index 37e7d2865ed..a4cae535c8e 100644 --- a/pkg/imagedistributor/mount.go +++ b/pkg/imagedistributor/mount.go @@ -98,7 +98,6 @@ func (c ImagerMounter) Mount(imageName string) ([]ClusterImageMountInfo, error) if err != nil { return nil, fmt.Errorf("failed to mount image with platform %s:%v", platform.ToString(), err) } - imageMountInfos = append(imageMountInfos, ClusterImageMountInfo{ Hosts: hosts, Platform: platform, diff --git a/pkg/imageengine/buildah/build.go b/pkg/imageengine/buildah/build.go index 22b74b23c89..d2e214492c4 100644 --- a/pkg/imageengine/buildah/build.go +++ b/pkg/imageengine/buildah/build.go @@ -339,11 +339,12 @@ func getKubefiles(files []string) []string { func (engine *Engine) migrateFlags2Wrapper(opts *options.BuildOptions, wrapper *buildFlagsWrapper) error { flags := engine.Flags() // imageengine cache related flags - // cache is enabled when "layers" is true & "no-cache" is false - wrapper.Layers = !opts.NoCache + // cache intermediate layers during build, it is enabled when len(opts.Platforms) <= 1 and "no-cache" is false + wrapper.Layers = len(opts.Platforms) <= 1 && !opts.NoCache wrapper.NoCache = opts.NoCache // tags. Like -t kubernetes:v1.16 - wrapper.Tag = opts.Tags + wrapper.Tag = []string{opts.Tag} + wrapper.Manifest = opts.Manifest // Hardcoded for network configuration. // check parse.NamespaceOptions for detailed logic. // this network setup for stage container, especially for RUN wget and so on. @@ -355,7 +356,7 @@ func (engine *Engine) migrateFlags2Wrapper(opts *options.BuildOptions, wrapper * // set platform to the flags in buildah // check the detail in parse.PlatformsFromOptions - err = flags.Set("platform", opts.Platform) + err = flags.Set("platform", strings.Join(opts.Platforms, ",")) if err != nil { return err } diff --git a/pkg/imageengine/buildah/commit.go b/pkg/imageengine/buildah/commit.go index 276cbd45ddd..124f1c29c03 100644 --- a/pkg/imageengine/buildah/commit.go +++ b/pkg/imageengine/buildah/commit.go @@ -18,8 +18,6 @@ import ( "os" "time" - "github.com/sealerio/sealer/pkg/define/options" - "github.com/containers/buildah" "github.com/containers/buildah/define" "github.com/containers/buildah/util" @@ -27,21 +25,24 @@ import ( storageTransport "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" + "github.com/containers/storage" "github.com/pkg/errors" + "github.com/sealerio/sealer/pkg/define/options" "github.com/sirupsen/logrus" ) -func (engine *Engine) Commit(opts *options.CommitOptions) error { +func (engine *Engine) Commit(opts *options.CommitOptions) (string, error) { var dest types.ImageReference if len(opts.ContainerID) == 0 { - return errors.Errorf("container ID must be specified") + return "", errors.Errorf("container ID must be specified") } - if len(opts.Image) == 0 { - return errors.Errorf("image name should be specified") + if len(opts.Image) == 0 && len(opts.Manifest) == 0 { + return "", errors.Errorf("image name should be specified") } name := opts.ContainerID image := opts.Image + manifest := opts.Manifest compress := define.Gzip if opts.DisableCompression { compress = define.Uncompressed @@ -49,14 +50,14 @@ func (engine *Engine) Commit(opts *options.CommitOptions) error { format, err := getImageType(opts.Format) if err != nil { - return err + return "", err } ctx := getContext() store := engine.ImageStore() builder, err := openBuilder(ctx, store, name) if err != nil { - return errors.Wrapf(err, "error reading build container %q", name) + return "", errors.Wrapf(err, "error reading build container %q", name) } systemCxt := engine.SystemContext() @@ -64,24 +65,16 @@ func (engine *Engine) Commit(opts *options.CommitOptions) error { // If the user specified an image, we may need to massage it a bit if // no transport is specified. // TODO we support commit to local image only, we'd better limit the input of name - if dest, err = alltransports.ParseImageName(image); err != nil { - candidates, err := shortnames.ResolveLocally(systemCxt, image) + if image != "" { + dest, err = getImageReference(image, store, systemCxt) if err != nil { - return err - } - if len(candidates) == 0 { - return errors.Errorf("no candidate tags for target image name %q", image) + return "", err } - dest2, err2 := storageTransport.Transport.ParseStoreReference(store, candidates[0].String()) - if err2 != nil { - return errors.Wrapf(err, "error parsing target image name %q", image) - } - dest = dest2 } options := buildah.CommitOptions{ PreferredManifestType: format, - Manifest: opts.Manifest, + Manifest: manifest, Compression: compress, SystemContext: systemCxt, Squash: opts.Squash, @@ -96,7 +89,7 @@ func (engine *Engine) Commit(opts *options.CommitOptions) error { } id, ref, _, err := builder.Commit(ctx, dest, options) if err != nil { - return util.GetFailureCause(err, errors.Wrapf(err, "error committing container %q to %q", builder.Container, image)) + return "", util.GetFailureCause(err, errors.Wrapf(err, "error committing container %q to %q", builder.Container, image)) } if ref != nil && id != "" { logrus.Debugf("wrote image %s with ID %s", ref, id) @@ -109,7 +102,28 @@ func (engine *Engine) Commit(opts *options.CommitOptions) error { } if opts.Rm { - return builder.Delete() + return id, builder.Delete() + } + return id, nil +} + +func getImageReference(image string, store storage.Store, systemCxt *types.SystemContext) (types.ImageReference, error) { + dest, err := alltransports.ParseImageName(image) + if err == nil { + return dest, nil } - return nil + + candidates, err := shortnames.ResolveLocally(systemCxt, image) + if err != nil { + return nil, err + } + if len(candidates) == 0 { + return nil, errors.Errorf("no candidate tags for target image name %q", image) + } + dest, err = storageTransport.Transport.ParseStoreReference(store, candidates[0].String()) + if err != nil { + return nil, errors.Wrapf(err, "error parsing target image name %q", image) + } + + return dest, nil } diff --git a/pkg/imageengine/buildah/copy.go b/pkg/imageengine/buildah/copy.go index fcfedbf7707..89797644dc1 100644 --- a/pkg/imageengine/buildah/copy.go +++ b/pkg/imageengine/buildah/copy.go @@ -17,6 +17,8 @@ package buildah import ( "fmt" + "github.com/sirupsen/logrus" + "github.com/sealerio/sealer/pkg/define/options" "github.com/containers/buildah" @@ -79,7 +81,7 @@ func (engine *Engine) Copy(opts *options.CopyOptions) error { contentType, digest := builder.ContentDigester.Digest() if !opts.Quiet { - fmt.Printf("%s\n", digest.Hex()) + logrus.Infof("%s", digest.Hex()) } if contentType != "" { contentType = contentType + ":" diff --git a/pkg/imageengine/buildah/images.go b/pkg/imageengine/buildah/images.go index 3ef4dee7391..a8af4ea81b6 100644 --- a/pkg/imageengine/buildah/images.go +++ b/pkg/imageengine/buildah/images.go @@ -21,11 +21,11 @@ import ( "strings" "time" - "github.com/sealerio/sealer/pkg/define/options" - "github.com/containers/buildah/pkg/formats" "github.com/containers/common/libimage" "github.com/docker/go-units" + "github.com/sealerio/sealer/pkg/define/options" + "github.com/sirupsen/logrus" ) const none = "" @@ -78,11 +78,11 @@ var imagesHeader = map[string]string{ } func (engine *Engine) Images(opts *options.ImagesOptions) error { - runtime := engine.libimageRuntime + runtime := engine.ImageRuntime() options := &libimage.ListImagesOptions{} if !opts.All { options.Filters = append(options.Filters, "intermediate=false") - options.Filters = append(options.Filters, "label=io.sealer.version") + //options.Filters = append(options.Filters, "label=io.sealer.version") } //TODO add some label to identify sealer image and oci image. @@ -159,7 +159,7 @@ func formatImagesJSON(images []*libimage.Image, opts imageOptions) error { if err != nil { return err } - fmt.Printf("%s\n", data) + logrus.Infof("%s", data) return nil } diff --git a/pkg/imageengine/buildah/manifest.go b/pkg/imageengine/buildah/manifest.go index 687fa3c0e07..5d8452ae817 100644 --- a/pkg/imageengine/buildah/manifest.go +++ b/pkg/imageengine/buildah/manifest.go @@ -15,29 +15,177 @@ package buildah import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/containers/buildah/util" "github.com/containers/common/libimage" "github.com/containers/common/libimage/manifests" cp "github.com/containers/image/v5/copy" "github.com/containers/image/v5/manifest" - - "github.com/sealerio/sealer/pkg/define/options" - - "os" - + "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/image/v5/types" "github.com/containers/storage" + "github.com/hashicorp/go-multierror" + "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + "github.com/sealerio/sealer/pkg/define/options" + "github.com/sirupsen/logrus" ) -func manifestPush(systemContext *types.SystemContext, store storage.Store, listImageSpec, destSpec string, opts options.PushOptions) error { - runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) +func (engine *Engine) LookupManifest(name string) (*libimage.ManifestList, error) { + return engine.libimageRuntime.LookupManifestList(name) +} + +func (engine *Engine) CreateManifest(name string, opts *options.ManifestCreateOpts) error { + store := engine.ImageStore() + systemCxt := engine.SystemContext() + list := manifests.Create() + + names, err := util.ExpandNames([]string{name}, systemCxt, store) if err != nil { + return fmt.Errorf("encountered while expanding image name %q: %w", name, err) + } + + imageID, err := list.SaveToImage(store, "", names, manifest.DockerV2ListMediaType) + if err == nil { + logrus.Infof("%s", imageID) + } + + return err +} + +func (engine *Engine) DeleteManifests(names []string, opts *options.ManifestDeleteOpts) error { + runtime := engine.ImageRuntime() + + rmiReports, rmiErrors := runtime.RemoveImages(context.Background(), names, &libimage.RemoveImagesOptions{ + Filters: []string{"readonly=false"}, + LookupManifest: true, + }) + for _, r := range rmiReports { + for _, u := range r.Untagged { + logrus.Infof("untagged: %s", u) + } + } + for _, r := range rmiReports { + if r.Removed { + logrus.Infof("%s", r.ID) + } + } + + var multiE *multierror.Error + multiE = multierror.Append(multiE, rmiErrors...) + return multiE.ErrorOrNil() +} + +func (engine *Engine) InspectManifest(name string, opts *options.ManifestInspectOpts) error { + printManifest := func(manifest []byte) error { + var b bytes.Buffer + err := json.Indent(&b, manifest, "", " ") + if err != nil { + return fmt.Errorf("rendering manifest for display: %w", err) + } + + logrus.Infof("%s", b.String()) + return nil + } + + runtime := engine.ImageRuntime() + store := engine.ImageStore() + systemCxt := engine.SystemContext() + ctx := getContext() + + // Before doing a remote lookup, attempt to resolve the manifest list + // locally. + manifestList, err := runtime.LookupManifestList(name) + if err == nil { + schema2List, err := manifestList.Inspect() + if err != nil { + return err + } + + rawSchema2List, err := json.Marshal(schema2List) + if err != nil { + return err + } + + return printManifest(rawSchema2List) + } + if !errors.Is(err, storage.ErrImageUnknown) && !errors.Is(err, libimage.ErrNotAManifestList) { return err } - manifestList, err := runtime.LookupManifestList(listImageSpec) + // TODO: at some point `libimage` should support resolving manifests + // like that. Similar to `libimage.Runtime.LookupImage` we could + // implement a `*.LookupImageIndex`. + refs, err := util.ResolveNameToReferences(store, systemCxt, name) + if err != nil { + logrus.Debugf("error parsing reference to image %q: %v", name, err) + } + + if ref, _, err := util.FindImage(store, "", systemCxt, name); err == nil { + refs = append(refs, ref) + } else if ref, err := alltransports.ParseImageName(name); err == nil { + refs = append(refs, ref) + } + if len(refs) == 0 { + return fmt.Errorf("locating images with names %v", name) + } + + var ( + latestErr error + result []byte + ) + + appendErr := func(e error) { + if latestErr == nil { + latestErr = e + } else { + latestErr = fmt.Errorf("tried %v: %w", e, latestErr) + } + } + + for _, ref := range refs { + logrus.Debugf("Testing reference %q for possible manifest", transports.ImageName(ref)) + + src, err := ref.NewImageSource(ctx, systemCxt) + if err != nil { + appendErr(fmt.Errorf("reading image %q: %w", transports.ImageName(ref), err)) + continue + } + defer src.Close() + + manifestBytes, manifestType, err := src.GetManifest(ctx, nil) + if err != nil { + appendErr(fmt.Errorf("loading manifest %q: %w", transports.ImageName(ref), err)) + continue + } + + if !manifest.MIMETypeIsMultiImage(manifestType) { + appendErr(fmt.Errorf("manifest is of type %s (not a list type)", manifestType)) + continue + } + result = manifestBytes + break + } + if len(result) == 0 && latestErr != nil { + return latestErr + } + + return printManifest(result) +} + +func (engine *Engine) PushManifest(name, destSpec string, opts *options.PushOptions) error { + runtime := engine.ImageRuntime() + store := engine.ImageStore() + systemCxt := engine.SystemContext() + + manifestList, err := runtime.LookupManifestList(name) if err != nil { return err } @@ -49,7 +197,22 @@ func manifestPush(systemContext *types.SystemContext, store storage.Store, listI dest, err := alltransports.ParseImageName(destSpec) if err != nil { - return err + destTransport := strings.Split(destSpec, ":")[0] + if t := transports.Get(destTransport); t != nil { + return err + } + + if strings.Contains(destSpec, "://") { + return err + } + + destSpec = "docker://" + destSpec + dest2, err2 := alltransports.ParseImageName(destSpec) + if err2 != nil { + return err + } + dest = dest2 + logrus.Debugf("Assuming docker:// as the transport method for DESTINATION: %s", destSpec) } var manifestType string @@ -60,25 +223,25 @@ func manifestPush(systemContext *types.SystemContext, store storage.Store, listI case "v2s2", "docker": manifestType = manifest.DockerV2Schema2MediaType default: - return errors.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format) + return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci' or 'v2s2'", opts.Format) } } - - options := manifests.PushOptions{ + fmt.Printf("AAAAAA PushOptions %+v\n", opts) + pushOptions := manifests.PushOptions{ Store: store, - SystemContext: systemContext, - ImageListSelection: cp.CopySpecificImages, + SystemContext: systemCxt, + ImageListSelection: cp.CopySystemImage, Instances: nil, ManifestType: manifestType, } if opts.All { - options.ImageListSelection = cp.CopyAllImages + pushOptions.ImageListSelection = cp.CopyAllImages } if !opts.Quiet { - options.ReportWriter = os.Stderr + pushOptions.ReportWriter = os.Stderr } - _, _, err = list.Push(getContext(), dest, options) + _, _, err = list.Push(getContext(), dest, pushOptions) if err == nil && opts.Rm { _, err = store.DeleteImage(manifestList.ID(), true) @@ -86,3 +249,108 @@ func manifestPush(systemContext *types.SystemContext, store storage.Store, listI return err } + +func (engine *Engine) AddToManifest(name, imageSpec string, opts *options.ManifestAddOpts) error { + runtime := engine.ImageRuntime() + store := engine.ImageStore() + systemCxt := engine.SystemContext() + + manifestList, err := runtime.LookupManifestList(name) + if err != nil { + return err + } + _, list, err := manifests.LoadFromImage(store, manifestList.ID()) + if err != nil { + return err + } + + ref, err := alltransports.ParseImageName(imageSpec) + if err != nil { + if ref, err = alltransports.ParseImageName(util.DefaultTransport + imageSpec); err != nil { + // check if the local image exists + if ref, _, err = util.FindImage(store, "", systemCxt, imageSpec); err != nil { + return err + } + } + } + + digestID, err := list.Add(getContext(), systemCxt, ref, opts.All) + if err != nil { + var storeErr error + // Retry without a custom system context. A user may want to add + // a custom platform (see #3511). + if ref, _, storeErr = util.FindImage(store, "", nil, imageSpec); storeErr != nil { + logrus.Errorf("Error while trying to find image on local storage: %v", storeErr) + return err + } + digestID, storeErr = list.Add(getContext(), systemCxt, ref, opts.All) + if storeErr != nil { + logrus.Errorf("Error while trying to add on manifest list: %v", storeErr) + return err + } + } + + if opts.Os != "" { + if err = list.SetOS(digestID, opts.Os); err != nil { + return err + } + } + if opts.OsVersion != "" { + if err = list.SetOSVersion(digestID, opts.OsVersion); err != nil { + return err + } + } + if len(opts.OsFeatures) != 0 { + if err = list.SetOSFeatures(digestID, opts.OsFeatures); err != nil { + return err + } + } + if opts.Arch != "" { + if err = list.SetArchitecture(digestID, opts.Arch); err != nil { + return err + } + } + if opts.Variant != "" { + if err = list.SetVariant(digestID, opts.Variant); err != nil { + return err + } + } + + if len(opts.Annotations) != 0 { + annotations := make(map[string]string) + for _, annotationSpec := range opts.Annotations { + spec := strings.SplitN(annotationSpec, "=", 2) + if len(spec) != 2 { + return fmt.Errorf("no value given for annotation %q", spec[0]) + } + annotations[spec[0]] = spec[1] + } + if err = list.SetAnnotations(&digestID, annotations); err != nil { + return err + } + } + + updatedListID, err := list.SaveToImage(store, manifestList.ID(), nil, "") + if err == nil { + logrus.Infof("%s: %s", updatedListID, digestID.String()) + } + + return err +} + +func (engine *Engine) RemoveFromManifest(name string, instanceDigest digest.Digest, opts *options.ManifestRemoveOpts) error { + runtime := engine.ImageRuntime() + + manifestList, err := runtime.LookupManifestList(name) + if err != nil { + return err + } + + if err = manifestList.RemoveInstance(instanceDigest); err != nil { + return err + } + + logrus.Infof("%s: %s", manifestList.ID(), instanceDigest.String()) + + return nil +} diff --git a/pkg/imageengine/buildah/pull.go b/pkg/imageengine/buildah/pull.go index 6ad731ea52b..aca14000751 100644 --- a/pkg/imageengine/buildah/pull.go +++ b/pkg/imageengine/buildah/pull.go @@ -17,6 +17,8 @@ package buildah import ( "fmt" + "github.com/sirupsen/logrus" + "github.com/containers/buildah/pkg/parse" "os" @@ -74,6 +76,6 @@ func (engine *Engine) Pull(opts *options.PullOptions) error { if err != nil { return err } - fmt.Printf("%s\n", id) + logrus.Infof("%s", id) return nil } diff --git a/pkg/imageengine/buildah/push.go b/pkg/imageengine/buildah/push.go index 5ab8b672fd6..44421b4df1a 100644 --- a/pkg/imageengine/buildah/push.go +++ b/pkg/imageengine/buildah/push.go @@ -15,22 +15,21 @@ package buildah import ( - "github.com/containers/buildah" - "github.com/containers/buildah/define" - - "github.com/sealerio/sealer/pkg/define/options" - + "fmt" "os" "strings" + "github.com/containers/buildah" + "github.com/containers/buildah/define" "github.com/containers/buildah/util" + "github.com/containers/common/libimage" "github.com/containers/common/pkg/auth" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" - "github.com/containers/storage" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + "github.com/sealerio/sealer/pkg/define/options" "github.com/sirupsen/logrus" ) @@ -44,10 +43,11 @@ func (engine *Engine) Push(opts *options.PushOptions) error { } src, destSpec := opts.Image, opts.Image + if len(opts.Destination) != 0 { + destSpec = opts.Destination + } compress := define.Gzip - store := engine.ImageStore() - dest, err := alltransports.ParseImageName(destSpec) // add the docker:// transport to see if they neglected it. if err != nil { @@ -69,6 +69,27 @@ func (engine *Engine) Push(opts *options.PushOptions) error { logrus.Debugf("Assuming docker:// as the transport method for DESTINATION: %s", destSpec) } + img, _, err := engine.ImageRuntime().LookupImage(src, &libimage.LookupImageOptions{ + ManifestList: true, + }) + if err != nil { + return err + } + + isManifest, err := img.IsManifestList(getContext()) + if err != nil { + return err + } + + fmt.Println("AAAAA", src, dest, isManifest) + + if isManifest { + if manifestsErr := engine.PushManifest(src, destSpec, opts); manifestsErr == nil { + return nil + } + return util.GetFailureCause(err, errors.Wrapf(err, "error pushing image %q to %q", src, destSpec)) + } + var manifestType string if opts.Format != "" { switch opts.Format { @@ -94,15 +115,9 @@ func (engine *Engine) Push(opts *options.PushOptions) error { if !opts.Quiet { options.ReportWriter = os.Stderr } - + fmt.Printf("AAAAAA push options %+v\n", options) ref, digest, err := buildah.Push(getContext(), src, dest, options) if err != nil { - if errors.Cause(err) != storage.ErrImageUnknown { - // Image might be a manifest so attempt a manifest push - if manifestsErr := manifestPush(systemCxt, store, src, destSpec, *opts); manifestsErr == nil { - return nil - } - } return util.GetFailureCause(err, errors.Wrapf(err, "error pushing image %q to %q", src, destSpec)) } if ref != nil { diff --git a/pkg/imageengine/buildah/remove_container.go b/pkg/imageengine/buildah/remove_container.go index 9af84a37729..1ba30488d76 100644 --- a/pkg/imageengine/buildah/remove_container.go +++ b/pkg/imageengine/buildah/remove_container.go @@ -16,14 +16,13 @@ package buildah import ( "fmt" - "os" - "github.com/sealerio/sealer/pkg/define/options" - "github.com/containers/buildah" "github.com/containers/buildah/util" "github.com/pkg/errors" + "github.com/sealerio/sealer/pkg/define/options" + "github.com/sirupsen/logrus" ) func (engine *Engine) RemoveContainer(opts *options.RemoveContainerOptions) error { @@ -49,7 +48,7 @@ func (engine *Engine) RemoveContainer(opts *options.RemoveContainerOptions) erro lastError = util.WriteError(os.Stderr, errors.Wrapf(err, "%s %q", delContainerErrStr, builder.Container), lastError) continue } - fmt.Printf("%s\n", id) + logrus.Infof("%s", id) } } else { for _, name := range opts.ContainerNamesOrIDs { @@ -63,7 +62,7 @@ func (engine *Engine) RemoveContainer(opts *options.RemoveContainerOptions) erro lastError = util.WriteError(os.Stderr, errors.Wrapf(err, "%s %q", delContainerErrStr, name), lastError) continue } - fmt.Printf("%s\n", id) + logrus.Infof("%s", id) } } return lastError diff --git a/pkg/imageengine/buildah/rmi.go b/pkg/imageengine/buildah/rmi.go index b9ecb77ca6c..4b4bbe8f62e 100644 --- a/pkg/imageengine/buildah/rmi.go +++ b/pkg/imageengine/buildah/rmi.go @@ -16,13 +16,12 @@ package buildah import ( "context" - "fmt" - - "github.com/sealerio/sealer/pkg/define/options" "github.com/containers/common/libimage" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" + "github.com/sealerio/sealer/pkg/define/options" + "github.com/sirupsen/logrus" ) func (engine *Engine) RemoveImage(opts *options.RemoveImageOptions) error { @@ -42,19 +41,42 @@ func (engine *Engine) RemoveImage(opts *options.RemoveImageOptions) error { } options.Force = opts.Force + // take it as image first rmiReports, rmiErrors := engine.ImageRuntime().RemoveImages(context.Background(), opts.ImageNamesOrIDs, options) for _, r := range rmiReports { for _, u := range r.Untagged { - fmt.Printf("untagged: %s\n", u) + logrus.Infof("untagged: %s", u) + } + } + for _, r := range rmiReports { + if r.Removed { + logrus.Infof("%s", r.ID) + } + } + + if len(rmiErrors) == 0 { + return nil + } + + // take it as manifestList and try again + options.LookupManifest = true + rmiReports, rmiErrors2 := engine.ImageRuntime().RemoveImages(context.Background(), opts.ImageNamesOrIDs, options) + for _, r := range rmiReports { + for _, u := range r.Untagged { + logrus.Infof("untagged: %s", u) } } for _, r := range rmiReports { if r.Removed { - fmt.Printf("%s\n", r.ID) + logrus.Infof("%s", r.ID) } } + if len(rmiErrors2) == 0 { + return nil + } + var multiE *multierror.Error - multiE = multierror.Append(multiE, rmiErrors...) + multiE = multierror.Append(multiE, append(rmiErrors, rmiErrors2...)...) return multiE.ErrorOrNil() } diff --git a/pkg/imageengine/interface.go b/pkg/imageengine/interface.go index 8f9f8bb0057..3eb378bbe87 100644 --- a/pkg/imageengine/interface.go +++ b/pkg/imageengine/interface.go @@ -15,6 +15,8 @@ package imageengine import ( + "github.com/containers/common/libimage" + "github.com/opencontainers/go-digest" v1 "github.com/sealerio/sealer/pkg/define/image/v1" "github.com/sealerio/sealer/pkg/define/options" ) @@ -30,7 +32,7 @@ type Interface interface { Copy(opts *options.CopyOptions) error - Commit(opts *options.CommitOptions) error + Commit(opts *options.CommitOptions) (string, error) Login(opts *options.LoginOptions) error @@ -57,4 +59,18 @@ type Interface interface { Tag(opts *options.TagOptions) error GetSealerImageExtension(opts *options.GetImageAnnoOptions) (v1.ImageExtension, error) + + LookupManifest(name string) (*libimage.ManifestList, error) + + CreateManifest(name string, opts *options.ManifestCreateOpts) error + + DeleteManifests(names []string, opts *options.ManifestDeleteOpts) error + + InspectManifest(name string, opts *options.ManifestInspectOpts) error + + PushManifest(name, destSpec string, opts *options.PushOptions) error + + AddToManifest(name, imageSpec string, opts *options.ManifestAddOpts) error + + RemoveFromManifest(name string, instanceDigest digest.Digest, opts *options.ManifestRemoveOpts) error } diff --git a/types/api/v1/image_types.go b/types/api/v1/image_types.go index bf8370bdfe7..cd72e177b7a 100644 --- a/types/api/v1/image_types.go +++ b/types/api/v1/image_types.go @@ -102,7 +102,7 @@ type Platform struct { Variant string `json:"variant,omitempty"` } -func (p Platform) ToString() string { +func (p *Platform) ToString() string { str := p.OS + "/" + p.Architecture + "/" + p.Variant str = strings.TrimSuffix(str, "/") str = strings.TrimPrefix(str, "/")