From 66b44e0d47a1641e805624bb90827d5f13a82fd5 Mon Sep 17 00:00:00 2001 From: Zymate Studio Date: Tue, 7 Nov 2023 08:28:03 +0530 Subject: [PATCH] WIP: added runtime for storing Manifest List * added client logic for manifest cli * added manifest push Signed-off-by: WYGIN * WIP refactor code Signed-off-by: WYGIN * WIP refactor code Signed-off-by: WYGIN --------- Signed-off-by: WYGIN Signed-off-by: WYGIN Co-authored-by: WYGIN --- internal/commands/manifest_annotate.go | 3 +- internal/commands/manifest_create.go | 8 +- internal/commands/manifest_exists.go | 2 +- internal/commands/manifest_exists_test.go | 2 +- internal/commands/manifest_push.go | 8 +- .../commands/testmocks/mock_pack_client.go | 2 +- internal/runtime/index.go | 144 ++++++++++++++++++ internal/runtime/runtime.go | 99 ++++++++++++ pkg/client/add_manifest.go | 30 ++-- pkg/client/annotate_manifest.go | 29 ++-- pkg/client/client.go | 90 ++++++++++- pkg/client/create_manifest.go | 32 ++-- pkg/client/delete_manifest.go | 27 +--- pkg/client/exists_manifest.go | 18 +-- pkg/client/exists_manifest_test.go | 2 +- pkg/client/inspect_manifest.go | 10 +- pkg/client/push_manifest.go | 17 +-- pkg/client/remove_manifest.go | 8 +- pkg/errors/errors.go | 9 ++ pkg/testmocks/mock_image_factory.go | 32 ++++ 20 files changed, 442 insertions(+), 130 deletions(-) create mode 100644 internal/runtime/index.go create mode 100644 internal/runtime/runtime.go create mode 100644 pkg/errors/errors.go diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index dd941c848..1d3f2461a 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -8,7 +8,7 @@ import ( // ManifestAnnotateFlags define flags provided to the ManifestAnnotate type ManifestAnnotateFlags struct { - os, arch, variant, osVersion string + os, arch, variant, osVersion string features, osFeatures, annotations []string } @@ -38,7 +38,6 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().StringSliceVar(&flags.osFeatures, "os-features", nil, "override the os `features` of the specified image") cmd.Flags().StringSliceVar(&flags.annotations, "annotations", nil, "set an `annotation` for the specified image") - AddHelpFlag(cmd, "annotate") return cmd } diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 4883ffd13..b42c77961 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -11,7 +11,7 @@ import ( // ManifestCreateFlags define flags provided to the ManifestCreate type ManifestCreateFlags struct { - format, registry, os, arch string + format, registry, os, arch string insecure, publish, all, amend bool } @@ -48,10 +48,10 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { } id, err := pack.CreateManifest(cmd.Context(), imageIndex, manifests, client.CreateManifestOptions{ - Format: flags.format, + Format: flags.format, Registry: flags.registry, Insecure: flags.insecure, - Publish: flags.publish, + Publish: flags.publish, }) if err != nil { @@ -89,4 +89,4 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { func validateManifestCreateFlags(flags ManifestCreateFlags) error { return nil -} \ No newline at end of file +} diff --git a/internal/commands/manifest_exists.go b/internal/commands/manifest_exists.go index 654459931..e0772b627 100644 --- a/internal/commands/manifest_exists.go +++ b/internal/commands/manifest_exists.go @@ -19,7 +19,7 @@ func ManifestExists(logger logging.Logger, pack PackClient) *cobra.Command { Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Short: "Delete one or more manifest lists from local storage", Example: `pack manifest exists cnbs/sample-package:hello-multiarch-universe`, - Long: `Checks if a manifest list exists in local storage`, + Long: `Checks if a manifest list exists in local storage`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { if err := pack.ExistsManifest(cmd.Context(), args[0]); err != nil { return err diff --git a/internal/commands/manifest_exists_test.go b/internal/commands/manifest_exists_test.go index fe7b8c53f..07536081d 100644 --- a/internal/commands/manifest_exists_test.go +++ b/internal/commands/manifest_exists_test.go @@ -1 +1 @@ -package commands_test \ No newline at end of file +package commands_test diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index 8eb4c9188..27d7fbe3f 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -9,7 +9,7 @@ import ( // ManifestPushFlags define flags provided to the ManifestPush type ManifestPushFlags struct { - format string + format string insecure, purge, all, quite bool } @@ -31,9 +31,9 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { } imageID, err := pack.PushManifest(cmd.Context(), args[0], client.PushManifestOptions{ - Format: flags.format, + Format: flags.format, Insecure: flags.insecure, - Purge: flags.purge, + Purge: flags.purge, }) if err != nil { @@ -57,4 +57,4 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { func parseFalgs(flags ManifestPushFlags) error { return nil -} \ No newline at end of file +} diff --git a/internal/commands/testmocks/mock_pack_client.go b/internal/commands/testmocks/mock_pack_client.go index 9060b7fb5..f6291b235 100644 --- a/internal/commands/testmocks/mock_pack_client.go +++ b/internal/commands/testmocks/mock_pack_client.go @@ -36,7 +36,7 @@ func (m *MockPackClient) EXPECT() *MockPackClientMockRecorder { } // AddManifest mocks base method. -func (m *MockPackClient) AddManifest(arg0 context.Context, arg1 string, arg2 []string, arg3 client.ManifestAddOptions) (string, error) { +func (m *MockPackClient) AddManifest(arg0 context.Context, arg1, arg2 string, arg3 client.ManifestAddOptions) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddManifest", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(string) diff --git a/internal/runtime/index.go b/internal/runtime/index.go new file mode 100644 index 000000000..2cbbee12d --- /dev/null +++ b/internal/runtime/index.go @@ -0,0 +1,144 @@ +package runtime + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/buildpacks/imgutil" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" +) + +type ImageIndex struct { + Index imgutil.Index + Instances map[string][]ManifestConfig `json:"instance"` + runtime *runtime +} + +type ManifestConfig struct { + Hash v1.Hash `json:"hash"` + OS string `json:"os,omitempty"` + OSVersion string `json:"os-version,omitempty"` + Variant string `json:"variant,omitempty"` + Architecture string `json:"arch,omitempty"` + MediaType imgutil.MediaTypes `json:"mediaType,omitempty"` + Local bool `json:"local,omitempty"` +} + +type PushOptions struct { + ManifestType string + All bool + Insecure bool +} + +type imageIndex interface { + Save(names []string, mimeType string) (string, error) + Push(ctx context.Context, opts PushOptions) (name.Digest, error) + Add(ctx context.Context, ref name.Reference, all bool) (name.Digest, error) + Remove(digest name.Digest) error + Delete(name string) error +} + +func (i *ImageIndex) Save(names []string, mimeType string) (err error) { + for _, name := range names { + ref, err := i.runtime.ParseReference(name) + if err != nil { + return err + } + writeManifest := func(path string) error { + manifest, err := json.MarshalIndent(i.Instances[ref.Identifier()], "", " ") + file, err := os.Create(path) + if err != nil { + return err + } + _, err = file.Write(manifest) + if err != nil { + return err + } + return nil + } + path := filepath.Join(i.runtime.manifestListPath, makeFilesafeName(ref.Identifier())) + if _, err := os.Stat(filepath.Join(path, makeFilesafeName(name)+".config.json")); err != nil { + fmt.Printf("overriding '%s'...", ref.Name()) + if err := writeManifest(filepath.Join(path, makeFilesafeName(name)+".config.json")); err != nil { + return err + } + } + if err := writeManifest(filepath.Join(path, makeFilesafeName(name)+".config.json")); err != nil { + return err + } + i.Index.Save(path, name, i.runtime.ImageType(mimeType)) + } + return nil +} + +func (i *ImageIndex) Push(ctx context.Context, opts PushOptions) (digest name.Digest, err error) { + for _, manifest := range i.Index.Manifests { + for k, v := range i.Instances { + for _, m := range v { + if m.Hash.String() == manifest.Digest.String() && m.Local { + fmt.Errorf("image: '%s' is not found in registry", k) + } + } + } + } + digest, err = i.Index.Push(ctx, opts) + if err == nil { + fmt.Printf("successfully pushed ImageIndex to registry") + } + return +} + +func (i *ImageIndex) Add(ctx context.Context, ref name.Reference, all bool) (digest name.Digest, err error) { + for _, v := range i.Instances[ref.Identifier()] { + var img string + if image, err := i.runtime.ParseReference(ref.Name()); err == nil { + img = strings.Split(image.Name(), ":")[0] + "@" + v.Hash.String() + } + if image, err := i.runtime.ParseDigest(ref.Name()); err == nil { + img = image.Name() + } + if img == "" { + return digest, fmt.Errorf("unable to parse the reference '%s'", ref.Name()) + } + if all { + i.Index.Add(ctx, img, v.MediaType) + } + } + + return ref.Context().Digest(ref.Identifier()), err +} + +func (i *ImageIndex) Remove(name string) (err error) { + if ref, err := i.runtime.ParseDigest(name); err == nil { + err = i.Index.Remove(ref) + if err != nil { + return err + } + return nil + } + if ref, err := i.runtime.ParseReference(name); err == nil { + for _, v := range i.Instances[ref.Identifier()] { + name := strings.Split(ref.Name(), ":") + if ref, err := i.runtime.ParseDigest(name[0] + "@" + v.Hash.String()); err == nil { + if err := i.Index.Remove(ref); err == nil { + fmt.Printf("Successfully removed Image '%s'", ref.Name()) + } + } + err = nil + } + } + return +} + +func (i ImageIndex) Delete(name string) error { + if _, err := i.runtime.ParseReference(name); err != nil { + return err + } + + return i.Index.Delete(name) +} diff --git a/internal/runtime/runtime.go b/internal/runtime/runtime.go new file mode 100644 index 000000000..87f0bc05a --- /dev/null +++ b/internal/runtime/runtime.go @@ -0,0 +1,99 @@ +package runtime + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/buildpacks/imgutil" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + + "github.com/buildpacks/pack/internal/config" +) + +type runtime struct { + manifestListPath string +} + +func NewRuntime() (runtime, error) { + if value, ok := os.LookupEnv("XDG_RUNTIME_DIR"); ok { + return runtime{ + manifestListPath: filepath.Join(value, "manifestList"), + }, nil + } + if home, err := config.PackHome(); err == nil { + return runtime{ + manifestListPath: filepath.Join(home, "manifestList"), + }, nil + } + return runtime{}, fmt.Errorf("unable to runtime path") +} + +func (r runtime) LookupImageIndex(name string) (index ImageIndex, err error) { + n, err := r.ParseReference(name) + if err != nil { + return index, err + } + filepath := filepath.Join(r.manifestListPath, makeFilesafeName(n.Name())) + _, err = os.Stat(filepath) + if err != nil { + return + } + manifestBytes, _ := os.ReadFile(filepath) + if err != nil { + return + } + var dockerManifest imgutil.DockerManifestList + var ociImageIndex v1.ImageIndex + if err := json.Unmarshal(manifestBytes, &dockerManifest); err != nil { + return ImageIndex{}, err + } + if err := json.Unmarshal(manifestBytes, &ociImageIndex); err != nil { + return ImageIndex{}, err + } + return ImageIndex{ + docker: dockerManifest, + oci: ociImageIndex, + }, err +} + +func (r runtime) ImageType(format string) (manifestType imgutil.MediaTypes) { + switch format { + case imgutil.ImageIndexTypes.OCI: + return imgutil.ImageIndexTypes.OCI + case imgutil.ImageIndexTypes.Index: + return imgutil.ImageIndexTypes.Index + default: + return imgutil.ImageIndexTypes.Docker + } +} + +func (r runtime) ParseReference(image string) (ref name.Reference, err error) { + return name.ParseReference(image) +} + +func (r runtime) ParseDigest(image string) (ref name.Digest, err error) { + return name.NewDigest(image) +} + +func (r runtime) RemoveManifests(ctx context.Context, names []string) (err error) { + for _, name := range names { + name = makeFilesafeName(name) + if _, err := os.Stat(filepath.Join(r.manifestListPath, name, name)); err == nil { + err := os.Remove(filepath.Join(r.manifestListPath, name)) + if err != nil { + errors = append(errors, err) + } + } + } + return +} + +func makeFilesafeName(ref string) string { + fileName := strings.Replace(ref, ":", "-", -1) + return strings.Replace(fileName, "/", "_", -1) +} diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 1ec7d5e80..6a449e2a6 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -6,64 +6,60 @@ import ( "strings" ) - type ManifestAddOptions struct { ManifestAnnotateOptions All bool } + // AddManifest implements commands.PackClient. func (c *Client) AddManifest(ctx context.Context, index string, image string, opts ManifestAddOptions) (indexID string, err error) { - imgIndex, err := c.runtime.LookupImageIndex(index) - if err != nil { - return - } - _, list, err := c.runtime.LoadFromImage(imgIndex.ID()) + ref, err := c.runtime.ParseReference(image) if err != nil { return } - ref, err := c.runtime.ParseReference(image) + imgIndex, err := c.runtime.LookupImageIndex(index) if err != nil { return } - digest, err := list.Add(ctx, ref, opts.All) + digest, err := imgIndex.Add(ctx, ref, opts.All) if err != nil { - if ref, _, err = c.runtime.FindImage(image); err != nil { + if ref, _, err = c.imageFactory.FindImage(image); err != nil { return indexID, fmt.Errorf("Error while trying to find image on local storage: %v", err) } - digest, err = list.Add(ctx, ref, opts.All) + digest, err = imgIndex.Add(ctx, ref, opts.All) if err != nil { return indexID, fmt.Errorf("Error while trying to add on manifest list: %v", err) } } if opts.OS != "" { - if _, err := list.SetOS(digest, opts.OS); err != nil { + if _, err := imgIndex.Index.SetOS(digest, opts.OS); err != nil { return indexID, err } } if opts.OSArch != "" { - if _, err := list.SetArchitecture(digest, opts.OSArch); err != nil { + if _, err := imgIndex.Index.SetArchitecture(digest, opts.OSArch); err != nil { return indexID, err } } if opts.OSVariant != "" { - if _, err := list.SetVariant(digest, opts.OSVariant); err != nil { + if _, err := imgIndex.Index.SetVariant(digest, opts.OSVariant); err != nil { return indexID, err } } if opts.OSVersion != "" { - if _, err := list.SetOSVersion(digest, opts.OSVersion); err != nil { + if _, err := imgIndex.Index.SetOSVersion(digest, opts.OSVersion); err != nil { return indexID, err } } if len(opts.Features) != 0 { - if _, err := list.SetFeatures(digest, opts.Features); err != nil { + if _, err := imgIndex.Index.SetFeatures(digest, opts.Features); err != nil { return indexID, err } } @@ -77,12 +73,12 @@ func (c *Client) AddManifest(ctx context.Context, index string, image string, op } annotations[spec[0]] = spec[1] } - if err := list.SetAnnotations(&digest, annotations); err != nil { + if err := imgIndex.Index.SetAnnotations(&digest, annotations); err != nil { return err } } - indexID, err = list.Save(imgIndex.ID(), nil, "") + indexID, err = imgIndex.Index.Save(index, nil, "") if err == nil { fmt.Printf("%s: %s\n", indexID, digest.String()) } diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index c79d2c481..7cde81079 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -7,11 +7,10 @@ import ( ) type ManifestAnnotateOptions struct { - OS, OSVersion, OSArch, OSVariant string + OS, OSVersion, OSArch, OSVariant string OSFeatures, Annotations, Features map[string]string } - // AnnotateManifest implements commands.PackClient. func (c *Client) AnnotateManifest(ctx context.Context, name string, image string, opts ManifestAnnotateOptions) error { manifestList, err := c.runtime.LookupImageIndex(name) @@ -19,51 +18,45 @@ func (c *Client) AnnotateManifest(ctx context.Context, name string, image string return err } - _, list, err := c.runtime.LoadFromImage(manifestList.ID()) - if err != nil { - return err - } - digest, err := c.runtime.ParseDigest(image) if err != nil { - ref, _, err := c.runtime.FindImage(image) + ref, _, err := c.imageFactory.FindImage(image) if err != nil { return err } - digest , err = c.runtime.ParseDigest(ref.Name()) + digest, err = c.runtime.ParseDigest(ref.Name()) if err != nil { return err } } - if opts.OS != "" { - if err := list.SetOS(digest, opts.OS); err != nil { + if err := manifestList.Index.SetOS(digest, opts.OS); err != nil { return err } } if opts.OSVersion != "" { - if err := list.SetOSVersion(digest, opts.OSVersion); err != nil { + if err := manifestList.Index.SetOSVersion(digest, opts.OSVersion); err != nil { return err } } if len(opts.OSFeatures) != 0 { - if err := list.SetOSFeatures(digest, opts.OSFeatures); err != nil { + if err := manifestList.Index.SetOSFeatures(digest, opts.OSFeatures); err != nil { return err } } if opts.OSArch != "" { - if err := list.SetArchitecture(digest, opts.OSArch); err != nil { + if err := manifestList.Index.SetArchitecture(digest, opts.OSArch); err != nil { return err } } if opts.OSVariant != "" { - if err := list.SetVariant(digest, opts.OSVariant); err != nil { + if err := manifestList.Index.SetVariant(digest, opts.OSVariant); err != nil { return err } } if len(opts.Features) != 0 { - if err := list.SetFeatures(digest, opts.Features); err != nil { + if err := manifestList.Index.SetFeatures(digest, opts.Features); err != nil { return err } } @@ -76,12 +69,12 @@ func (c *Client) AnnotateManifest(ctx context.Context, name string, image string } annotations[spec[0]] = spec[1] } - if err := list.SetAnnotations(&digest, annotations); err != nil { + if err := manifestList.Index.SetAnnotations(&digest, annotations); err != nil { return err } } - updatedListID, err := list.Save(manifestList.ID(), nil, "") + updatedListID, err := manifestList.Index.Save(name, nil, "") if err == nil { fmt.Printf("%s: %s\n", updatedListID, digest.String()) } diff --git a/pkg/client/client.go b/pkg/client/client.go index 9e259bd37..0ee5c6d11 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -31,6 +31,7 @@ import ( "github.com/buildpacks/pack" "github.com/buildpacks/pack/internal/build" iconfig "github.com/buildpacks/pack/internal/config" + runtime "github.com/buildpacks/pack/internal/runtime" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/blob" "github.com/buildpacks/pack/pkg/buildpack" @@ -73,6 +74,39 @@ type ImageFactory interface { // NewImage initializes an image object with required settings so that it // can be written either locally or to a registry. NewImage(repoName string, local bool, imageOS string) (imgutil.Image, error) + // loads an image from the local storage + // LoadImage(repoName string, platform imgutil.Platform) (imgutil.Image, error) + // FindImage locates the locally-stored image which corresponds to a given name. + FindImage(name string) (ref name.Reference, image imgutil.Image, err error) +} + +// IndexFactory is an interface representing the ability to create a ImageIndex/ManifestList. +type IndexFactory interface { + NewIndex(reponame string, opts imgutil.IndexOptions) (imgutil.Index, error) + // Fetch ManifestList from Registry with the given name + FetchIndex(name string) (imgutil.Index, error) +} + +type Runtime interface { + // LookupManifestList looks up a manifest list with the specified name in the + // containers storage. + LookupImageIndex(name string) (index runtime.ImageIndex, err error) + // LoadFromImage reads the manifest list or image index, and additional + // information about where the various instances that it contains live, from an + // image record with the specified ID in local storage. + // LoadFromImage(name string) (imageID string, index imgutil.Index, err error) + // ExpandNames takes unqualified names, parses them as image names, and returns + // the fully expanded result, including a tag. Names which don't include a registry + // name will be marked for the most-preferred registry + // ExpandIndexNames(names []string) (images []string, err error) + // ImageType returns the MediaType of the given image's format + ImageType(format string) (manifestType imgutil.MediaTypes) + // parse name reference + ParseReference(image string) (name.Reference, error) + // parses the digest reference + ParseDigest(image string) (name.Digest, error) + // RemoveManifests will delete manifest/manifestList from the local stroage + RemoveManifests(ctx context.Context, names []string) (err error) } // IndexFactory is an interface representing the ability to create a ImageIndex/ManifestList. @@ -123,8 +157,8 @@ type Client struct { keychain authn.Keychain imageFactory ImageFactory - indexFactory IndexFactory - runtime Runtime + indexFactory IndexFactory + runtime Runtime imageFetcher ImageFetcher downloader BlobDownloader lifecycleExecutor LifecycleExecutor @@ -153,6 +187,13 @@ func WithImageFactory(f ImageFactory) Option { } } +// WithIndexFactory supply your own index factory +func WithIndexFactory(f IndexFactory) Option { + return func(c *Client) { + c.indexFactory = f + } +} + // WithFetcher supply your own Fetcher. // A Fetcher retrieves both local and remote images to make them available. func WithFetcher(f ImageFetcher) Option { @@ -161,6 +202,13 @@ func WithFetcher(f ImageFetcher) Option { } } +// WithRuntime supply your own runtime +func WithRuntime(f Runtime) Option { + return func(c *Client) { + c.runtime = f + } +} + // WithDownloader supply your own downloader. // A Downloader is used to gather buildpacks from both remote urls, or local sources. func WithDownloader(d BlobDownloader) Option { @@ -255,7 +303,7 @@ func NewClient(opts ...Option) (*Client, error) { } if client.runtime == nil { - client.runtime = runtime.NewRuntime() + client.runtime, _ = runtime.NewRuntime() } if client.imageFactory == nil { @@ -320,20 +368,50 @@ func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (i return remote.NewImage(repoName, f.keychain, remote.WithDefaultPlatform(platform)) } +func (f *imageFactory) LoadImage(repoName string, platform imgutil.Platform) (img imgutil.Image, err error) { + img, err = local.NewImage(repoName, f.dockerClient, local.WithDefaultPlatform(platform), local.WithPreviousImage(repoName)) + if err == nil { + return + } + + img, err = layout.NewImage(repoName, layout.WithPreviousImage(repoName), layout.WithDefaultPlatform(platform)) + if err == nil { + return + } + return nil, errors.Errorf("Image: '%s' not found", repoName) +} + +func (f *imageFactory) FindImage(repoName string) (ref name.Reference, img imgutil.Image, err error) { + img, err = local.NewImage(repoName, f.dockerClient, local.WithPreviousImage(repoName)) + if err == nil { + return + } + + img, err = layout.NewImage(repoName, layout.WithPreviousImage(repoName)) + if err == nil { + return + } + return ref, img, errors.Errorf("Image: '%s' not found", repoName) +} + type indexFactory struct { keychain authn.Keychain } -func(f *indexFactory) NewIndex(name string, opts imgutil.IndexOptions) (index imgutil.Index, err error) { +func (f *indexFactory) NewIndex(name string, opts imgutil.IndexOptions) (index imgutil.Index, err error) { index, err = remote.NewIndex(name, f.keychain, opts) if err != nil { if opts.MediaType == imgutil.MediaTypes.OCI { - return layout.NewIndex(name, f.keychain, opts) + return layout.NewIndex(name, opts) } else { - return local.NewIndex(name, f.keychain, opts) + return local.NewIndex(name, opts) } } return index, err } + +func (f *indexFactory) FetchIndex(repoName string) (index imgutil.Index, err error) { + return remote.NewIndex(repoName, f.keychain, imgutil.IndexOptions{}) +} diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 00a69a19c..0059672a6 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -6,11 +6,12 @@ import ( "fmt" "github.com/buildpacks/imgutil" + packErrors "github.com/buildpacks/pack/pkg/errors" ) type CreateManifestOptions struct { - Format, Registry string + Format, Registry string Insecure, Publish, amend, all bool } @@ -21,23 +22,14 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin return } index.Create() - names, err := c.runtime.ExpandIndexNames([]string{name}) if err != nil { return } - var imageIndexID string - if imageIndexID, err = index.Save("", names, c.runtime.ImageType(opts.Format)); err != nil { + if imageID, err = index.Save(name, c.runtime.ImageType(opts.Format)); err != nil { if errors.Is(err, packErrors.ErrDuplicateName) && opts.amend { - for _, idxName := range names { - imageIndex, err := c.runtime.LookupImageIndex(idxName) - if err != nil { - fmt.Printf("no list named %q found: %v", name, err) - } - if _, index, err = c.runtime.LoadFromImage(imageIndex.ID()); err != nil { - fmt.Printf("no list found in %q", idxName) - } - imageIndexID = imageIndex.ID() - break + _, err := c.runtime.LookupImageIndex(name) + if err != nil { + fmt.Printf("no list named %q found: %v", name, err) } if index == nil { @@ -53,7 +45,7 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin if err != nil { return imageID, err } - if localRef, _, err := c.runtime.FindImage(img); err == nil { + if localRef, _, err := c.imageFactory.FindImage(img); err == nil { ref = localRef } if _, err = index.Add(ctx, ref, opts.all); err != nil { @@ -61,14 +53,18 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin } } - imageID, err = index.Save(imageIndexID, names, c.runtime.ImageType(opts.Format)) + imageID, err = index.Save(name, c.runtime.ImageType(opts.Format)) if err == nil { fmt.Printf("%s\n", imageID) } + if opts.Publish { + + } + return imageID, err } -func parseOptsToIndexOptions(opts CreateManifestOptions) imgutil.IndexOptions { - return opts +func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts imgutil.IndexOptions) { + return idxOpts } diff --git a/pkg/client/delete_manifest.go b/pkg/client/delete_manifest.go index c503bd961..19a91df93 100644 --- a/pkg/client/delete_manifest.go +++ b/pkg/client/delete_manifest.go @@ -2,32 +2,11 @@ package client import ( "context" - "fmt" - "strings" + // "fmt" + // "strings" ) // DeleteManifest implements commands.PackClient. func (c *Client) DeleteManifest(ctx context.Context, names []string) error { - rmiReports, rmiErrors := c.runtime.RemoveManifests(ctx, names) - for _, r := range rmiReports { - for _, u := range r.Untagged { - fmt.Printf("untagged: %s\n", u) - } - } - - for _, r := range rmiReports { - if r.Removed { - fmt.Printf("%s\n", r.ID) - } - } - var errors []string - for _, err := range rmiErrors { - errors = append(errors, err.Error()) - } - - if len(errors) == 0 { - return nil - } - - return fmt.Errorf(strings.Join(errors, "\n")) + return c.runtime.RemoveManifests(ctx, names) } diff --git a/pkg/client/exists_manifest.go b/pkg/client/exists_manifest.go index d125ffeef..467fb46e4 100644 --- a/pkg/client/exists_manifest.go +++ b/pkg/client/exists_manifest.go @@ -2,26 +2,16 @@ package client import ( "context" - "os" - "github.com/buildpacks/imgutil" - packErrors "github.com/buildpacks/pack/pkg/errors" + // "github.com/buildpacks/imgutil" "github.com/pkg/errors" ) -func(c *Client) ExistsManifest(ctx context.Context, image string) error { - index, err := c.indexFactory.NewIndex(image, imgutil.IndexOptions{}) - if err != nil { - return errors.Errorf("error while initializing index: %s", image) - } +func (c *Client) ExistsManifest(ctx context.Context, image string) error { if _, err := c.runtime.LookupImageIndex(image); err != nil { - if errors.Is(err, packErrors.ErrImageUnknown) { - os.Exit(1) - } else { - return errors.Errorf("image '%s' is not found", image) - } + return errors.Errorf("image '%s' is not found", image) } return nil -} \ No newline at end of file +} diff --git a/pkg/client/exists_manifest_test.go b/pkg/client/exists_manifest_test.go index e148123e6..e169c0b61 100644 --- a/pkg/client/exists_manifest_test.go +++ b/pkg/client/exists_manifest_test.go @@ -1 +1 @@ -package client_test \ No newline at end of file +package client_test diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index 64b7ce41d..fcc90e417 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -7,11 +7,11 @@ import ( "fmt" "github.com/pkg/errors" + packErrors "github.com/buildpacks/pack/pkg/errors" ) type InspectManifestOptions struct { - } // InspectManifest implements commands.PackClient. @@ -31,16 +31,16 @@ func (c *Client) InspectManifest(ctx context.Context, name string, opts InspectM // locally. manifestList, err := c.runtime.LookupImageIndex(name) if err == nil { - schema2List, err := manifestList.Inspect() + schema2List, err := manifestList.Index.Inspect() if err != nil { rawSchema2List, err := json.Marshal(schema2List) if err != nil { return err } - + return printManifest(rawSchema2List) } - if !errors.Is(err, packErrors.ErrImageUnknown) && !errors.Is(err, packErrors.ErrNotAManifestList) { + if !errors.Is(err, packErrors.ErrIndexUnknown) && !errors.Is(err, packErrors.ErrNotAddManifestList) { return err } @@ -49,7 +49,7 @@ func (c *Client) InspectManifest(ctx context.Context, name string, opts InspectM fmt.Printf("error parsing reference to image %q: %v", name, err) } - index, err := c.runtime.FetchIndex(name) + index, err := c.indexFactory.FetchIndex(name) if err != nil { return err diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index f6ae71da0..968734e16 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -2,13 +2,15 @@ package client import ( "context" - "github.com/buildpacks/imgutil" + + runtime "github.com/buildpacks/pack/internal/runtime" ) type PushManifestOptions struct { - Format string + Format string Insecure, Purge bool } + // PushManifest implements commands.PackClient. func (c *Client) PushManifest(ctx context.Context, index string, opts PushManifestOptions) (imageID string, err error) { manifestList, err := c.runtime.LookupImageIndex(index) @@ -16,20 +18,15 @@ func (c *Client) PushManifest(ctx context.Context, index string, opts PushManife return } - _, list, err := c.runtime.LoadFromImage(manifestList.ID()) - if err != nil { - return - } - - _, _, err = list.Push(ctx, parseFalgsForImgUtil(opts)) + _, err = manifestList.Push(ctx, parseFalgsForImgUtil(opts)) if err == nil && opts.Purge { - c.runtime.RemoveManifests(ctx, []string{manifestList.ID()}) + c.runtime.RemoveManifests(ctx, []string{index}) } return imageID, err } -func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions imgutil.IndexOptions) { +func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions runtime.PushOptions) { return idxOptions } diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index 9952f5267..d34ca5737 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -13,15 +13,15 @@ func (c *Client) RemoveManifest(ctx context.Context, name string, images []strin } for _, image := range images { - d, err := c.runtime.ParseDigest(image) + _, err := c.runtime.ParseReference(image) if err != nil { fmt.Errorf(`Invalid instance "%s": %v`, image, err) } - if err := imgIndex.Remove(d); err != nil { + if err := imgIndex.Remove(image); err != nil { return err } - fmt.Printf("%s: %s\n", imgIndex.ID(), d.String()) + fmt.Printf("Successfully removed %s from %s", image, name) } - + return nil } diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go new file mode 100644 index 000000000..669d3e8ab --- /dev/null +++ b/pkg/errors/errors.go @@ -0,0 +1,9 @@ +package errors + +import "errors" + +var ( + ErrDuplicateName = errors.New("Image/ImageIndex with the given name exists") + ErrIndexUnknown = errors.New("cannot find Image Index with the given name") + ErrNotAddManifestList = errors.New("error while adding ImageIndex to the list") +) diff --git a/pkg/testmocks/mock_image_factory.go b/pkg/testmocks/mock_image_factory.go index 42e4ec6b4..7b12410e6 100644 --- a/pkg/testmocks/mock_image_factory.go +++ b/pkg/testmocks/mock_image_factory.go @@ -9,6 +9,7 @@ import ( imgutil "github.com/buildpacks/imgutil" gomock "github.com/golang/mock/gomock" + name "github.com/google/go-containerregistry/pkg/name" ) // MockImageFactory is a mock of ImageFactory interface. @@ -34,6 +35,37 @@ func (m *MockImageFactory) EXPECT() *MockImageFactoryMockRecorder { return m.recorder } +// FindImage mocks base method. +func (m *MockImageFactory) FindImage(arg0 string) (name.Reference, imgutil.Image, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FindImage", arg0) + ret0, _ := ret[0].(name.Reference) + ret1, _ := ret[1].(imgutil.Image) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// FindImage indicates an expected call of FindImage. +func (mr *MockImageFactoryMockRecorder) FindImage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindImage", reflect.TypeOf((*MockImageFactory)(nil).FindImage), arg0) +} + +// LoadImage mocks base method. +func (m *MockImageFactory) LoadImage(arg0 string, arg1 imgutil.Platform) (imgutil.Image, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadImage", arg0, arg1) + ret0, _ := ret[0].(imgutil.Image) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LoadImage indicates an expected call of LoadImage. +func (mr *MockImageFactoryMockRecorder) LoadImage(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadImage", reflect.TypeOf((*MockImageFactory)(nil).LoadImage), arg0, arg1) +} + // NewImage mocks base method. func (m *MockImageFactory) NewImage(arg0 string, arg1 bool, arg2 string) (imgutil.Image, error) { m.ctrl.T.Helper()