From 835902d2a3646a853514604f766025759ee0c661 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Mon, 20 Mar 2023 19:25:53 +0530 Subject: [PATCH 01/79] commands: manifest: command to handle manifests Add a new manifest command to pack root commands and add a create subcommand to the manifest command. Signed-off-by: Husni Faiz --- cmd/cmd.go | 2 + internal/commands/manifest.go | 20 ++++ internal/commands/manifest_create.go | 138 +++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 internal/commands/manifest.go create mode 100644 internal/commands/manifest_create.go diff --git a/cmd/cmd.go b/cmd/cmd.go index aacebe8a25..394b5070a9 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -95,6 +95,8 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) { rootCmd.AddCommand(commands.CreateBuilder(logger, cfg, packClient)) rootCmd.AddCommand(commands.PackageBuildpack(logger, cfg, packClient, buildpackage.NewConfigReader())) + rootCmd.AddCommand(commands.NewManifestCommand(logger)) + if cfg.Experimental { rootCmd.AddCommand(commands.AddBuildpackRegistry(logger, cfg, cfgPath)) rootCmd.AddCommand(commands.ListBuildpackRegistries(logger, cfg)) diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go new file mode 100644 index 0000000000..07baef4178 --- /dev/null +++ b/internal/commands/manifest.go @@ -0,0 +1,20 @@ +package commands + +import ( + "github.com/buildpacks/pack/pkg/logging" + "github.com/spf13/cobra" +) + +func NewManifestCommand(logger logging.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "manifest", + Aliases: []string{"manifest"}, + Short: "Create manifest list", + RunE: nil, + } + + cmd.AddCommand(ManifestCreate(logger)) + + AddHelpFlag(cmd, "manifest") + return cmd +} diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go new file mode 100644 index 0000000000..7c7ec916cb --- /dev/null +++ b/internal/commands/manifest_create.go @@ -0,0 +1,138 @@ +package commands + +import ( + "fmt" + "strings" + + "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/remote" + "github.com/buildpacks/pack/pkg/logging" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/crane" + "github.com/spf13/cobra" +) + +// BuildpackNew generates the scaffolding of a buildpack +func ManifestCreate(logger logging.Logger) *cobra.Command { + // var flags BuildpackNewFlags + cmd := &cobra.Command{ + Use: "create ", + Short: "Creates a manifest list", + Args: cobra.MatchAll(cobra.MinimumNArgs(2)), + Example: `pack manifest create paketobuildpacks/builder:full-1.0.0 \ paketobuildpacks/builder:full-linux-amd64 \ + paketobuildpacks/builder:full-linux-arm`, + Long: "manifest create generates a manifest list for a multi-arch image", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + id := args[0] + idParts := strings.Split(id, " ") + // dirName := idParts[len(idParts)-1] + + fmt.Println(id) + + repoName := "registry-1.docker.io" + manifestListName := idParts[0] + + // for i, j := range args { + // manifestVal, err := crane.Manifest(args[i]) + // if err != nil { + // return err + // } + // manifest := &v1.Manifest{} + // json.Unmarshal(manifestVal, manifest) + + // } + + manifest1, err := crane.Manifest(args[1]) + manifest2, err := crane.Digest(args[2]) + fmt.Print(string(manifest1)) + fmt.Print(string(manifest2)) + + img, err := remote.NewImage( + repoName, + authn.DefaultKeychain, + remote.FromBaseImage(manifestListName), + remote.WithDefaultPlatform(imgutil.Platform{ + OS: "linux", + Architecture: "amd64", + }), + ) + + // manifest := v1.Manifest{} + // err := json.Unmarshal(data, &manifest) + + // manifest := &v1.Manifest{} + // if err := unmarshalJSONFromBlob(blob, pathFromDescriptor(*manifestDescriptor), manifest); err != nil { + // return nil, err + // } + + // arch, err := img.Architecture() + + os, err := img.OS() + + // manifestSize, err := img.ManifestSize() + // labels, err := img.Labels() + + if err != nil { + return err + } + + fmt.Println(os) + // fmt.Println(labels) + // img.fetchRemoteImage + + // tag := "hello-universe" + // repository := "cnbs/sample-package" + // url := fmt.Sprintf("https://registry-1.docker.io/v2/%s/manifests/%s", repository, tag) + + // req, err := http.NewRequest("GET", url, nil) + // if err != nil { + // fmt.Println(err) + // return nil + // } + + // // Encode the Docker registry username and password in base64 format + // auth := base64.StdEncoding.EncodeToString([]byte("drac98:dckr_pat_-t8WI7sW7xE2xoew5lr6YM3jbY0")) + // req.Header.Set("Bearer", fmt.Sprintf("Basic %s", auth)) + // req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json") + + // client := &http.Client{} + // resp, err := client.Do(req) + // if err != nil { + // fmt.Println(err) + // return nil + // } + + // defer resp.Body.Close() + // body, err := ioutil.ReadAll(resp.Body) + // if err != nil { + // fmt.Println(err) + // return nil + // } + + // fmt.Println(string(body)) + + // var path string + // if len(flags.Path) == 0 { + // cwd, err := os.Getwd() + // if err != nil { + // return err + // } + // path = filepath.Join(cwd, dirName) + // } else { + // path = flags.Path + // } + + // _, err := os.Stat(path) + // if !os.IsNotExist(err) { + // return fmt.Errorf("directory %s exists", style.Symbol(path)) + // } + + return nil + }), + } + + // cmd.Flags().StringVarP(&flags.API, "api", "a", "0.8", "Buildpack API compatibility of the generated buildpack") + + AddHelpFlag(cmd, "create") + return cmd +} From ffe1f9382f37c825d3a5a6bcc2a7e160b5173e5f Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 21 Mar 2023 05:27:24 +0530 Subject: [PATCH 02/79] cmd: manifest create: first PoC Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 154 ++++++++++----------------- 1 file changed, 55 insertions(+), 99 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 7c7ec916cb..5869e1f446 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -1,14 +1,17 @@ package commands import ( - "fmt" - "strings" + "encoding/json" "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/remote" "github.com/buildpacks/pack/pkg/logging" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/crane" + + // v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" ) @@ -23,109 +26,62 @@ func ManifestCreate(logger logging.Logger) *cobra.Command { paketobuildpacks/builder:full-linux-arm`, Long: "manifest create generates a manifest list for a multi-arch image", RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - id := args[0] - idParts := strings.Split(id, " ") - // dirName := idParts[len(idParts)-1] - - fmt.Println(id) - - repoName := "registry-1.docker.io" - manifestListName := idParts[0] - - // for i, j := range args { - // manifestVal, err := crane.Manifest(args[i]) - // if err != nil { - // return err - // } - // manifest := &v1.Manifest{} - // json.Unmarshal(manifestVal, manifest) - - // } - - manifest1, err := crane.Manifest(args[1]) - manifest2, err := crane.Digest(args[2]) - fmt.Print(string(manifest1)) - fmt.Print(string(manifest2)) - - img, err := remote.NewImage( - repoName, - authn.DefaultKeychain, - remote.FromBaseImage(manifestListName), - remote.WithDefaultPlatform(imgutil.Platform{ - OS: "linux", - Architecture: "amd64", - }), - ) - - // manifest := v1.Manifest{} - // err := json.Unmarshal(data, &manifest) - - // manifest := &v1.Manifest{} - // if err := unmarshalJSONFromBlob(blob, pathFromDescriptor(*manifestDescriptor), manifest); err != nil { - // return nil, err - // } - - // arch, err := img.Architecture() - - os, err := img.OS() + var manifests []v1.Descriptor + desc := v1.Descriptor{} + + for _, j := range args[1:] { + digestVal, err := crane.Digest(j) + if err != nil { + return err + } + + manifestVal, err := crane.Manifest(j) + if err != nil { + return err + } + + manifest := v1.Manifest{} + json.Unmarshal(manifestVal, &manifest) + + img, err := remote.NewImage( + "registry-1.docker.io", + authn.DefaultKeychain, + remote.FromBaseImage(j), + remote.WithDefaultPlatform(imgutil.Platform{ + OS: "linux", + Architecture: "amd64", + }), + ) + + // fmt.Println(manifest) + os, _ := img.OS() + arch, _ := img.Architecture() + + platform := v1.Platform{} + platform.Architecture = arch + platform.OS = os + + desc.Size, _ = img.ManifestSize() + desc.Platform = &platform + desc.Digest = digest.Digest(digestVal) + desc.MediaType = manifest.MediaType + + manifests = append(manifests, desc) - // manifestSize, err := img.ManifestSize() - // labels, err := img.Labels() - - if err != nil { - return err } - fmt.Println(os) - // fmt.Println(labels) - // img.fetchRemoteImage - - // tag := "hello-universe" - // repository := "cnbs/sample-package" - // url := fmt.Sprintf("https://registry-1.docker.io/v2/%s/manifests/%s", repository, tag) - - // req, err := http.NewRequest("GET", url, nil) - // if err != nil { - // fmt.Println(err) - // return nil - // } - - // // Encode the Docker registry username and password in base64 format - // auth := base64.StdEncoding.EncodeToString([]byte("drac98:dckr_pat_-t8WI7sW7xE2xoew5lr6YM3jbY0")) - // req.Header.Set("Bearer", fmt.Sprintf("Basic %s", auth)) - // req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json") + index := v1.Index{} + index.SchemaVersion = 2 + index.MediaType = "application/vnd.oci.image.index.v1+json" + index.Manifests = manifests - // client := &http.Client{} - // resp, err := client.Do(req) - // if err != nil { - // fmt.Println(err) - // return nil + // for _, j := range manifests { + // de, _ := json.Marshal(j) + // logger.Infof(string(de)) // } - // defer resp.Body.Close() - // body, err := ioutil.ReadAll(resp.Body) - // if err != nil { - // fmt.Println(err) - // return nil - // } - - // fmt.Println(string(body)) - - // var path string - // if len(flags.Path) == 0 { - // cwd, err := os.Getwd() - // if err != nil { - // return err - // } - // path = filepath.Join(cwd, dirName) - // } else { - // path = flags.Path - // } - - // _, err := os.Stat(path) - // if !os.IsNotExist(err) { - // return fmt.Errorf("directory %s exists", style.Symbol(path)) - // } + de, _ := json.Marshal(index) + logger.Infof(string(de)) return nil }), From 08ec40676d0503ed95c3317d35d5443d879b9485 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Mon, 27 Mar 2023 19:28:12 +0530 Subject: [PATCH 03/79] cmd: manifest: refactor code Use source/sinks instead of using primitives directly Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 82 ++++++++++++++-------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 5869e1f446..a8a768ca69 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -1,23 +1,23 @@ package commands import ( - "encoding/json" + v2 "github.com/buildpacks/imgutil/remote" - "github.com/buildpacks/imgutil" - "github.com/buildpacks/imgutil/remote" + "github.com/buildpacks/imgutil/layout" "github.com/buildpacks/pack/pkg/logging" "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/crane" + v1 "github.com/google/go-containerregistry/pkg/v1" - // v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/opencontainers/go-digest" - v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/types" ) -// BuildpackNew generates the scaffolding of a buildpack func ManifestCreate(logger logging.Logger) *cobra.Command { - // var flags BuildpackNewFlags cmd := &cobra.Command{ Use: "create ", Short: "Creates a manifest list", @@ -26,62 +26,60 @@ func ManifestCreate(logger logging.Logger) *cobra.Command { paketobuildpacks/builder:full-linux-arm`, Long: "manifest create generates a manifest list for a multi-arch image", RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - var manifests []v1.Descriptor - desc := v1.Descriptor{} + + var index v1.ImageIndex + + // initialize and set the media type + index = empty.Index + index = mutate.IndexMediaType(index, types.DockerManifestList) + + var adds []mutate.IndexAddendum for _, j := range args[1:] { - digestVal, err := crane.Digest(j) + + ref, err := name.ParseReference(j) if err != nil { - return err + panic(err) } - manifestVal, err := crane.Manifest(j) + img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) if err != nil { - return err + panic(err) } - manifest := v1.Manifest{} - json.Unmarshal(manifestVal, &manifest) + desc, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + if err != nil { + panic(err) + } + + // ################################################## - img, err := remote.NewImage( + img2, err := v2.NewImage( "registry-1.docker.io", authn.DefaultKeychain, - remote.FromBaseImage(j), - remote.WithDefaultPlatform(imgutil.Platform{ - OS: "linux", - Architecture: "amd64", - }), + v2.FromBaseImage(j), ) - // fmt.Println(manifest) - os, _ := img.OS() - arch, _ := img.Architecture() + os, _ := img2.OS() + arch, _ := img2.Architecture() platform := v1.Platform{} platform.Architecture = arch platform.OS = os - desc.Size, _ = img.ManifestSize() - desc.Platform = &platform - desc.Digest = digest.Digest(digestVal) - desc.MediaType = manifest.MediaType + desc.Descriptor.Platform = &platform + logger.Infof(desc.Descriptor.Platform.OS + "-" + desc.Descriptor.Platform.Architecture) - manifests = append(manifests, desc) + // ################################################## - } + adds = append(adds, mutate.IndexAddendum{Add: img, Descriptor: desc.Descriptor}) - index := v1.Index{} - index.SchemaVersion = 2 - index.MediaType = "application/vnd.oci.image.index.v1+json" - index.Manifests = manifests + } - // for _, j := range manifests { - // de, _ := json.Marshal(j) - // logger.Infof(string(de)) - // } + index = mutate.AppendManifests(index, adds...) - de, _ := json.Marshal(index) - logger.Infof(string(de)) + // write the index on disk, for example + layout.Write("out/", index) return nil }), From 9b2a97873a36efec7f6e7e5380a8e9567ecf5c47 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Sun, 2 Apr 2023 01:16:28 +0530 Subject: [PATCH 04/79] cmd: manifest: use ggcr library Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 30 +++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index a8a768ca69..8706c80ea0 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -1,7 +1,9 @@ package commands import ( - v2 "github.com/buildpacks/imgutil/remote" + "fmt" + + "github.com/pkg/errors" "github.com/buildpacks/imgutil/layout" "github.com/buildpacks/pack/pkg/logging" @@ -52,25 +54,25 @@ func ManifestCreate(logger logging.Logger) *cobra.Command { panic(err) } - // ################################################## + cfg, err := img.ConfigFile() - img2, err := v2.NewImage( - "registry-1.docker.io", - authn.DefaultKeychain, - v2.FromBaseImage(j), - ) + if err != nil { + return errors.Wrapf(err, "getting config file for image %q", j) + } + if cfg == nil { + return fmt.Errorf("missing config for image %q", j) + } + if cfg.OS == "" { + return fmt.Errorf("missing OS for image %q", j) + } - os, _ := img2.OS() - arch, _ := img2.Architecture() + // desc.Descriptor.Platform platform := v1.Platform{} - platform.Architecture = arch - platform.OS = os + platform.Architecture = cfg.Architecture + platform.OS = cfg.OS desc.Descriptor.Platform = &platform - logger.Infof(desc.Descriptor.Platform.OS + "-" + desc.Descriptor.Platform.Architecture) - - // ################################################## adds = append(adds, mutate.IndexAddendum{Add: img, Descriptor: desc.Descriptor}) From 3fc79cc01f17ac8eb1609faa7228a46777028821 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Wed, 5 Apr 2023 15:02:11 +0530 Subject: [PATCH 05/79] cmd: manifest: use imgutil Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 68 +++------------------------- 1 file changed, 6 insertions(+), 62 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 8706c80ea0..2c703ea61c 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -1,22 +1,10 @@ package commands import ( - "fmt" - - "github.com/pkg/errors" - - "github.com/buildpacks/imgutil/layout" + "github.com/buildpacks/imgutil/remote" "github.com/buildpacks/pack/pkg/logging" - "github.com/google/go-containerregistry/pkg/authn" - v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/spf13/cobra" - - "github.com/google/go-containerregistry/pkg/name" - "github.com/google/go-containerregistry/pkg/v1/empty" - "github.com/google/go-containerregistry/pkg/v1/mutate" - "github.com/google/go-containerregistry/pkg/v1/remote" - "github.com/google/go-containerregistry/pkg/v1/types" ) func ManifestCreate(logger logging.Logger) *cobra.Command { @@ -29,59 +17,15 @@ func ManifestCreate(logger logging.Logger) *cobra.Command { Long: "manifest create generates a manifest list for a multi-arch image", RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - var index v1.ImageIndex - - // initialize and set the media type - index = empty.Index - index = mutate.IndexMediaType(index, types.DockerManifestList) - - var adds []mutate.IndexAddendum + idx, _ := remote.NewIndex(args[0]) // This will return an empty index + // Add every manifest to image index for _, j := range args[1:] { - - ref, err := name.ParseReference(j) - if err != nil { - panic(err) - } - - img, err := remote.Image(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) - if err != nil { - panic(err) - } - - desc, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain)) - if err != nil { - panic(err) - } - - cfg, err := img.ConfigFile() - - if err != nil { - return errors.Wrapf(err, "getting config file for image %q", j) - } - if cfg == nil { - return fmt.Errorf("missing config for image %q", j) - } - if cfg.OS == "" { - return fmt.Errorf("missing OS for image %q", j) - } - - // desc.Descriptor.Platform - - platform := v1.Platform{} - platform.Architecture = cfg.Architecture - platform.OS = cfg.OS - - desc.Descriptor.Platform = &platform - - adds = append(adds, mutate.IndexAddendum{Add: img, Descriptor: desc.Descriptor}) - + idx.Add(j) } - index = mutate.AppendManifests(index, adds...) - - // write the index on disk, for example - layout.Write("out/", index) + // Store layout in local storage + idx.Save("out/index") return nil }), From 6b6f2b061482218fd07f701ac44eb6bf64a77589 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 18 Apr 2023 08:00:24 +0530 Subject: [PATCH 06/79] manifest: use pack client interface Signed-off-by: Husni Faiz --- cmd/cmd.go | 2 +- internal/commands/manifest.go | 6 +++--- internal/commands/manifest_create.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index 394b5070a9..ddc06b40d8 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -95,7 +95,7 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) { rootCmd.AddCommand(commands.CreateBuilder(logger, cfg, packClient)) rootCmd.AddCommand(commands.PackageBuildpack(logger, cfg, packClient, buildpackage.NewConfigReader())) - rootCmd.AddCommand(commands.NewManifestCommand(logger)) + rootCmd.AddCommand(commands.NewManifestCommand(logger, packClient)) if cfg.Experimental { rootCmd.AddCommand(commands.AddBuildpackRegistry(logger, cfg, cfgPath)) diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index 07baef4178..c83bf463f9 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -5,15 +5,15 @@ import ( "github.com/spf13/cobra" ) -func NewManifestCommand(logger logging.Logger) *cobra.Command { +func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "manifest", Aliases: []string{"manifest"}, - Short: "Create manifest list", + Short: "Handle manifest list", RunE: nil, } - cmd.AddCommand(ManifestCreate(logger)) + cmd.AddCommand(ManifestCreate(logger, client)) AddHelpFlag(cmd, "manifest") return cmd diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 2c703ea61c..2bbff28752 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -func ManifestCreate(logger logging.Logger) *cobra.Command { +func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "create ", Short: "Creates a manifest list", From e3908c266078c7668cbeadcc055d39366d4147a7 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 18 Apr 2023 08:04:09 +0530 Subject: [PATCH 07/79] manifest_create: separate input validation and manifest creation Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 67 ++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 2bbff28752..effd8a337f 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -1,38 +1,79 @@ package commands import ( - "github.com/buildpacks/imgutil/remote" + "path/filepath" + + "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" + "github.com/pkg/errors" "github.com/spf13/cobra" ) +type ManifestCreateFlags struct { + Publish bool + Insecure bool + Registry string + Format string + LayoutDir string +} + func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { + var flags ManifestCreateFlags cmd := &cobra.Command{ - Use: "create ", + Use: "create [ ... ]", Short: "Creates a manifest list", Args: cobra.MatchAll(cobra.MinimumNArgs(2)), - Example: `pack manifest create paketobuildpacks/builder:full-1.0.0 \ paketobuildpacks/builder:full-linux-amd64 \ - paketobuildpacks/builder:full-linux-arm`, + Example: `pack manifest create create cnbs/sample-package:hello-multiarch-universe \ + cnbs/sample-package:hello-universe \ + cnbs/sample-package:hello-universe-windows`, Long: "manifest create generates a manifest list for a multi-arch image", RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - - idx, _ := remote.NewIndex(args[0]) // This will return an empty index - - // Add every manifest to image index - for _, j := range args[1:] { - idx.Add(j) + if err := validateManifestCreateFlags(&flags); err != nil { + return err } - // Store layout in local storage - idx.Save("out/index") + layoutDir := "" + if flags.LayoutDir == "" { + layoutDir = "./oci-layout" + } else { + layoutDir = flags.LayoutDir + } + layoutDir, err := filepath.Abs(filepath.Dir(layoutDir)) + if err != nil { + return errors.Wrap(err, "getting absolute layout path") + } + indexName := args[0] + manifests := args[1:] + if err := pack.CreateManifest(cmd.Context(), client.CreateManifestOptions{ + ManifestName: indexName, + Manifests: manifests, + Format: flags.Format, + Publish: flags.Publish, + Registry: flags.Registry, + LayoutDir: layoutDir, + }); err != nil { + return err + } + logger.Infof("Successfully created image index %s", style.Symbol(indexName)) + // logging.Tip(logger, "Run %s to use this builder", style.Symbol(fmt.Sprintf("pack build --builder %s", imageName))) return nil + }), } - // cmd.Flags().StringVarP(&flags.API, "api", "a", "0.8", "Buildpack API compatibility of the generated buildpack") + cmd.Flags().BoolVar(&flags.Publish, "publish", false, `Publish to registry`) + cmd.Flags().BoolVar(&flags.Insecure, "insecure", false, `Allow publishing to insecure registry`) + cmd.Flags().StringVarP(&flags.Format, "format", "f", "", `Format to save image index as ("OCI" or "V2S2")`) + cmd.Flags().StringVarP(&flags.Registry, "registry", "reg", "", `Registry URL to publish the image index`) + cmd.Flags().StringVarP(&flags.LayoutDir, "layout", "out", "", `Relative directory path to save the OCI layout`) AddHelpFlag(cmd, "create") return cmd } + +func validateManifestCreateFlags(p *ManifestCreateFlags) error { + return nil +} From d767b1ea4f1beb8cf63828c4870b57646e8bb2dd Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 18 Apr 2023 08:08:44 +0530 Subject: [PATCH 08/79] client: manifest sub command logic function templates Function templates for the following manifest sub command logics - create - add - annotate - push - remove Signed-off-by: Husni Faiz --- internal/commands/commands.go | 5 +++ pkg/client/add_manifest.go | 16 ++++++++ pkg/client/annotate_manifest.go | 15 ++++++++ pkg/client/create_manifest.go | 68 +++++++++++++++++++++++++++++++++ pkg/client/push_manifest.go | 11 ++++++ pkg/client/remove_manifest.go | 11 ++++++ 6 files changed, 126 insertions(+) create mode 100644 pkg/client/add_manifest.go create mode 100644 pkg/client/annotate_manifest.go create mode 100644 pkg/client/create_manifest.go create mode 100644 pkg/client/push_manifest.go create mode 100644 pkg/client/remove_manifest.go diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 11fa645ca5..b912c3f92a 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -32,6 +32,11 @@ type PackClient interface { InspectExtension(client.InspectExtensionOptions) (*client.ExtensionInfo, error) PullBuildpack(context.Context, client.PullBuildpackOptions) error DownloadSBOM(name string, options client.DownloadSBOMOptions) error + CreateManifest(context.Context, client.CreateManifestOptions) error + AnnotateManifest(context.Context, client.AnnotateManifestOptions) error + AddManifest(context.Context, client.AddManifestOptions) error + PushManifest(context.Context, client.PushManifestOptions) error + RemoveManifest(context.Context, client.RemoveManifestOptions) error } func AddHelpFlag(cmd *cobra.Command, commandName string) { diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go new file mode 100644 index 0000000000..89844aad22 --- /dev/null +++ b/pkg/client/add_manifest.go @@ -0,0 +1,16 @@ +package client + +import "context" + +type AddManifestOptions struct { + Index string + Manifest string + Architecture string + OS string + Variant string + All bool +} + +func (c *Client) AddManifest(ctx context.Context, opts AddManifestOptions) error { + return nil +} diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go new file mode 100644 index 0000000000..a5b48f8750 --- /dev/null +++ b/pkg/client/annotate_manifest.go @@ -0,0 +1,15 @@ +package client + +import "context" + +type AnnotateManifestOptions struct { + Index string + Manifest string + Architecture string + OS string + Variant string +} + +func (c *Client) AnnotateManifest(ctx context.Context, opts AnnotateManifestOptions) error { + return nil +} diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go new file mode 100644 index 0000000000..2379d840fb --- /dev/null +++ b/pkg/client/create_manifest.go @@ -0,0 +1,68 @@ +package client + +import ( + "context" + + "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/remote" + "github.com/google/go-containerregistry/pkg/authn" +) + +type CreateManifestOptions struct { + // Name of the ManifestList. + ManifestName string + + // List of Images + Manifests []string + + // Manifest list type (oci or v2s2) to use when pushing the list (default is v2s2) + Format string + + // Skip creating index locally, directly publish to a registry. + // Requires ManifestName to be a valid registry location. + Publish bool + + // Defines the registry to publish the manifest list. + Registry string + + // Directory to store OCI layout + LayoutDir string +} + +func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) error { + + mediaType := imgutil.DockerTypes + if opts.Format == "oci" { + mediaType = imgutil.OCITypes + } else if opts.Format == "v2s2" { + mediaType = imgutil.DockerTypes + } else { + mediaType = imgutil.DockerTypes + } + + idx, err := remote.NewIndex( + opts.ManifestName, + authn.DefaultKeychain, + remote.WithIndexMediaTypes(mediaType), + remote.WithPath(opts.LayoutDir)) // This will return an empty index + if err != nil { + panic(err) + } + + // When the publish flag is used all the manifests MUST have os/arch defined otherwise an error must be thrown + // The format flag will be ignored if it is not used in conjunction with the publish flag + + // Add every manifest to image index + for _, j := range opts.Manifests { + err := idx.Add(j) + if err != nil { + panic(err) + } + } + + // Store layout in local storage + idx.Save() + + return nil + +} diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go new file mode 100644 index 0000000000..5b99c90f7b --- /dev/null +++ b/pkg/client/push_manifest.go @@ -0,0 +1,11 @@ +package client + +import "context" + +type PushManifestOptions struct { + Index string +} + +func (c *Client) PushManifest(ctx context.Context, opts PushManifestOptions) error { + return nil +} diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go new file mode 100644 index 0000000000..ee82eb1d15 --- /dev/null +++ b/pkg/client/remove_manifest.go @@ -0,0 +1,11 @@ +package client + +import "context" + +type RemoveManifestOptions struct { + Index string +} + +func (c *Client) RemoveManifest(ctx context.Context, opts RemoveManifestOptions) error { + return nil +} From 3c0b9ccec8593f5aac6e949d8b139661963df61d Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 18 Apr 2023 08:13:24 +0530 Subject: [PATCH 09/79] commands: manifest sub command templates Command templates for the following manifest sub commands - Add - Annotate - Push - Remove Signed-off-by: Husni Faiz --- internal/commands/manifest_add.go | 57 ++++++++++++++++++++++++++ internal/commands/manifest_annotate.go | 54 ++++++++++++++++++++++++ internal/commands/manifest_push.go | 57 ++++++++++++++++++++++++++ internal/commands/manifest_remove.go | 33 +++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 internal/commands/manifest_add.go create mode 100644 internal/commands/manifest_annotate.go create mode 100644 internal/commands/manifest_push.go create mode 100644 internal/commands/manifest_remove.go diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go new file mode 100644 index 0000000000..34397a25db --- /dev/null +++ b/internal/commands/manifest_add.go @@ -0,0 +1,57 @@ +package commands + +import ( + "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/pkg/client" + "github.com/buildpacks/pack/pkg/logging" + "github.com/spf13/cobra" +) + +type ManifestAddFlags struct { + All bool + Architecture string + OS string + Variant string +} + +func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { + var flags ManifestAddFlags + cmd := &cobra.Command{ + Use: "add [OPTIONS] ", + Short: "Add a new image to the manifest list", + Args: cobra.MatchAll(cobra.ExactArgs(2)), + Example: `pack manifest add cnbs/sample-package:hello-multiarch-universe \ + cnbs/sample-package:hello-universe-riscv-linux`, + Long: "manifest add modifies a manifest list (Image index) and add a new image to the list of manifests.", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + if err := validateManifestAddFlags(&flags); err != nil { + return err + } + + indexName := args[0] + manifest := args[1] + if err := pack.AddManifest(cmd.Context(), client.AddManifestOptions{ + Index: indexName, + Manifest: manifest, + }); err != nil { + return err + } + logger.Infof("Successfully added the image %s to the image index %s", style.Symbol(manifest), style.Symbol(indexName)) + + return nil + + }), + } + + cmd.Flags().BoolVar(&flags.All, "all", false, `add all of the contents to the local list (applies only if is an index)`) + cmd.Flags().StringVar(&flags.Architecture, "arch", "", "Set the architecutre") + cmd.Flags().StringVar(&flags.OS, "os", "", "Set the operating system") + cmd.Flags().StringVar(&flags.Variant, "variant", "", "Set the architecutre variant") + + AddHelpFlag(cmd, "add") + return cmd +} + +func validateManifestAddFlags(p *ManifestAddFlags) error { + return nil +} diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go new file mode 100644 index 0000000000..2b7ab90712 --- /dev/null +++ b/internal/commands/manifest_annotate.go @@ -0,0 +1,54 @@ +package commands + +import ( + "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/pkg/client" + "github.com/buildpacks/pack/pkg/logging" + "github.com/spf13/cobra" +) + +type ManifestAnnotateFlags struct { + Architecture string // Set the architecture + OS string // Set the operating system + Variant string // Set the architecture variant +} + +func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { + var flags ManifestAnnotateFlags + cmd := &cobra.Command{ + Use: "annotate [OPTIONS] ", + Short: "Annotate a manifest list", + Args: cobra.MatchAll(cobra.ExactArgs(2)), + Example: `pack manifest annotate paketobuildpacks/builder:full-1.0.0 \ paketobuildpacks/builder:full-linux-amd64`, + Long: "manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + if err := validateManifestAnnotateFlags(&flags); err != nil { + return err + } + + indexName := args[0] + manifest := args[1] + if err := pack.AnnotateManifest(cmd.Context(), client.AnnotateManifestOptions{ + Index: indexName, + Manifest: manifest, + }); err != nil { + return err + } + logger.Infof("Successfully annotated image index %s", style.Symbol(indexName)) + + return nil + + }), + } + + cmd.Flags().StringVar(&flags.Architecture, "arch", "", "Set the architecutre") + cmd.Flags().StringVar(&flags.OS, "os", "", "Set the operating system") + cmd.Flags().StringVar(&flags.Variant, "variant", "", "Set the architecutre variant") + + AddHelpFlag(cmd, "annotate") + return cmd +} + +func validateManifestAnnotateFlags(p *ManifestAnnotateFlags) error { + return nil +} diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go new file mode 100644 index 0000000000..149f85cd63 --- /dev/null +++ b/internal/commands/manifest_push.go @@ -0,0 +1,57 @@ +package commands + +import ( + "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/pkg/client" + "github.com/buildpacks/pack/pkg/logging" + "github.com/spf13/cobra" +) + +type ManifestPushFlags struct { + // Manifest list type (oci or v2s2) to use when pushing the list (default is v2s2). + Format string + + // Allow push to an insecure registry. + Insecure bool + + //// Delete the manifest list or image index from local storage if pushing succeeds. + Purge bool +} + +func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { + var flags ManifestPushFlags + cmd := &cobra.Command{ + Use: "push [OPTIONS] ", + Short: "Push a manifest list to a repository", + Args: cobra.MatchAll(cobra.ExactArgs(2)), + Example: `pack manifest push cnbs/sample-package:hello-multiarch-universe`, + Long: "manifest push pushes a manifest list (Image index) to a registry.", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + if err := validateManifestPushFlags(&flags); err != nil { + return err + } + + indexName := args[0] + if err := pack.PushManifest(cmd.Context(), client.PushManifestOptions{ + Index: indexName, + }); err != nil { + return err + } + logger.Infof("Successfully pushed the %s image index to the repository.", style.Symbol(indexName)) + + return nil + + }), + } + + cmd.Flags().BoolVar(&flags.Insecure, "insecure", false, `Allow publishing to insecure registry`) + cmd.Flags().BoolVarP(&flags.Purge, "purge", "p", false, `Delete the manifest list or image index from local storage if pushing succeeds`) + cmd.Flags().StringVarP(&flags.Format, "format", "f", "", `Manifest list type (oci or v2s2) to use when pushing the list (default is v2s2)`) + + AddHelpFlag(cmd, "push") + return cmd +} + +func validateManifestPushFlags(p *ManifestPushFlags) error { + return nil +} diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go new file mode 100644 index 0000000000..7cf7e6f132 --- /dev/null +++ b/internal/commands/manifest_remove.go @@ -0,0 +1,33 @@ +package commands + +import ( + "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/pkg/client" + "github.com/buildpacks/pack/pkg/logging" + "github.com/spf13/cobra" +) + +func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { + cmd := &cobra.Command{ + Use: "remove [manifest-list] [manifest-list...]", + Short: "Delete one or more manifest lists from local storage", + Args: cobra.MatchAll(cobra.ExactArgs(2)), + Example: `pack manifest delete cnbs/sample-package:hello-multiarch-universe`, + Long: "Delete one or more manifest lists from local storage", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + indexName := args[0] + if err := pack.RemoveManifest(cmd.Context(), client.RemoveManifestOptions{ + Index: indexName, + }); err != nil { + return err + } + logger.Infof("Successfully removed the image index %s", style.Symbol(indexName)) + + return nil + + }), + } + + AddHelpFlag(cmd, "remove") + return cmd +} From e9ca7cb1e4b88b00f66dc92ccd0506193a0dcf01 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 25 Apr 2023 23:28:51 +0530 Subject: [PATCH 10/79] bugfix: manifest: create: use single char for shorthand flag Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index effd8a337f..090d220975 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -67,8 +67,8 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().BoolVar(&flags.Publish, "publish", false, `Publish to registry`) cmd.Flags().BoolVar(&flags.Insecure, "insecure", false, `Allow publishing to insecure registry`) cmd.Flags().StringVarP(&flags.Format, "format", "f", "", `Format to save image index as ("OCI" or "V2S2")`) - cmd.Flags().StringVarP(&flags.Registry, "registry", "reg", "", `Registry URL to publish the image index`) - cmd.Flags().StringVarP(&flags.LayoutDir, "layout", "out", "", `Relative directory path to save the OCI layout`) + cmd.Flags().StringVarP(&flags.Registry, "registry", "r", "", `Registry URL to publish the image index`) + cmd.Flags().StringVarP(&flags.LayoutDir, "layout", "o", "", `Relative directory path to save the OCI layout`) AddHelpFlag(cmd, "create") return cmd From 3a7c262b546910697430d365b3f272b95c833463 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 25 Apr 2023 23:35:40 +0530 Subject: [PATCH 11/79] manifest: create: move flag validations into command logic Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 13 ++++++++++++- pkg/client/create_manifest.go | 9 +-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 090d220975..0ea3bffb33 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -3,6 +3,7 @@ package commands import ( "path/filepath" + "github.com/buildpacks/imgutil" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" @@ -34,6 +35,16 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { return err } + mediaType := imgutil.DockerTypes + format := flags.Format + if format == "oci" { + mediaType = imgutil.OCITypes + } else if format == "v2s2" || format == "" { + mediaType = imgutil.DockerTypes + } else { + return errors.Errorf("unsupported media type given for --format") + } + layoutDir := "" if flags.LayoutDir == "" { layoutDir = "./oci-layout" @@ -50,7 +61,7 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { if err := pack.CreateManifest(cmd.Context(), client.CreateManifestOptions{ ManifestName: indexName, Manifests: manifests, - Format: flags.Format, + MediaType: mediaType, Publish: flags.Publish, Registry: flags.Registry, LayoutDir: layoutDir, diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 2379d840fb..7c7c1dfa55 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -16,7 +16,7 @@ type CreateManifestOptions struct { Manifests []string // Manifest list type (oci or v2s2) to use when pushing the list (default is v2s2) - Format string + MediaType imgutil.MediaTypes // Skip creating index locally, directly publish to a registry. // Requires ManifestName to be a valid registry location. @@ -32,13 +32,6 @@ type CreateManifestOptions struct { func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) error { mediaType := imgutil.DockerTypes - if opts.Format == "oci" { - mediaType = imgutil.OCITypes - } else if opts.Format == "v2s2" { - mediaType = imgutil.DockerTypes - } else { - mediaType = imgutil.DockerTypes - } idx, err := remote.NewIndex( opts.ManifestName, From 40eade3687ddbc0046c5b35639da4d1278fb1b2e Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 25 Apr 2023 23:38:52 +0530 Subject: [PATCH 12/79] manifest: create: remove path option from remote index: Signed-off-by: Husni Faiz --- pkg/client/create_manifest.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 7c7c1dfa55..83cb1b9940 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -36,8 +36,7 @@ func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) idx, err := remote.NewIndex( opts.ManifestName, authn.DefaultKeychain, - remote.WithIndexMediaTypes(mediaType), - remote.WithPath(opts.LayoutDir)) // This will return an empty index + remote.WithIndexMediaTypes(mediaType)) // This will return an empty index if err != nil { panic(err) } From 67d56447cb78547f34f8fa6020d9074c19582495 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 25 Apr 2023 23:41:16 +0530 Subject: [PATCH 13/79] create_manifest: raise error if saving the index fails Signed-off-by: Husni Faiz --- pkg/client/create_manifest.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 83cb1b9940..0005c15112 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -52,8 +52,11 @@ func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) } } - // Store layout in local storage - idx.Save() + // Store index + err = idx.Save() + if err != nil { + panic(err) + } return nil From b6b0f0d589611727d10a21c3febee65d99007d7e Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 25 Apr 2023 23:43:21 +0530 Subject: [PATCH 14/79] manifest_create: add default layout path to save an index Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 0ea3bffb33..6e47fee4f5 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -45,12 +45,11 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { return errors.Errorf("unsupported media type given for --format") } - layoutDir := "" - if flags.LayoutDir == "" { - layoutDir = "./oci-layout" - } else { + layoutDir := "./oci-layout" + if flags.LayoutDir != "" { layoutDir = flags.LayoutDir } + layoutDir, err := filepath.Abs(filepath.Dir(layoutDir)) if err != nil { return errors.Wrap(err, "getting absolute layout path") From 43f97b5070c928ed2739a7aa669b4ddc4df0b535 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Mon, 1 May 2023 04:27:54 +0530 Subject: [PATCH 15/79] manifest: flag command as experimental Signed-off-by: Husni Faiz --- cmd/cmd.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index ddc06b40d8..c7d5210bc1 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -95,8 +95,6 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) { rootCmd.AddCommand(commands.CreateBuilder(logger, cfg, packClient)) rootCmd.AddCommand(commands.PackageBuildpack(logger, cfg, packClient, buildpackage.NewConfigReader())) - rootCmd.AddCommand(commands.NewManifestCommand(logger, packClient)) - if cfg.Experimental { rootCmd.AddCommand(commands.AddBuildpackRegistry(logger, cfg, cfgPath)) rootCmd.AddCommand(commands.ListBuildpackRegistries(logger, cfg)) @@ -104,6 +102,7 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) { rootCmd.AddCommand(commands.SetDefaultRegistry(logger, cfg, cfgPath)) rootCmd.AddCommand(commands.RemoveRegistry(logger, cfg, cfgPath)) rootCmd.AddCommand(commands.YankBuildpack(logger, cfg, packClient)) + rootCmd.AddCommand(commands.NewManifestCommand(logger, packClient)) } packHome, err := config.PackHome() From c3ac0ceaa0f4f7965d9ef593ab902bbeb531e986 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 2 May 2023 03:22:58 +0530 Subject: [PATCH 16/79] client: create an index factory Signed-off-by: Husni Faiz --- pkg/client/client.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/pkg/client/client.go b/pkg/client/client.go index b3862cc4cc..6d15d78999 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -72,6 +72,10 @@ type ImageFactory interface { NewImage(repoName string, local bool, imageOS string) (imgutil.Image, error) } +type IndexFactory interface { + NewIndex(CreateManifestOptions) (imgutil.ImageIndex, error) +} + //go:generate mockgen -package testmocks -destination ../testmocks/mock_buildpack_downloader.go github.com/buildpacks/pack/pkg/client BuildpackDownloader // BuildpackDownloader is an interface for downloading and extracting buildpacks from various sources @@ -90,6 +94,7 @@ type Client struct { keychain authn.Keychain imageFactory ImageFactory imageFetcher ImageFetcher + indexFactory IndexFactory downloader BlobDownloader lifecycleExecutor LifecycleExecutor buildpackDownloader BuildpackDownloader @@ -225,6 +230,13 @@ func NewClient(opts ...Option) (*Client, error) { } } + if client.indexFactory == nil { + client.indexFactory = &indexFactory{ + dockerClient: client.docker, + keychain: client.keychain, + } + } + if client.buildpackDownloader == nil { client.buildpackDownloader = buildpack.NewDownloader( client.logger, @@ -273,3 +285,22 @@ func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (i return remote.NewImage(repoName, f.keychain, remote.WithDefaultPlatform(platform)) } + +type indexFactory struct { + dockerClient local.DockerClient + keychain authn.Keychain +} + +func (f *indexFactory) NewIndex(opts CreateManifestOptions) (imgutil.ImageIndex, error) { + if opts.Publish { + return remote.NewIndex( + opts.ManifestName, + f.keychain, + remote.WithIndexMediaTypes(opts.MediaType)) + } + + return local.NewIndex( + opts.ManifestName, + f.dockerClient, + local.WithIndexMediaTypes(opts.MediaType)) +} From 1530c23c3d77d85b121782ba9b15b9220140561c Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 2 May 2023 03:24:04 +0530 Subject: [PATCH 17/79] create_manifest: use the index factory Signed-off-by: Husni Faiz --- pkg/client/create_manifest.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 0005c15112..118119a173 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -4,8 +4,6 @@ import ( "context" "github.com/buildpacks/imgutil" - "github.com/buildpacks/imgutil/remote" - "github.com/google/go-containerregistry/pkg/authn" ) type CreateManifestOptions struct { @@ -31,19 +29,12 @@ type CreateManifestOptions struct { func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) error { - mediaType := imgutil.DockerTypes - - idx, err := remote.NewIndex( - opts.ManifestName, - authn.DefaultKeychain, - remote.WithIndexMediaTypes(mediaType)) // This will return an empty index + indexCreator := c.indexFactory + idx, err := indexCreator.NewIndex(opts) if err != nil { panic(err) } - // When the publish flag is used all the manifests MUST have os/arch defined otherwise an error must be thrown - // The format flag will be ignored if it is not used in conjunction with the publish flag - // Add every manifest to image index for _, j := range opts.Manifests { err := idx.Add(j) From 8460b977be8ae4b49d24a095a38a90cf1daf4fbe Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 2 May 2023 21:51:35 +0530 Subject: [PATCH 18/79] manifest: register subcommands Signed-off-by: Husni Faiz --- internal/commands/manifest.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index c83bf463f9..1ddd7012dd 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -14,6 +14,10 @@ func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command } cmd.AddCommand(ManifestCreate(logger, client)) + cmd.AddCommand(ManifestAnnotate(logger, client)) + cmd.AddCommand(ManifestAdd(logger, client)) + cmd.AddCommand(ManifestPush(logger, client)) + cmd.AddCommand(ManifestRemove(logger, client)) AddHelpFlag(cmd, "manifest") return cmd From 715e18ab074e8a039faa5d4632cea0b156c2fbf1 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 2 May 2023 21:54:38 +0530 Subject: [PATCH 19/79] manifest: add annotate subcommand Signed-off-by: Husni Faiz --- internal/commands/manifest_annotate.go | 7 +++++-- pkg/client/annotate_manifest.go | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 2b7ab90712..c7bc5b78df 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -29,8 +29,11 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { indexName := args[0] manifest := args[1] if err := pack.AnnotateManifest(cmd.Context(), client.AnnotateManifestOptions{ - Index: indexName, - Manifest: manifest, + Index: indexName, + Manifest: manifest, + Architecture: flags.Architecture, + OS: flags.OS, + Variant: flags.Variant, }); err != nil { return err } diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index a5b48f8750..abc0d4ddc3 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -1,6 +1,10 @@ package client -import "context" +import ( + "context" + + "github.com/buildpacks/imgutil/local" +) type AnnotateManifestOptions struct { Index string @@ -11,5 +15,17 @@ type AnnotateManifestOptions struct { } func (c *Client) AnnotateManifest(ctx context.Context, opts AnnotateManifestOptions) error { + err := local.AnnotateManifest( + opts.Index, + opts.Manifest, + local.AnnotateFields{ + Architecture: opts.Architecture, + OS: opts.OS, + Variant: opts.Variant, + }) + if err != nil { + return err + } + return nil } From 4a478b9cfbe9f5ed4ae33062f86817d72852579f Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 2 May 2023 21:55:26 +0530 Subject: [PATCH 20/79] manifest: add 'add' subcommand Signed-off-by: Husni Faiz --- internal/commands/manifest_add.go | 7 +++++++ pkg/client/add_manifest.go | 21 ++++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 34397a25db..4c8d33ed3f 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -30,9 +30,16 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { indexName := args[0] manifest := args[1] + all := false + + if flags.All { + all = flags.All + } + if err := pack.AddManifest(cmd.Context(), client.AddManifestOptions{ Index: indexName, Manifest: manifest, + All: all, }); err != nil { return err } diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 89844aad22..61768aa137 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -1,16 +1,23 @@ package client -import "context" +import ( + "context" + + "github.com/buildpacks/imgutil/local" +) type AddManifestOptions struct { - Index string - Manifest string - Architecture string - OS string - Variant string - All bool + Index string + Manifest string + All bool } func (c *Client) AddManifest(ctx context.Context, opts AddManifestOptions) error { + + err := local.AppendManifest(opts.Index, opts.Manifest) + if err != nil { + return err + } + return nil } From b840b19600790dc86c15d8914901936c669659d1 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 9 May 2023 21:16:03 +0530 Subject: [PATCH 21/79] manifest: use pack home directory to store local manifests Signed-off-by: Husni Faiz --- internal/commands/manifest_add.go | 11 +++++++++++ internal/commands/manifest_annotate.go | 12 ++++++++++++ internal/commands/manifest_create.go | 9 +++++++++ internal/commands/manifest_push.go | 13 ++++++++++++- pkg/client/add_manifest.go | 12 +++++++++++- pkg/client/annotate_manifest.go | 14 ++++++++++++-- pkg/client/client.go | 2 +- pkg/client/create_manifest.go | 9 ++++++--- pkg/client/push_manifest.go | 24 +++++++++++++++++++++++- 9 files changed, 97 insertions(+), 9 deletions(-) diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 4c8d33ed3f..155ab7f60f 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -1,6 +1,9 @@ package commands import ( + "path/filepath" + + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" @@ -36,8 +39,16 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { all = flags.All } + packHome, err := config.PackHome() + if err != nil { + return err + } + + manifestDir := filepath.Join(packHome, "manifests") + if err := pack.AddManifest(cmd.Context(), client.AddManifestOptions{ Index: indexName, + Path: manifestDir, Manifest: manifest, All: all, }); err != nil { diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index c7bc5b78df..fc25ccbace 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -1,6 +1,9 @@ package commands import ( + "path/filepath" + + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" @@ -28,8 +31,17 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { indexName := args[0] manifest := args[1] + + packHome, err := config.PackHome() + if err != nil { + return err + } + + manifestDir := filepath.Join(packHome, "manifests") + if err := pack.AnnotateManifest(cmd.Context(), client.AnnotateManifestOptions{ Index: indexName, + Path: manifestDir, Manifest: manifest, Architecture: flags.Architecture, OS: flags.OS, diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 6e47fee4f5..be010e3e42 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -4,6 +4,7 @@ import ( "path/filepath" "github.com/buildpacks/imgutil" + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" @@ -55,6 +56,13 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { return errors.Wrap(err, "getting absolute layout path") } + packHome, err := config.PackHome() + if err != nil { + return err + } + + manifestDir := filepath.Join(packHome, "manifests") + indexName := args[0] manifests := args[1:] if err := pack.CreateManifest(cmd.Context(), client.CreateManifestOptions{ @@ -64,6 +72,7 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { Publish: flags.Publish, Registry: flags.Registry, LayoutDir: layoutDir, + ManifestDir: manifestDir, }); err != nil { return err } diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index 149f85cd63..efc9523491 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -1,6 +1,9 @@ package commands import ( + "path/filepath" + + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" @@ -23,7 +26,7 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "push [OPTIONS] ", Short: "Push a manifest list to a repository", - Args: cobra.MatchAll(cobra.ExactArgs(2)), + Args: cobra.MatchAll(cobra.ExactArgs(1)), Example: `pack manifest push cnbs/sample-package:hello-multiarch-universe`, Long: "manifest push pushes a manifest list (Image index) to a registry.", RunE: logError(logger, func(cmd *cobra.Command, args []string) error { @@ -32,8 +35,16 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { } indexName := args[0] + packHome, err := config.PackHome() + if err != nil { + return err + } + + manifestDir := filepath.Join(packHome, "manifests") + if err := pack.PushManifest(cmd.Context(), client.PushManifestOptions{ Index: indexName, + Path: manifestDir, }); err != nil { return err } diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 61768aa137..4c10477577 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -8,13 +8,23 @@ import ( type AddManifestOptions struct { Index string + Path string Manifest string All bool } func (c *Client) AddManifest(ctx context.Context, opts AddManifestOptions) error { + indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) + if err != nil { + panic(err) + } + + idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) + if err != nil { + panic(err) + } - err := local.AppendManifest(opts.Index, opts.Manifest) + err = idx.AppendManifest(opts.Manifest) if err != nil { return err } diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index abc0d4ddc3..2ec2e45039 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -8,6 +8,7 @@ import ( type AnnotateManifestOptions struct { Index string + Path string Manifest string Architecture string OS string @@ -15,8 +16,17 @@ type AnnotateManifestOptions struct { } func (c *Client) AnnotateManifest(ctx context.Context, opts AnnotateManifestOptions) error { - err := local.AnnotateManifest( - opts.Index, + indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) + if err != nil { + panic(err) + } + + idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) + if err != nil { + panic(err) + } + + err = idx.AnnotateManifest( opts.Manifest, local.AnnotateFields{ Architecture: opts.Architecture, diff --git a/pkg/client/client.go b/pkg/client/client.go index 6d15d78999..4b7781ad21 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -301,6 +301,6 @@ func (f *indexFactory) NewIndex(opts CreateManifestOptions) (imgutil.ImageIndex, return local.NewIndex( opts.ManifestName, - f.dockerClient, + opts.ManifestDir, local.WithIndexMediaTypes(opts.MediaType)) } diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 118119a173..c982b90909 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -25,6 +25,9 @@ type CreateManifestOptions struct { // Directory to store OCI layout LayoutDir string + + // Directory to store OCI layout + ManifestDir string } func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) error { @@ -32,21 +35,21 @@ func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) indexCreator := c.indexFactory idx, err := indexCreator.NewIndex(opts) if err != nil { - panic(err) + return err } // Add every manifest to image index for _, j := range opts.Manifests { err := idx.Add(j) if err != nil { - panic(err) + return err } } // Store index err = idx.Save() if err != nil { - panic(err) + return err } return nil diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index 5b99c90f7b..bb5d52724b 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -1,11 +1,33 @@ package client -import "context" +import ( + "context" + + "github.com/buildpacks/imgutil/local" + "github.com/buildpacks/imgutil/remote" +) type PushManifestOptions struct { Index string + Path string } func (c *Client) PushManifest(ctx context.Context, opts PushManifestOptions) error { + indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) + if err != nil { + panic(err) + } + + idx, err := remote.NewIndex(opts.Index, c.keychain, remote.WithManifest(indexManifest)) + if err != nil { + panic(err) + } + + // Store index + err = idx.Save() + if err != nil { + panic(err) + } + return nil } From 6dee05dd29c9d55059729f40cd5fb17b33776e94 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Sun, 21 May 2023 23:34:29 +0530 Subject: [PATCH 22/79] manifest: return error instead of calling panic Signed-off-by: Husni Faiz --- pkg/client/add_manifest.go | 4 ++-- pkg/client/annotate_manifest.go | 4 ++-- pkg/client/push_manifest.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 4c10477577..f24a8eb7e8 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -16,12 +16,12 @@ type AddManifestOptions struct { func (c *Client) AddManifest(ctx context.Context, opts AddManifestOptions) error { indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) if err != nil { - panic(err) + return err } idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) if err != nil { - panic(err) + return err } err = idx.AppendManifest(opts.Manifest) diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index 2ec2e45039..1c6f5c4ea9 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -18,12 +18,12 @@ type AnnotateManifestOptions struct { func (c *Client) AnnotateManifest(ctx context.Context, opts AnnotateManifestOptions) error { indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) if err != nil { - panic(err) + return err } idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) if err != nil { - panic(err) + return err } err = idx.AnnotateManifest( diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index bb5d52724b..c8349f5937 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -15,18 +15,18 @@ type PushManifestOptions struct { func (c *Client) PushManifest(ctx context.Context, opts PushManifestOptions) error { indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) if err != nil { - panic(err) + return err } idx, err := remote.NewIndex(opts.Index, c.keychain, remote.WithManifest(indexManifest)) if err != nil { - panic(err) + return err } // Store index err = idx.Save() if err != nil { - panic(err) + return err } return nil From ba95ad8852ccd304ea88348f01e841a31e77a6c7 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Sun, 21 May 2023 23:43:52 +0530 Subject: [PATCH 23/79] manifest add: AppendManifest wrapper function will be removed from imgutil AppendManifest is a wrapper function in imgutil to append a new manifest to a local index and save it in local storage. This function will be removed in the future versions of imgutil. We will use the direct functions to append the manifest and store the index locally. Signed-off-by: Husni Faiz --- pkg/client/add_manifest.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index f24a8eb7e8..8219ac535c 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -24,7 +24,14 @@ func (c *Client) AddManifest(ctx context.Context, opts AddManifestOptions) error return err } - err = idx.AppendManifest(opts.Manifest) + // Append manifest to local index + err = idx.Add(opts.Manifest) + if err != nil { + return err + } + + // Store index in local storage + err = idx.Save() if err != nil { return err } From 0631b34ead46864b4f42465bb2b24747f50e97f3 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 23 May 2023 19:46:06 +0530 Subject: [PATCH 24/79] manfiest: add remove command Signed-off-by: Husni Faiz --- internal/commands/manifest_remove.go | 16 +++++++++++++- pkg/client/remove_manifest.go | 32 ++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index 7cf7e6f132..ef85ba016f 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -1,6 +1,9 @@ package commands import ( + "path/filepath" + + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" @@ -16,8 +19,19 @@ func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { Long: "Delete one or more manifest lists from local storage", RunE: logError(logger, func(cmd *cobra.Command, args []string) error { indexName := args[0] + manifest := args[1] + + packHome, err := config.PackHome() + if err != nil { + return err + } + + manifestDir := filepath.Join(packHome, "manifests") + if err := pack.RemoveManifest(cmd.Context(), client.RemoveManifestOptions{ - Index: indexName, + Index: indexName, + Path: manifestDir, + Manifest: manifest, }); err != nil { return err } diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index ee82eb1d15..6657d0afdc 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -1,11 +1,39 @@ package client -import "context" +import ( + "context" + + "github.com/buildpacks/imgutil/local" +) type RemoveManifestOptions struct { - Index string + Index string + Path string + Manifest string } func (c *Client) RemoveManifest(ctx context.Context, opts RemoveManifestOptions) error { + indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) + if err != nil { + return err + } + + idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) + if err != nil { + return err + } + + // Append manifest to local index + err = idx.Remove(opts.Manifest) + if err != nil { + return err + } + + // Store index in local storage + err = idx.Save() + if err != nil { + return err + } + return nil } From 605a994429e131f4ec1d1f531413bbaf4a0950d9 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 23 May 2023 21:40:02 +0530 Subject: [PATCH 25/79] manifest create: remove layout option Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 21 ++++----------------- pkg/client/create_manifest.go | 3 --- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index be010e3e42..1ff24a685a 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -14,11 +14,10 @@ import ( ) type ManifestCreateFlags struct { - Publish bool - Insecure bool - Registry string - Format string - LayoutDir string + Publish bool + Insecure bool + Registry string + Format string } func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { @@ -46,16 +45,6 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { return errors.Errorf("unsupported media type given for --format") } - layoutDir := "./oci-layout" - if flags.LayoutDir != "" { - layoutDir = flags.LayoutDir - } - - layoutDir, err := filepath.Abs(filepath.Dir(layoutDir)) - if err != nil { - return errors.Wrap(err, "getting absolute layout path") - } - packHome, err := config.PackHome() if err != nil { return err @@ -71,7 +60,6 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { MediaType: mediaType, Publish: flags.Publish, Registry: flags.Registry, - LayoutDir: layoutDir, ManifestDir: manifestDir, }); err != nil { return err @@ -87,7 +75,6 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().BoolVar(&flags.Insecure, "insecure", false, `Allow publishing to insecure registry`) cmd.Flags().StringVarP(&flags.Format, "format", "f", "", `Format to save image index as ("OCI" or "V2S2")`) cmd.Flags().StringVarP(&flags.Registry, "registry", "r", "", `Registry URL to publish the image index`) - cmd.Flags().StringVarP(&flags.LayoutDir, "layout", "o", "", `Relative directory path to save the OCI layout`) AddHelpFlag(cmd, "create") return cmd diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index c982b90909..1910e7e78b 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -23,9 +23,6 @@ type CreateManifestOptions struct { // Defines the registry to publish the manifest list. Registry string - // Directory to store OCI layout - LayoutDir string - // Directory to store OCI layout ManifestDir string } From bb7902e53e2f3c5b6f2e9655ceff79149176aae8 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 23 May 2023 21:43:15 +0530 Subject: [PATCH 26/79] manifest add: remove redundent code Signed-off-by: Husni Faiz --- internal/commands/manifest_add.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 155ab7f60f..7df44c7a93 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -33,11 +33,6 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { indexName := args[0] manifest := args[1] - all := false - - if flags.All { - all = flags.All - } packHome, err := config.PackHome() if err != nil { @@ -50,7 +45,7 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { Index: indexName, Path: manifestDir, Manifest: manifest, - All: all, + All: flags.All, }); err != nil { return err } From 7be918924072e4268abe3eefceaf7ee56c29422d Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Tue, 23 May 2023 21:47:25 +0530 Subject: [PATCH 27/79] manifest annotate: update example to use cnbs/sample-package Signed-off-by: Husni Faiz --- internal/commands/manifest_annotate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index fc25ccbace..4ce00c6e4e 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -22,7 +22,7 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { Use: "annotate [OPTIONS] ", Short: "Annotate a manifest list", Args: cobra.MatchAll(cobra.ExactArgs(2)), - Example: `pack manifest annotate paketobuildpacks/builder:full-1.0.0 \ paketobuildpacks/builder:full-linux-amd64`, + Example: `pack manifest annotate cnbs/sample-package:hello-universe-multiarch \ cnbs/sample-package:hello-universe --arch amd64`, Long: "manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.", RunE: logError(logger, func(cmd *cobra.Command, args []string) error { if err := validateManifestAnnotateFlags(&flags); err != nil { From 12b1c0ffb6dd402c08755576f3bd248dbcf7226e Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Wed, 24 May 2023 00:10:28 +0530 Subject: [PATCH 28/79] manifest create: remove redundant comment Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 1ff24a685a..8add6b06fd 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -64,10 +64,10 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { }); err != nil { return err } + logger.Infof("Successfully created image index %s", style.Symbol(indexName)) - // logging.Tip(logger, "Run %s to use this builder", style.Symbol(fmt.Sprintf("pack build --builder %s", imageName))) - return nil + return nil }), } From 1824233c33f2897d89be8e69fd6b492e4fce62b9 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Wed, 24 May 2023 00:39:45 +0530 Subject: [PATCH 29/79] manifest create: simplify the logic for getting the mediatype Signed-off-by: Husni Faiz --- internal/commands/manifest_create.go | 30 +++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 8add6b06fd..460c814e62 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -35,14 +35,9 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { return err } - mediaType := imgutil.DockerTypes - format := flags.Format - if format == "oci" { - mediaType = imgutil.OCITypes - } else if format == "v2s2" || format == "" { - mediaType = imgutil.DockerTypes - } else { - return errors.Errorf("unsupported media type given for --format") + mediaType, err := validateMediaTypeFlag(flags.Format) + if err != nil { + return err } packHome, err := config.PackHome() @@ -73,7 +68,7 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().BoolVar(&flags.Publish, "publish", false, `Publish to registry`) cmd.Flags().BoolVar(&flags.Insecure, "insecure", false, `Allow publishing to insecure registry`) - cmd.Flags().StringVarP(&flags.Format, "format", "f", "", `Format to save image index as ("OCI" or "V2S2")`) + cmd.Flags().StringVarP(&flags.Format, "format", "f", "v2s2", `Format to save image index as ("OCI" or "V2S2")`) cmd.Flags().StringVarP(&flags.Registry, "registry", "r", "", `Registry URL to publish the image index`) AddHelpFlag(cmd, "create") @@ -81,5 +76,22 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { } func validateManifestCreateFlags(p *ManifestCreateFlags) error { + if p.Format == "" { + return errors.Errorf("--format flag received an empty value") + } return nil } + +func validateMediaTypeFlag(format string) (imgutil.MediaTypes, error) { + var mediaType imgutil.MediaTypes + + if format == "oci" { + mediaType = imgutil.OCITypes + } else if format == "v2s2" { + mediaType = imgutil.DockerTypes + } else { + return imgutil.MissingTypes, errors.Errorf("unsupported media type given for --format") + } + + return mediaType, nil +} From 0b99d2b20509c241eb9c646c7914518ace15eeff Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Sat, 27 May 2023 02:08:25 +0530 Subject: [PATCH 30/79] manifest command: wrap erros with useful information Signed-off-by: Husni Faiz --- pkg/client/add_manifest.go | 9 +++++---- pkg/client/annotate_manifest.go | 7 ++++--- pkg/client/create_manifest.go | 15 ++++++++++++--- pkg/client/push_manifest.go | 7 ++++--- pkg/client/remove_manifest.go | 9 +++++---- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 8219ac535c..6b3bb2b0d4 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -4,6 +4,7 @@ import ( "context" "github.com/buildpacks/imgutil/local" + "github.com/pkg/errors" ) type AddManifestOptions struct { @@ -16,24 +17,24 @@ type AddManifestOptions struct { func (c *Client) AddManifest(ctx context.Context, opts AddManifestOptions) error { indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) if err != nil { - return err + return errors.Wrapf(err, "Get local index manifest '%s' from path '%s'", opts.Index, opts.Path) } idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) if err != nil { - return err + return errors.Wrapf(err, "Create local index from '%s' local index manifest", opts.Index) } // Append manifest to local index err = idx.Add(opts.Manifest) if err != nil { - return err + return errors.Wrapf(err, "Appending '%s' manifest to index '%s'", opts.Manifest, opts.Index) } // Store index in local storage err = idx.Save() if err != nil { - return err + return errors.Wrapf(err, "Save local index '%s' at '%s' path", opts.Index, opts.Path) } return nil diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index 1c6f5c4ea9..79a5ef7ec9 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -4,6 +4,7 @@ import ( "context" "github.com/buildpacks/imgutil/local" + "github.com/pkg/errors" ) type AnnotateManifestOptions struct { @@ -18,12 +19,12 @@ type AnnotateManifestOptions struct { func (c *Client) AnnotateManifest(ctx context.Context, opts AnnotateManifestOptions) error { indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) if err != nil { - return err + return errors.Wrapf(err, "Get local index manifest '%s' from path '%s'", opts.Index, opts.Path) } idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) if err != nil { - return err + return errors.Wrapf(err, "Create local index from '%s' local index manifest", opts.Index) } err = idx.AnnotateManifest( @@ -34,7 +35,7 @@ func (c *Client) AnnotateManifest(ctx context.Context, opts AnnotateManifestOpti Variant: opts.Variant, }) if err != nil { - return err + return errors.Wrapf(err, "Annotate manifet '%s' of index '%s", opts.Manifest, opts.Index) } return nil diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 1910e7e78b..41080eb669 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -4,6 +4,7 @@ import ( "context" "github.com/buildpacks/imgutil" + "github.com/pkg/errors" ) type CreateManifestOptions struct { @@ -32,21 +33,29 @@ func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) indexCreator := c.indexFactory idx, err := indexCreator.NewIndex(opts) if err != nil { - return err + if opts.Publish { + return errors.Wrapf(err, "Failed to create remote index '%s'", opts.ManifestName) + } else { + return errors.Wrapf(err, "Failed to create local index '%s'", opts.ManifestName) + } } // Add every manifest to image index for _, j := range opts.Manifests { err := idx.Add(j) if err != nil { - return err + return errors.Wrapf(err, "Appending manifest to index '%s'", opts.ManifestName) } } // Store index err = idx.Save() if err != nil { - return err + if opts.Publish { + return errors.Wrapf(err, "Storing index '%s' in registry.", opts.ManifestName) + } else { + return errors.Wrapf(err, "Save local index '%s' at '%s' path", opts.ManifestName, opts.ManifestDir) + } } return nil diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index c8349f5937..ef2cff541e 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -5,6 +5,7 @@ import ( "github.com/buildpacks/imgutil/local" "github.com/buildpacks/imgutil/remote" + "github.com/pkg/errors" ) type PushManifestOptions struct { @@ -15,18 +16,18 @@ type PushManifestOptions struct { func (c *Client) PushManifest(ctx context.Context, opts PushManifestOptions) error { indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) if err != nil { - return err + return errors.Wrapf(err, "Get local index manifest '%s' from path '%s'", opts.Index, opts.Path) } idx, err := remote.NewIndex(opts.Index, c.keychain, remote.WithManifest(indexManifest)) if err != nil { - return err + return errors.Wrapf(err, "Create remote index from '%s' local index manifest", opts.Index) } // Store index err = idx.Save() if err != nil { - return err + return errors.Wrapf(err, "Storing index '%s' in registry. Check if all the referenced manifests are in the same repository in registry", opts.Index) } return nil diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index 6657d0afdc..03e2321351 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -4,6 +4,7 @@ import ( "context" "github.com/buildpacks/imgutil/local" + "github.com/pkg/errors" ) type RemoveManifestOptions struct { @@ -15,24 +16,24 @@ type RemoveManifestOptions struct { func (c *Client) RemoveManifest(ctx context.Context, opts RemoveManifestOptions) error { indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) if err != nil { - return err + return errors.Wrapf(err, "Get local index manifest '%s' from path '%s'", opts.Index, opts.Path) } idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) if err != nil { - return err + return errors.Wrapf(err, "Create local index from '%s' local index manifest", opts.Index) } // Append manifest to local index err = idx.Remove(opts.Manifest) if err != nil { - return err + return errors.Wrapf(err, "Removing '%s' manifest from index '%s'", opts.Manifest, opts.Index) } // Store index in local storage err = idx.Save() if err != nil { - return err + return errors.Wrapf(err, "Save local index '%s' at '%s' path", opts.Index, opts.Path) } return nil From 16b34961e75b60081c43077056d49205d27a9b09 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Sat, 27 May 2023 02:23:07 +0530 Subject: [PATCH 31/79] manifest rm: rename file Signed-off-by: Husni Faiz --- internal/commands/{manifest_remove.go => manifest_rm.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename internal/commands/{manifest_remove.go => manifest_rm.go} (100%) diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_rm.go similarity index 100% rename from internal/commands/manifest_remove.go rename to internal/commands/manifest_rm.go From 090a0efba295b4b3fe5ce891f19d47eb8e8e1f78 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Sat, 27 May 2023 02:24:51 +0530 Subject: [PATCH 32/79] manifest rm: change command name remove to rm A new "remove" command will be added to remove an index from local storage and "rm" command will do the function of removing a manifest from an index. Signed-off-by: Husni Faiz --- internal/commands/manifest_rm.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/commands/manifest_rm.go b/internal/commands/manifest_rm.go index ef85ba016f..3be8035a88 100644 --- a/internal/commands/manifest_rm.go +++ b/internal/commands/manifest_rm.go @@ -12,11 +12,12 @@ import ( func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ - Use: "remove [manifest-list] [manifest-list...]", - Short: "Delete one or more manifest lists from local storage", - Args: cobra.MatchAll(cobra.ExactArgs(2)), - Example: `pack manifest delete cnbs/sample-package:hello-multiarch-universe`, - Long: "Delete one or more manifest lists from local storage", + Use: "rm [manifest-list] [manifest]", + Short: "Remove an image manifest from index", + Args: cobra.MatchAll(cobra.ExactArgs(2)), + Example: `pack manifest rm cnbs/sample-package:hello-multiarch-universe \ + cnbs/sample-package:hello-universe-windows`, + Long: "manifest remove will remove the specified image manifest if it is already referenced in the index", RunE: logError(logger, func(cmd *cobra.Command, args []string) error { indexName := args[0] manifest := args[1] @@ -35,13 +36,13 @@ func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { }); err != nil { return err } - logger.Infof("Successfully removed the image index %s", style.Symbol(indexName)) + logger.Infof("Successfully removed the manifest '%s' from image index %s", style.Symbol(manifest), style.Symbol(indexName)) return nil }), } - AddHelpFlag(cmd, "remove") + AddHelpFlag(cmd, "rm") return cmd } From 802b7948b426d980b35175830c2018ae908e7bab Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Thu, 1 Jun 2023 10:08:14 +0530 Subject: [PATCH 33/79] manifest rm: rename file Signed-off-by: Husni Faiz --- pkg/client/{remove_manifest.go => rm_manifest.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/client/{remove_manifest.go => rm_manifest.go} (100%) diff --git a/pkg/client/remove_manifest.go b/pkg/client/rm_manifest.go similarity index 100% rename from pkg/client/remove_manifest.go rename to pkg/client/rm_manifest.go From 6ca7fcc8ae318f3c91c47a05ce9d8487555fcd1c Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Thu, 1 Jun 2023 10:09:30 +0530 Subject: [PATCH 34/79] manifest remove: new command to delete a local index Signed-off-by: Husni Faiz --- internal/commands/commands.go | 1 + internal/commands/manifest.go | 1 + internal/commands/manifest_remove.go | 48 ++++++++++++++++++++++++++++ pkg/client/remove_manifest.go | 33 +++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 internal/commands/manifest_remove.go create mode 100644 pkg/client/remove_manifest.go diff --git a/internal/commands/commands.go b/internal/commands/commands.go index b912c3f92a..8ef863bbf3 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -37,6 +37,7 @@ type PackClient interface { AddManifest(context.Context, client.AddManifestOptions) error PushManifest(context.Context, client.PushManifestOptions) error RemoveManifest(context.Context, client.RemoveManifestOptions) error + DeleteManifest(context.Context, client.DeleteManifestOptions) error } func AddHelpFlag(cmd *cobra.Command, commandName string) { diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index 1ddd7012dd..164e96af6e 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -18,6 +18,7 @@ func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command cmd.AddCommand(ManifestAdd(logger, client)) cmd.AddCommand(ManifestPush(logger, client)) cmd.AddCommand(ManifestRemove(logger, client)) + cmd.AddCommand(ManifestDelete(logger, client)) AddHelpFlag(cmd, "manifest") return cmd diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go new file mode 100644 index 0000000000..f99d0a67d8 --- /dev/null +++ b/internal/commands/manifest_remove.go @@ -0,0 +1,48 @@ +package commands + +import ( + "path/filepath" + + "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/pkg/client" + "github.com/buildpacks/pack/pkg/logging" + "github.com/spf13/cobra" +) + +func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { + cmd := &cobra.Command{ + Use: "remove [manifest-list] [manifest-list...]", + Short: "Delete one or more manifest lists from local storage", + Args: cobra.MatchAll(cobra.MinimumNArgs(1)), + Example: `pack manifest delete cnbs/sample-package:hello-multiarch-universe`, + Long: "Delete one or more manifest lists from local storage", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + indexNames := args + + packHome, err := config.PackHome() + if err != nil { + return err + } + + manifestDir := filepath.Join(packHome, "manifests") + + for _, repoName := range indexNames { + err = pack.DeleteManifest(cmd.Context(), client.DeleteManifestOptions{ + Index: repoName, + Path: manifestDir, + }) + if err != nil { + logger.Infof("Failed to remove index '%s' from local storage\n", style.Symbol(repoName)) + } else { + logger.Infof("Successfully removed index '%s' from local storage\n", style.Symbol(repoName)) + } + } + return nil + + }), + } + + AddHelpFlag(cmd, "remove") + return cmd +} diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go new file mode 100644 index 0000000000..988512bf40 --- /dev/null +++ b/pkg/client/remove_manifest.go @@ -0,0 +1,33 @@ +package client + +import ( + "context" + + "github.com/buildpacks/imgutil/local" + "github.com/buildpacks/pack/internal/style" + "github.com/pkg/errors" +) + +type DeleteManifestOptions struct { + Index string + Path string +} + +func (c *Client) DeleteManifest(ctx context.Context, opts DeleteManifestOptions) error { + indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) + if err != nil { + return errors.Wrapf(err, "Get local index manifest '%s' from path '%s'", opts.Index, opts.Path) + } + + idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) + if err != nil { + return errors.Wrapf(err, "Create local index from '%s' local index manifest", opts.Index) + } + + err = idx.Delete() + if err != nil { + return errors.Wrapf(err, "Failed to remove index '%s' from local storage\n", style.Symbol(opts.Index)) + } + + return nil +} From bf1503e9f440a22bfb0f15adea9e055852acaf09 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Thu, 1 Jun 2023 10:10:38 +0530 Subject: [PATCH 35/79] manifest inspect: new command to inspect local index Signed-off-by: Husni Faiz --- internal/commands/commands.go | 1 + internal/commands/manifest.go | 1 + internal/commands/manifest_inspect.go | 42 +++++++++++++++++++++++++++ pkg/client/inspect_manifest.go | 30 +++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 internal/commands/manifest_inspect.go create mode 100644 pkg/client/inspect_manifest.go diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 8ef863bbf3..0cb008e225 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -38,6 +38,7 @@ type PackClient interface { PushManifest(context.Context, client.PushManifestOptions) error RemoveManifest(context.Context, client.RemoveManifestOptions) error DeleteManifest(context.Context, client.DeleteManifestOptions) error + InspectManifest(context.Context, client.InspectManifestOptions) error } func AddHelpFlag(cmd *cobra.Command, commandName string) { diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index 164e96af6e..1f1da30941 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -19,6 +19,7 @@ func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command cmd.AddCommand(ManifestPush(logger, client)) cmd.AddCommand(ManifestRemove(logger, client)) cmd.AddCommand(ManifestDelete(logger, client)) + cmd.AddCommand(ManifestInspect(logger, client)) AddHelpFlag(cmd, "manifest") return cmd diff --git a/internal/commands/manifest_inspect.go b/internal/commands/manifest_inspect.go new file mode 100644 index 0000000000..1b0c9f9dea --- /dev/null +++ b/internal/commands/manifest_inspect.go @@ -0,0 +1,42 @@ +package commands + +import ( + "path/filepath" + + "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/pkg/client" + "github.com/buildpacks/pack/pkg/logging" + "github.com/spf13/cobra" +) + +func ManifestInspect(logger logging.Logger, pack PackClient) *cobra.Command { + cmd := &cobra.Command{ + Use: "inspect ", + Short: "Inspect a local manifest list", + Args: cobra.MatchAll(cobra.ExactArgs(1)), + Example: `pack manifest inspect cnbs/sample-builder:multiarch`, + Long: "manifest inspect shows the manifest information stored in local storage", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + indexName := args[0] + + packHome, err := config.PackHome() + if err != nil { + return err + } + + manifestDir := filepath.Join(packHome, "manifests") + + if err := pack.InspectManifest(cmd.Context(), client.InspectManifestOptions{ + Index: indexName, + Path: manifestDir, + }); err != nil { + return err + } + + return nil + }), + } + + AddHelpFlag(cmd, "inspect") + return cmd +} diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go new file mode 100644 index 0000000000..1abd5496e8 --- /dev/null +++ b/pkg/client/inspect_manifest.go @@ -0,0 +1,30 @@ +package client + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/buildpacks/imgutil/local" + "github.com/pkg/errors" +) + +type InspectManifestOptions struct { + Index string + Path string +} + +func (c *Client) InspectManifest(ctx context.Context, opts InspectManifestOptions) error { + indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) + if err == nil { + data, err := json.MarshalIndent(indexManifest, "", " ") + if err != nil { + return errors.Wrapf(err, "Marshal the '%s' manifest information", opts.Index) + } + + c.logger.Infof("%s\n", string(data)) + return nil + } + + return fmt.Errorf("Index %s not found in local storage", opts.Index) +} From 2b8fdf184a61a53271e055e9096f9f21ae922832 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Mon, 5 Jun 2023 20:10:58 +0530 Subject: [PATCH 36/79] manifest: code formatting Signed-off-by: Husni Faiz --- internal/commands/manifest.go | 3 ++- internal/commands/manifest_add.go | 3 ++- internal/commands/manifest_annotate.go | 3 ++- internal/commands/manifest_create.go | 3 ++- internal/commands/manifest_inspect.go | 3 ++- internal/commands/manifest_push.go | 3 ++- internal/commands/manifest_remove.go | 3 ++- internal/commands/manifest_rm.go | 3 ++- pkg/client/remove_manifest.go | 3 ++- 9 files changed, 18 insertions(+), 9 deletions(-) diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index 1f1da30941..8f000bf881 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -1,8 +1,9 @@ package commands import ( - "github.com/buildpacks/pack/pkg/logging" "github.com/spf13/cobra" + + "github.com/buildpacks/pack/pkg/logging" ) func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command { diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 7df44c7a93..3061a632c6 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -3,11 +3,12 @@ package commands import ( "path/filepath" + "github.com/spf13/cobra" + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" - "github.com/spf13/cobra" ) type ManifestAddFlags struct { diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 4ce00c6e4e..09c8181f76 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -3,11 +3,12 @@ package commands import ( "path/filepath" + "github.com/spf13/cobra" + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" - "github.com/spf13/cobra" ) type ManifestAnnotateFlags struct { diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 460c814e62..e9f8c468b0 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -4,11 +4,12 @@ import ( "path/filepath" "github.com/buildpacks/imgutil" + "github.com/pkg/errors" + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" - "github.com/pkg/errors" "github.com/spf13/cobra" ) diff --git a/internal/commands/manifest_inspect.go b/internal/commands/manifest_inspect.go index 1b0c9f9dea..0bb09594ba 100644 --- a/internal/commands/manifest_inspect.go +++ b/internal/commands/manifest_inspect.go @@ -3,10 +3,11 @@ package commands import ( "path/filepath" + "github.com/spf13/cobra" + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" - "github.com/spf13/cobra" ) func ManifestInspect(logger logging.Logger, pack PackClient) *cobra.Command { diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index efc9523491..53149b6ccb 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -3,11 +3,12 @@ package commands import ( "path/filepath" + "github.com/spf13/cobra" + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" - "github.com/spf13/cobra" ) type ManifestPushFlags struct { diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index f99d0a67d8..cbc45e70c7 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -3,11 +3,12 @@ package commands import ( "path/filepath" + "github.com/spf13/cobra" + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" - "github.com/spf13/cobra" ) func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { diff --git a/internal/commands/manifest_rm.go b/internal/commands/manifest_rm.go index 3be8035a88..b8a2b60666 100644 --- a/internal/commands/manifest_rm.go +++ b/internal/commands/manifest_rm.go @@ -3,11 +3,12 @@ package commands import ( "path/filepath" + "github.com/spf13/cobra" + "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" - "github.com/spf13/cobra" ) func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index 988512bf40..6333ab1e79 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -4,8 +4,9 @@ import ( "context" "github.com/buildpacks/imgutil/local" - "github.com/buildpacks/pack/internal/style" "github.com/pkg/errors" + + "github.com/buildpacks/pack/internal/style" ) type DeleteManifestOptions struct { From 729c39073aa4863f54fea76e674bf20ca47cea02 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Mon, 5 Jun 2023 22:14:01 +0530 Subject: [PATCH 37/79] go mod tidy: imgutil v1.3.0 Signed-off-by: Husni Faiz --- go.mod | 12 +++++++----- go.sum | 23 ++++++++++++----------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 81236bfeaf..4c9d0bc8d6 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/apex/log v1.9.0 github.com/buildpacks/imgutil v0.0.0-20230428141433-24db5a78c900 github.com/buildpacks/lifecycle v0.17.0-pre.2 - github.com/docker/cli v24.0.0+incompatible - github.com/docker/docker v24.0.0+incompatible + github.com/docker/cli v24.0.2+incompatible + github.com/docker/docker v24.0.2+incompatible github.com/docker/go-connections v0.4.0 github.com/dustin/go-humanize v1.0.1 github.com/gdamore/tcell/v2 v2.6.0 @@ -40,7 +40,7 @@ require ( require ( github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.28 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect @@ -100,7 +100,7 @@ require ( github.com/moby/buildkit v0.11.4 // indirect github.com/moby/patternmatcher v0.5.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect - github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect + github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/runc v1.1.5 // indirect @@ -108,7 +108,7 @@ require ( github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/sergi/go-diff v1.2.0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect + github.com/sirupsen/logrus v1.9.2 // indirect github.com/skeema/knownhosts v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/vbatts/tar-split v0.11.3 // indirect @@ -124,3 +124,5 @@ require ( ) go 1.20 + +replace github.com/buildpacks/imgutil => github.com/drac98/imgutil v1.3.0 diff --git a/go.sum b/go.sum index 717f444b36..26e96c2a0b 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= @@ -85,8 +85,6 @@ github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230110223219-40efa3093a22 h1:Sq2n1xnF4uuEGNUVOLKHZXoL0CVdxIQOQTEZ7EW762Q= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230110223219-40efa3093a22/go.mod h1:G93AFVEAkW0+tabIqmQCLN/r6sZgP4pFxfoiEzDeQNM= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= -github.com/buildpacks/imgutil v0.0.0-20230428141433-24db5a78c900 h1:f6SrGzyotuJxn+BuIQC3ZBXQiNKgeXhWZLnAJEavaxI= -github.com/buildpacks/imgutil v0.0.0-20230428141433-24db5a78c900/go.mod h1:/xuDxsWO9JE/s95g+OfXB8C+G5TeHznq7vURY2s1yPM= github.com/buildpacks/lifecycle v0.17.0-pre.2 h1:y6QUW2DWw6lgcaskFBA8ABt8xG+cgJfr0v20pr2RjjU= github.com/buildpacks/lifecycle v0.17.0-pre.2/go.mod h1:YZMUvNOkwv7AsARB81GqcHANM4pLu4wP/qe5N4Kzmxs= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= @@ -117,12 +115,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= -github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM= -github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v24.0.2+incompatible h1:QdqR7znue1mtkXIJ+ruQMGQhpw2JzMJLRXp6zpzF6tM= +github.com/docker/cli v24.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.0+incompatible h1:z4bf8HvONXX9Tde5lGBMQ7yCJgNahmJumdrStZAbeY4= -github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.2+incompatible h1:eATx+oLz9WdNVkQrr0qjQ8HvRJ4bOOxfzEo8R+dA3cg= +github.com/docker/docker v24.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -130,6 +128,8 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/drac98/imgutil v1.3.0 h1:jmnN5b/8mZYmFtruhH71agLgo1q6Qh8CdKnkxxO8tH8= +github.com/drac98/imgutil v1.3.0/go.mod h1:M6J5S49+IPQ0i5Yr/Z2z+ZX245FaIkdaXu+FSJo1VDY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -254,8 +254,8 @@ github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YO github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= @@ -308,8 +308,9 @@ github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= From d4f809da2a4dd38d71b33a7f466cf59eb56f6750 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Wed, 12 Jul 2023 08:15:25 +0530 Subject: [PATCH 38/79] Use imgutil v1.4.0 Signed-off-by: Husni Faiz --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index c6b6d798c3..e0ece10857 100644 --- a/go.mod +++ b/go.mod @@ -126,4 +126,4 @@ require ( go 1.20 -replace github.com/buildpacks/imgutil => github.com/drac98/imgutil v1.3.0 +replace github.com/buildpacks/imgutil => github.com/drac98/imgutil v1.4.0 diff --git a/go.sum b/go.sum index 20fd554615..94fa9944df 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,6 @@ github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230522190001-adf1bafd791a h1:rW+dV12c0WD3+O4Zs8Qt4+oqnr8ecXeyg8g3yB73ZKA= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230522190001-adf1bafd791a/go.mod h1:1mvdZLjy932pV2fhj1jjwUSHaF5Ogq2gk5bvi/6ngEU= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= -github.com/buildpacks/imgutil v0.0.0-20230626185301-726f02e4225c h1:HlRuSz+JGAzudNtNCfHIzXe0AEuHX6Vx8uZgmjvX02o= -github.com/buildpacks/imgutil v0.0.0-20230626185301-726f02e4225c/go.mod h1:mBG5M3GJW5nknCEOOqtmMHyPYnSpw/5GEiciuYU/COw= github.com/buildpacks/lifecycle v0.17.0-rc.3 h1:GAo6Gv2hFbhjf0JX7M2khS8jd6vU8I9oCkheIhHQwxg= github.com/buildpacks/lifecycle v0.17.0-rc.3/go.mod h1:WFzcNp1WG4bwgHuXtKxMg4tdU3AguL44ZlP3knANeVs= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= @@ -131,8 +129,8 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/drac98/imgutil v1.3.0 h1:jmnN5b/8mZYmFtruhH71agLgo1q6Qh8CdKnkxxO8tH8= -github.com/drac98/imgutil v1.3.0/go.mod h1:M6J5S49+IPQ0i5Yr/Z2z+ZX245FaIkdaXu+FSJo1VDY= +github.com/drac98/imgutil v1.4.0 h1:b8dtuhmfyYrWfjxRyg92MofmVt/bKgq3izY7Sc7NsMY= +github.com/drac98/imgutil v1.4.0/go.mod h1:mBG5M3GJW5nknCEOOqtmMHyPYnSpw/5GEiciuYU/COw= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= From ac9855fba1d71693cdcc87f139871be573e7559c Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Wed, 12 Jul 2023 08:27:19 +0530 Subject: [PATCH 39/79] fix lint Signed-off-by: Husni Faiz --- internal/commands/manifest_add.go | 5 ++--- internal/commands/manifest_annotate.go | 5 ++--- internal/commands/manifest_create.go | 7 ++++--- internal/commands/manifest_push.go | 1 - internal/commands/manifest_remove.go | 1 - internal/commands/manifest_rm.go | 1 - pkg/client/create_manifest.go | 2 -- pkg/client/inspect_manifest.go | 2 +- 8 files changed, 9 insertions(+), 15 deletions(-) diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 3061a632c6..18b59de435 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -53,14 +53,13 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { logger.Infof("Successfully added the image %s to the image index %s", style.Symbol(manifest), style.Symbol(indexName)) return nil - }), } cmd.Flags().BoolVar(&flags.All, "all", false, `add all of the contents to the local list (applies only if is an index)`) - cmd.Flags().StringVar(&flags.Architecture, "arch", "", "Set the architecutre") + cmd.Flags().StringVar(&flags.Architecture, "arch", "", "Set the architecture") cmd.Flags().StringVar(&flags.OS, "os", "", "Set the operating system") - cmd.Flags().StringVar(&flags.Variant, "variant", "", "Set the architecutre variant") + cmd.Flags().StringVar(&flags.Variant, "variant", "", "Set the architecture variant") AddHelpFlag(cmd, "add") return cmd diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 09c8181f76..10590922ba 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -53,13 +53,12 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { logger.Infof("Successfully annotated image index %s", style.Symbol(indexName)) return nil - }), } - cmd.Flags().StringVar(&flags.Architecture, "arch", "", "Set the architecutre") + cmd.Flags().StringVar(&flags.Architecture, "arch", "", "Set the architecture") cmd.Flags().StringVar(&flags.OS, "os", "", "Set the operating system") - cmd.Flags().StringVar(&flags.Variant, "variant", "", "Set the architecutre variant") + cmd.Flags().StringVar(&flags.Variant, "variant", "", "Set the architecture variant") AddHelpFlag(cmd, "annotate") return cmd diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index e9f8c468b0..86c184babc 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -86,11 +86,12 @@ func validateManifestCreateFlags(p *ManifestCreateFlags) error { func validateMediaTypeFlag(format string) (imgutil.MediaTypes, error) { var mediaType imgutil.MediaTypes - if format == "oci" { + switch format { + case "oci": mediaType = imgutil.OCITypes - } else if format == "v2s2" { + case "v2s2": mediaType = imgutil.DockerTypes - } else { + default: return imgutil.MissingTypes, errors.Errorf("unsupported media type given for --format") } diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index 53149b6ccb..7bd3030b2d 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -52,7 +52,6 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { logger.Infof("Successfully pushed the %s image index to the repository.", style.Symbol(indexName)) return nil - }), } diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index cbc45e70c7..41ee172eb0 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -40,7 +40,6 @@ func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { } } return nil - }), } diff --git a/internal/commands/manifest_rm.go b/internal/commands/manifest_rm.go index b8a2b60666..938eafecf2 100644 --- a/internal/commands/manifest_rm.go +++ b/internal/commands/manifest_rm.go @@ -40,7 +40,6 @@ func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { logger.Infof("Successfully removed the manifest '%s' from image index %s", style.Symbol(manifest), style.Symbol(indexName)) return nil - }), } diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 41080eb669..f2064ae5ad 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -29,7 +29,6 @@ type CreateManifestOptions struct { } func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) error { - indexCreator := c.indexFactory idx, err := indexCreator.NewIndex(opts) if err != nil { @@ -59,5 +58,4 @@ func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) } return nil - } diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index 1abd5496e8..3df389f2ae 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -26,5 +26,5 @@ func (c *Client) InspectManifest(ctx context.Context, opts InspectManifestOption return nil } - return fmt.Errorf("Index %s not found in local storage", opts.Index) + return fmt.Errorf("index %s not found in local storage", opts.Index) } From 2cae96540ee6cee968b287124e301705d5562a08 Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Wed, 12 Jul 2023 08:33:26 +0530 Subject: [PATCH 40/79] manifest: make generate Signed-off-by: Husni Faiz --- internal/builder/testmocks/mock_lifecycle.go | 3 +- .../mock_inspect_image_writer_factory.go | 3 +- .../commands/testmocks/mock_pack_client.go | 101 +++++++++++++++++- pkg/testmocks/mock_blob_downloader.go | 3 +- pkg/testmocks/mock_build_module.go | 3 +- pkg/testmocks/mock_buildpack_downloader.go | 3 +- pkg/testmocks/mock_docker_client.go | 8 +- pkg/testmocks/mock_image_fetcher.go | 3 +- 8 files changed, 109 insertions(+), 18 deletions(-) diff --git a/internal/builder/testmocks/mock_lifecycle.go b/internal/builder/testmocks/mock_lifecycle.go index 88aacd786f..fabeab8401 100644 --- a/internal/builder/testmocks/mock_lifecycle.go +++ b/internal/builder/testmocks/mock_lifecycle.go @@ -8,9 +8,8 @@ import ( io "io" reflect "reflect" - gomock "github.com/golang/mock/gomock" - builder "github.com/buildpacks/pack/internal/builder" + gomock "github.com/golang/mock/gomock" ) // MockLifecycle is a mock of Lifecycle interface. diff --git a/internal/commands/testmocks/mock_inspect_image_writer_factory.go b/internal/commands/testmocks/mock_inspect_image_writer_factory.go index 9f5a65c546..afeaa5c20f 100644 --- a/internal/commands/testmocks/mock_inspect_image_writer_factory.go +++ b/internal/commands/testmocks/mock_inspect_image_writer_factory.go @@ -7,9 +7,8 @@ package testmocks import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" - writer "github.com/buildpacks/pack/internal/inspectimage/writer" + gomock "github.com/golang/mock/gomock" ) // MockInspectImageWriterFactory is a mock of InspectImageWriterFactory interface. diff --git a/internal/commands/testmocks/mock_pack_client.go b/internal/commands/testmocks/mock_pack_client.go index f49b92def9..c066c6856d 100644 --- a/internal/commands/testmocks/mock_pack_client.go +++ b/internal/commands/testmocks/mock_pack_client.go @@ -8,9 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - client "github.com/buildpacks/pack/pkg/client" + gomock "github.com/golang/mock/gomock" ) // MockPackClient is a mock of PackClient interface. @@ -36,6 +35,34 @@ func (m *MockPackClient) EXPECT() *MockPackClientMockRecorder { return m.recorder } +// AddManifest mocks base method. +func (m *MockPackClient) AddManifest(arg0 context.Context, arg1 client.AddManifestOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddManifest", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddManifest indicates an expected call of AddManifest. +func (mr *MockPackClientMockRecorder) AddManifest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddManifest", reflect.TypeOf((*MockPackClient)(nil).AddManifest), arg0, arg1) +} + +// AnnotateManifest mocks base method. +func (m *MockPackClient) AnnotateManifest(arg0 context.Context, arg1 client.AnnotateManifestOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AnnotateManifest", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AnnotateManifest indicates an expected call of AnnotateManifest. +func (mr *MockPackClientMockRecorder) AnnotateManifest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnnotateManifest", reflect.TypeOf((*MockPackClient)(nil).AnnotateManifest), arg0, arg1) +} + // Build mocks base method. func (m *MockPackClient) Build(arg0 context.Context, arg1 client.BuildOptions) error { m.ctrl.T.Helper() @@ -64,6 +91,34 @@ func (mr *MockPackClientMockRecorder) CreateBuilder(arg0, arg1 interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBuilder", reflect.TypeOf((*MockPackClient)(nil).CreateBuilder), arg0, arg1) } +// CreateManifest mocks base method. +func (m *MockPackClient) CreateManifest(arg0 context.Context, arg1 client.CreateManifestOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateManifest", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateManifest indicates an expected call of CreateManifest. +func (mr *MockPackClientMockRecorder) CreateManifest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateManifest", reflect.TypeOf((*MockPackClient)(nil).CreateManifest), arg0, arg1) +} + +// DeleteManifest mocks base method. +func (m *MockPackClient) DeleteManifest(arg0 context.Context, arg1 client.DeleteManifestOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteManifest", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteManifest indicates an expected call of DeleteManifest. +func (mr *MockPackClientMockRecorder) DeleteManifest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteManifest", reflect.TypeOf((*MockPackClient)(nil).DeleteManifest), arg0, arg1) +} + // DownloadSBOM mocks base method. func (m *MockPackClient) DownloadSBOM(arg0 string, arg1 client.DownloadSBOMOptions) error { m.ctrl.T.Helper() @@ -143,6 +198,20 @@ func (mr *MockPackClientMockRecorder) InspectImage(arg0, arg1 interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectImage", reflect.TypeOf((*MockPackClient)(nil).InspectImage), arg0, arg1) } +// InspectManifest mocks base method. +func (m *MockPackClient) InspectManifest(arg0 context.Context, arg1 client.InspectManifestOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InspectManifest", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// InspectManifest indicates an expected call of InspectManifest. +func (mr *MockPackClientMockRecorder) InspectManifest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectManifest", reflect.TypeOf((*MockPackClient)(nil).InspectManifest), arg0, arg1) +} + // NewBuildpack mocks base method. func (m *MockPackClient) NewBuildpack(arg0 context.Context, arg1 client.NewBuildpackOptions) error { m.ctrl.T.Helper() @@ -199,6 +268,20 @@ func (mr *MockPackClientMockRecorder) PullBuildpack(arg0, arg1 interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PullBuildpack", reflect.TypeOf((*MockPackClient)(nil).PullBuildpack), arg0, arg1) } +// PushManifest mocks base method. +func (m *MockPackClient) PushManifest(arg0 context.Context, arg1 client.PushManifestOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PushManifest", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// PushManifest indicates an expected call of PushManifest. +func (mr *MockPackClientMockRecorder) PushManifest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushManifest", reflect.TypeOf((*MockPackClient)(nil).PushManifest), arg0, arg1) +} + // Rebase mocks base method. func (m *MockPackClient) Rebase(arg0 context.Context, arg1 client.RebaseOptions) error { m.ctrl.T.Helper() @@ -227,6 +310,20 @@ func (mr *MockPackClientMockRecorder) RegisterBuildpack(arg0, arg1 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterBuildpack", reflect.TypeOf((*MockPackClient)(nil).RegisterBuildpack), arg0, arg1) } +// RemoveManifest mocks base method. +func (m *MockPackClient) RemoveManifest(arg0 context.Context, arg1 client.RemoveManifestOptions) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveManifest", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveManifest indicates an expected call of RemoveManifest. +func (mr *MockPackClientMockRecorder) RemoveManifest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveManifest", reflect.TypeOf((*MockPackClient)(nil).RemoveManifest), arg0, arg1) +} + // YankBuildpack mocks base method. func (m *MockPackClient) YankBuildpack(arg0 client.YankBuildpackOptions) error { m.ctrl.T.Helper() diff --git a/pkg/testmocks/mock_blob_downloader.go b/pkg/testmocks/mock_blob_downloader.go index 80a04d9878..9a044edd21 100644 --- a/pkg/testmocks/mock_blob_downloader.go +++ b/pkg/testmocks/mock_blob_downloader.go @@ -8,9 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - blob "github.com/buildpacks/pack/pkg/blob" + gomock "github.com/golang/mock/gomock" ) // MockBlobDownloader is a mock of BlobDownloader interface. diff --git a/pkg/testmocks/mock_build_module.go b/pkg/testmocks/mock_build_module.go index 5548ad6f6f..35045ea9f5 100644 --- a/pkg/testmocks/mock_build_module.go +++ b/pkg/testmocks/mock_build_module.go @@ -8,9 +8,8 @@ import ( io "io" reflect "reflect" - gomock "github.com/golang/mock/gomock" - buildpack "github.com/buildpacks/pack/pkg/buildpack" + gomock "github.com/golang/mock/gomock" ) // MockBuildModule is a mock of BuildModule interface. diff --git a/pkg/testmocks/mock_buildpack_downloader.go b/pkg/testmocks/mock_buildpack_downloader.go index d67ff548d2..9758f24b2c 100644 --- a/pkg/testmocks/mock_buildpack_downloader.go +++ b/pkg/testmocks/mock_buildpack_downloader.go @@ -8,9 +8,8 @@ import ( context "context" reflect "reflect" - gomock "github.com/golang/mock/gomock" - buildpack "github.com/buildpacks/pack/pkg/buildpack" + gomock "github.com/golang/mock/gomock" ) // MockBuildpackDownloader is a mock of BuildpackDownloader interface. diff --git a/pkg/testmocks/mock_docker_client.go b/pkg/testmocks/mock_docker_client.go index 18b51c18f1..5ba29fec53 100644 --- a/pkg/testmocks/mock_docker_client.go +++ b/pkg/testmocks/mock_docker_client.go @@ -224,10 +224,10 @@ func (mr *MockCommonAPIClientMockRecorder) ContainerCreate(arg0, arg1, arg2, arg } // ContainerDiff mocks base method. -func (m *MockCommonAPIClient) ContainerDiff(arg0 context.Context, arg1 string) ([]container.ContainerChangeResponseItem, error) { +func (m *MockCommonAPIClient) ContainerDiff(arg0 context.Context, arg1 string) ([]container.FilesystemChange, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ContainerDiff", arg0, arg1) - ret0, _ := ret[0].([]container.ContainerChangeResponseItem) + ret0, _ := ret[0].([]container.FilesystemChange) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1338,7 +1338,7 @@ func (mr *MockCommonAPIClientMockRecorder) PluginUpgrade(arg0, arg1, arg2 interf } // RegistryLogin mocks base method. -func (m *MockCommonAPIClient) RegistryLogin(arg0 context.Context, arg1 types.AuthConfig) (registry.AuthenticateOKBody, error) { +func (m *MockCommonAPIClient) RegistryLogin(arg0 context.Context, arg1 registry.AuthConfig) (registry.AuthenticateOKBody, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RegistryLogin", arg0, arg1) ret0, _ := ret[0].(registry.AuthenticateOKBody) @@ -1725,7 +1725,7 @@ func (mr *MockCommonAPIClientMockRecorder) VolumeInspectWithRaw(arg0, arg1 inter } // VolumeList mocks base method. -func (m *MockCommonAPIClient) VolumeList(arg0 context.Context, arg1 filters.Args) (volume.ListResponse, error) { +func (m *MockCommonAPIClient) VolumeList(arg0 context.Context, arg1 volume.ListOptions) (volume.ListResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "VolumeList", arg0, arg1) ret0, _ := ret[0].(volume.ListResponse) diff --git a/pkg/testmocks/mock_image_fetcher.go b/pkg/testmocks/mock_image_fetcher.go index 281f28d04d..df0e08ff7f 100644 --- a/pkg/testmocks/mock_image_fetcher.go +++ b/pkg/testmocks/mock_image_fetcher.go @@ -9,9 +9,8 @@ import ( reflect "reflect" imgutil "github.com/buildpacks/imgutil" - gomock "github.com/golang/mock/gomock" - image "github.com/buildpacks/pack/pkg/image" + gomock "github.com/golang/mock/gomock" ) // MockImageFetcher is a mock of ImageFetcher interface. From a504b1a6d190fb6a6f7f97b2bfbd6963c2d8742f Mon Sep 17 00:00:00 2001 From: Husni Faiz Date: Wed, 12 Jul 2023 08:57:14 +0530 Subject: [PATCH 41/79] fix format Signed-off-by: Husni Faiz --- internal/builder/testmocks/mock_lifecycle.go | 3 ++- .../commands/testmocks/mock_inspect_image_writer_factory.go | 3 ++- internal/commands/testmocks/mock_pack_client.go | 3 ++- pkg/testmocks/mock_blob_downloader.go | 3 ++- pkg/testmocks/mock_build_module.go | 3 ++- pkg/testmocks/mock_buildpack_downloader.go | 3 ++- pkg/testmocks/mock_image_fetcher.go | 3 ++- 7 files changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/builder/testmocks/mock_lifecycle.go b/internal/builder/testmocks/mock_lifecycle.go index fabeab8401..88aacd786f 100644 --- a/internal/builder/testmocks/mock_lifecycle.go +++ b/internal/builder/testmocks/mock_lifecycle.go @@ -8,8 +8,9 @@ import ( io "io" reflect "reflect" - builder "github.com/buildpacks/pack/internal/builder" gomock "github.com/golang/mock/gomock" + + builder "github.com/buildpacks/pack/internal/builder" ) // MockLifecycle is a mock of Lifecycle interface. diff --git a/internal/commands/testmocks/mock_inspect_image_writer_factory.go b/internal/commands/testmocks/mock_inspect_image_writer_factory.go index afeaa5c20f..9f5a65c546 100644 --- a/internal/commands/testmocks/mock_inspect_image_writer_factory.go +++ b/internal/commands/testmocks/mock_inspect_image_writer_factory.go @@ -7,8 +7,9 @@ package testmocks import ( reflect "reflect" - writer "github.com/buildpacks/pack/internal/inspectimage/writer" gomock "github.com/golang/mock/gomock" + + writer "github.com/buildpacks/pack/internal/inspectimage/writer" ) // MockInspectImageWriterFactory is a mock of InspectImageWriterFactory interface. diff --git a/internal/commands/testmocks/mock_pack_client.go b/internal/commands/testmocks/mock_pack_client.go index c066c6856d..66172c48c1 100644 --- a/internal/commands/testmocks/mock_pack_client.go +++ b/internal/commands/testmocks/mock_pack_client.go @@ -8,8 +8,9 @@ import ( context "context" reflect "reflect" - client "github.com/buildpacks/pack/pkg/client" gomock "github.com/golang/mock/gomock" + + client "github.com/buildpacks/pack/pkg/client" ) // MockPackClient is a mock of PackClient interface. diff --git a/pkg/testmocks/mock_blob_downloader.go b/pkg/testmocks/mock_blob_downloader.go index 9a044edd21..80a04d9878 100644 --- a/pkg/testmocks/mock_blob_downloader.go +++ b/pkg/testmocks/mock_blob_downloader.go @@ -8,8 +8,9 @@ import ( context "context" reflect "reflect" - blob "github.com/buildpacks/pack/pkg/blob" gomock "github.com/golang/mock/gomock" + + blob "github.com/buildpacks/pack/pkg/blob" ) // MockBlobDownloader is a mock of BlobDownloader interface. diff --git a/pkg/testmocks/mock_build_module.go b/pkg/testmocks/mock_build_module.go index 35045ea9f5..5548ad6f6f 100644 --- a/pkg/testmocks/mock_build_module.go +++ b/pkg/testmocks/mock_build_module.go @@ -8,8 +8,9 @@ import ( io "io" reflect "reflect" - buildpack "github.com/buildpacks/pack/pkg/buildpack" gomock "github.com/golang/mock/gomock" + + buildpack "github.com/buildpacks/pack/pkg/buildpack" ) // MockBuildModule is a mock of BuildModule interface. diff --git a/pkg/testmocks/mock_buildpack_downloader.go b/pkg/testmocks/mock_buildpack_downloader.go index 9758f24b2c..d67ff548d2 100644 --- a/pkg/testmocks/mock_buildpack_downloader.go +++ b/pkg/testmocks/mock_buildpack_downloader.go @@ -8,8 +8,9 @@ import ( context "context" reflect "reflect" - buildpack "github.com/buildpacks/pack/pkg/buildpack" gomock "github.com/golang/mock/gomock" + + buildpack "github.com/buildpacks/pack/pkg/buildpack" ) // MockBuildpackDownloader is a mock of BuildpackDownloader interface. diff --git a/pkg/testmocks/mock_image_fetcher.go b/pkg/testmocks/mock_image_fetcher.go index df0e08ff7f..281f28d04d 100644 --- a/pkg/testmocks/mock_image_fetcher.go +++ b/pkg/testmocks/mock_image_fetcher.go @@ -9,8 +9,9 @@ import ( reflect "reflect" imgutil "github.com/buildpacks/imgutil" - image "github.com/buildpacks/pack/pkg/image" gomock "github.com/golang/mock/gomock" + + image "github.com/buildpacks/pack/pkg/image" ) // MockImageFetcher is a mock of ImageFetcher interface. From 1a7a9303e208edf7ad12f6ba896ea71970d0f00c Mon Sep 17 00:00:00 2001 From: WYGIN Date: Fri, 15 Dec 2023 12:37:29 +0000 Subject: [PATCH 42/79] WIP added pack manifest cli Signed-off-by: WYGIN --- go.mod | 4 +- go.sum | 4 + internal/builder/testmocks/mock_lifecycle.go | 3 +- internal/commands/commands.go | 15 +-- internal/commands/manifest.go | 13 +-- internal/commands/manifest_add.go | 93 ++++++++++----- internal/commands/manifest_annotate.go | 72 ++++++------ internal/commands/manifest_create.go | 115 +++++++++---------- internal/commands/manifest_exists.go | 33 ++++++ internal/commands/manifest_inspect.go | 37 +++--- internal/commands/manifest_push.go | 57 ++++----- internal/commands/manifest_remove.go | 40 +++---- internal/commands/manifest_rm.go | 41 +++---- pkg/client/add_manifest.go | 87 +++++++++++--- pkg/client/annotate_manifest.go | 83 +++++++++---- pkg/client/client.go | 61 +++++++--- pkg/client/create_manifest.go | 74 ++++++------ pkg/client/exists_manifest.go | 15 +++ pkg/client/inspect_manifest.go | 53 +++++++-- pkg/client/push_manifest.go | 32 +++--- pkg/client/remove_manifest.go | 31 ++--- pkg/client/rm_manifest.go | 41 +++---- pkg/errors/errors.go | 9 ++ pkg/testmocks/mock_build_module.go | 3 +- 24 files changed, 579 insertions(+), 437 deletions(-) create mode 100644 internal/commands/manifest_exists.go create mode 100644 pkg/client/exists_manifest.go create mode 100644 pkg/errors/errors.go diff --git a/go.mod b/go.mod index 950df49656..fa43025b88 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/Microsoft/go-winio v0.6.1 github.com/apex/log v1.9.0 - github.com/buildpacks/imgutil v0.0.0-20230626185301-726f02e4225c + github.com/buildpacks/imgutil v0.0.0-20231027205711-0eae658d5962 github.com/buildpacks/lifecycle v0.17.1 github.com/docker/cli v24.0.6+incompatible github.com/docker/docker v24.0.6+incompatible @@ -126,4 +126,4 @@ require ( go 1.20 -replace github.com/buildpacks/imgutil => github.com/drac98/imgutil v1.4.0 +replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20231027205711-0eae658d5962 diff --git a/go.sum b/go.sum index 5d0e6c68c2..d935bfa205 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,10 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20230626185301-726f02e4225c h1:KMziTau8mg/nYMh598Lt0IZ97h0nbQqiRz7/jaE3nM8= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20230626185301-726f02e4225c/go.mod h1:mBG5M3GJW5nknCEOOqtmMHyPYnSpw/5GEiciuYU/COw= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20231027205711-0eae658d5962 h1:z6GOX2Wl2+SDLJRret/eAANd5B8tCf35TI4xoopRIbM= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20231027205711-0eae658d5962/go.mod h1:Ade+4Q1OovFw6Zdzd+/UVaqWptZSlpnZ8n/vlkgS7M8= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= diff --git a/internal/builder/testmocks/mock_lifecycle.go b/internal/builder/testmocks/mock_lifecycle.go index 88aacd786f..fabeab8401 100644 --- a/internal/builder/testmocks/mock_lifecycle.go +++ b/internal/builder/testmocks/mock_lifecycle.go @@ -8,9 +8,8 @@ import ( io "io" reflect "reflect" - gomock "github.com/golang/mock/gomock" - builder "github.com/buildpacks/pack/internal/builder" + gomock "github.com/golang/mock/gomock" ) // MockLifecycle is a mock of Lifecycle interface. diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 0cb008e225..08bfd3933b 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -32,13 +32,14 @@ type PackClient interface { InspectExtension(client.InspectExtensionOptions) (*client.ExtensionInfo, error) PullBuildpack(context.Context, client.PullBuildpackOptions) error DownloadSBOM(name string, options client.DownloadSBOMOptions) error - CreateManifest(context.Context, client.CreateManifestOptions) error - AnnotateManifest(context.Context, client.AnnotateManifestOptions) error - AddManifest(context.Context, client.AddManifestOptions) error - PushManifest(context.Context, client.PushManifestOptions) error - RemoveManifest(context.Context, client.RemoveManifestOptions) error - DeleteManifest(context.Context, client.DeleteManifestOptions) error - InspectManifest(context.Context, client.InspectManifestOptions) error + CreateManifest(ctx context.Context, name string, images []string, opts client.CreateManifestOptions) (imageID string, err error) + AnnotateManifest(ctx context.Context, name string, image string, opts client.ManifestAnnotateOptions) error + ExistsManifest(ctx context.Context, image string) error + AddManifest(ctx context.Context, index string, images string, opts client.ManifestAddOptions) (imageID string, err error) + DeleteManifest(ctx context.Context, name []string) error + RemoveManifest(ctx context.Context, name string, images []string) error + PushManifest(ctx context.Context, index string, opts client.PushManifestOptions) (imageID string, err error) + InspectManifest(ctx context.Context, name string, opts client.InspectManifestOptions) error } func AddHelpFlag(cmd *cobra.Command, commandName string) { diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index 8f000bf881..d7f671148a 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -8,19 +8,18 @@ import ( func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command { cmd := &cobra.Command{ - Use: "manifest", - Aliases: []string{"manifest"}, - Short: "Handle manifest list", - RunE: nil, + Use: "manifest", + Short: "Interact with image index or manifest list", + RunE: nil, } cmd.AddCommand(ManifestCreate(logger, client)) - cmd.AddCommand(ManifestAnnotate(logger, client)) cmd.AddCommand(ManifestAdd(logger, client)) - cmd.AddCommand(ManifestPush(logger, client)) - cmd.AddCommand(ManifestRemove(logger, client)) + cmd.AddCommand(ManifestAnnotate(logger, client)) cmd.AddCommand(ManifestDelete(logger, client)) cmd.AddCommand(ManifestInspect(logger, client)) + cmd.AddCommand(ManifestPush(logger, client)) + cmd.AddCommand(ManifestRemove(logger, client)) AddHelpFlag(cmd, "manifest") return cmd diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 18b59de435..61e0b318b2 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -1,70 +1,101 @@ package commands import ( - "path/filepath" + "fmt" + "strings" "github.com/spf13/cobra" - "github.com/buildpacks/pack/internal/config" - "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" ) +// ManifestAddFlags define flags provided to the ManifestAdd type ManifestAddFlags struct { - All bool - Architecture string - OS string - Variant string + os, osVersion, osArch, osVariant string + osFeatures, annotations, features string + all bool } +// ManifestAdd modifies a manifest list (Image index) and add a new image to the list of manifests. func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestAddFlags + cmd := &cobra.Command{ - Use: "add [OPTIONS] ", - Short: "Add a new image to the manifest list", - Args: cobra.MatchAll(cobra.ExactArgs(2)), - Example: `pack manifest add cnbs/sample-package:hello-multiarch-universe \ - cnbs/sample-package:hello-universe-riscv-linux`, - Long: "manifest add modifies a manifest list (Image index) and add a new image to the list of manifests.", + Use: "pack manifest add [OPTIONS] [flags]", + Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), + Short: "manifest add modifies a manifest list (Image index) and add a new image to the list of manifests.", + Example: `pack manifest add cnbs/sample-package:hello-multiarch-universe \ + cnbs/sample-package:hello-universe-riscv-linux`, + Long: `manifest add modifies a manifest list (Image index) and add a new image to the list of manifests. + + When a manifest list exits locally, user can add a new image to the manifest list using this command`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - if err := validateManifestAddFlags(&flags); err != nil { + imageIndex := args[0] + manifests := args[1] + if err := validateManifestAddFlags(flags); err != nil { return err } - indexName := args[0] - manifest := args[1] - - packHome, err := config.PackHome() + osFeatures:= strings.Split(flags.osFeatures, ";") + features:= strings.Split(flags.features, ";") + annotations, err := StringToKeyValueMap(flags.annotations) if err != nil { return err } - manifestDir := filepath.Join(packHome, "manifests") + imageID, err := pack.AddManifest(cmd.Context(), imageIndex, manifests, client.ManifestAddOptions{ + OS: flags.os, + OSVersion: flags.osVersion, + OSArch: flags.osArch, + OSVariant: flags.osVariant, + OSFeatures: osFeatures, + Features: features, + Annotations: annotations, + All: flags.all, + }) - if err := pack.AddManifest(cmd.Context(), client.AddManifestOptions{ - Index: indexName, - Path: manifestDir, - Manifest: manifest, - All: flags.All, - }); err != nil { + if err != nil { return err } - logger.Infof("Successfully added the image %s to the image index %s", style.Symbol(manifest), style.Symbol(indexName)) + logger.Infof(imageID) return nil }), } - cmd.Flags().BoolVar(&flags.All, "all", false, `add all of the contents to the local list (applies only if is an index)`) - cmd.Flags().StringVar(&flags.Architecture, "arch", "", "Set the architecture") - cmd.Flags().StringVar(&flags.OS, "os", "", "Set the operating system") - cmd.Flags().StringVar(&flags.Variant, "variant", "", "Set the architecture variant") + cmd.Flags().BoolVar(&flags.all, "all", false, "add all of the contents to the local list (applies only if is an index)") + cmd.Flags().StringVar(&flags.os, "os", "", "Set the operating system") + cmd.Flags().StringVar(&flags.osArch, "arch", "", "Set the architecture") + cmd.Flags().StringVar(&flags.osVariant, "variant", "", "Set the architecture variant") + cmd.Flags().StringVar(&flags.osFeatures, "os-features", "", "Set the OSFeatures") + cmd.Flags().StringVar(&flags.features, "features", "", "Set the Features") + cmd.Flags().StringVar(&flags.annotations, "annotations", "", "Set the annotations") AddHelpFlag(cmd, "add") return cmd } -func validateManifestAddFlags(p *ManifestAddFlags) error { +func validateManifestAddFlags(flags ManifestAddFlags) error { return nil } + +func StringToKeyValueMap(s string) (map[string]string, error) { + keyValues := strings.Split(s, ";") + + m := map[string]string{} + + for _, keyValue := range keyValues { + parts := strings.Split(keyValue, "=") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid key-value pair: %s", keyValue) + } + + key := parts[0] + value := parts[1] + + m[key] = value + } + + return m, nil +} diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 10590922ba..a208fc0ff8 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -1,69 +1,61 @@ package commands import ( - "path/filepath" + "strings" "github.com/spf13/cobra" - "github.com/buildpacks/pack/internal/config" - "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" ) +// ManifestAnnotateFlags define flags provided to the ManifestAnnotate type ManifestAnnotateFlags struct { - Architecture string // Set the architecture - OS string // Set the operating system - Variant string // Set the architecture variant + os, arch, variant, osVersion string + features, osFeatures, annotations string } +// ManifestAnnotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list. func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestAnnotateFlags + cmd := &cobra.Command{ - Use: "annotate [OPTIONS] ", - Short: "Annotate a manifest list", - Args: cobra.MatchAll(cobra.ExactArgs(2)), - Example: `pack manifest annotate cnbs/sample-package:hello-universe-multiarch \ cnbs/sample-package:hello-universe --arch amd64`, - Long: "manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.", + Use: "pack manifest annotate [OPTIONS] [...] [flags]", + Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), + Short: "manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.", + Example: `pack manifest annotate cnbs/sample-package:hello-universe-multiarch \ + cnbs/sample-package:hello-universe --arch amd64`, + Long: `manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list. + Sometimes a manifest list could reference an image that doesn't specify the architecture, The "annotate" command allows users to update those values before pushing the manifest list a registry`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - if err := validateManifestAnnotateFlags(&flags); err != nil { - return err - } - - indexName := args[0] - manifest := args[1] - - packHome, err := config.PackHome() + osFeatures:= strings.Split(flags.osFeatures, ";") + features:= strings.Split(flags.features, ";") + annotations, err := StringToKeyValueMap(flags.annotations) if err != nil { return err } - manifestDir := filepath.Join(packHome, "manifests") - - if err := pack.AnnotateManifest(cmd.Context(), client.AnnotateManifestOptions{ - Index: indexName, - Path: manifestDir, - Manifest: manifest, - Architecture: flags.Architecture, - OS: flags.OS, - Variant: flags.Variant, - }); err != nil { - return err - } - logger.Infof("Successfully annotated image index %s", style.Symbol(indexName)) - + pack.AnnotateManifest(cmd.Context(), args[0], args[1], client.ManifestAnnotateOptions{ + OS: flags.os, + OSVersion: flags.osVersion, + OSArch: flags.arch, + OSVariant: flags.variant, + OSFeatures: osFeatures, + Features: features, + Annotations: annotations, + }) return nil }), } - cmd.Flags().StringVar(&flags.Architecture, "arch", "", "Set the architecture") - cmd.Flags().StringVar(&flags.OS, "os", "", "Set the operating system") - cmd.Flags().StringVar(&flags.Variant, "variant", "", "Set the architecture variant") + cmd.Flags().StringVar(&flags.os, "os", "", "Set the architecture") + cmd.Flags().StringVar(&flags.arch, "arch", "", "Set the architecture") + cmd.Flags().StringVar(&flags.variant, "variant", "", "Set the architecture") + cmd.Flags().StringVar(&flags.osVersion, "os-version", "", "override the os `version` of the specified image") + cmd.Flags().StringVar(&flags.features, "features", "", "override the `features` of the specified image") + cmd.Flags().StringVar(&flags.osFeatures, "os-features", "", "override the os `features` of the specified image") + cmd.Flags().StringVar(&flags.annotations, "annotations", "", "set an `annotation` for the specified image") AddHelpFlag(cmd, "annotate") return cmd } - -func validateManifestAnnotateFlags(p *ManifestAnnotateFlags) error { - return nil -} diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 86c184babc..b42c779618 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -1,99 +1,92 @@ package commands import ( - "path/filepath" + "fmt" - "github.com/buildpacks/imgutil" - "github.com/pkg/errors" + "github.com/spf13/cobra" - "github.com/buildpacks/pack/internal/config" - "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" - - "github.com/spf13/cobra" ) +// ManifestCreateFlags define flags provided to the ManifestCreate type ManifestCreateFlags struct { - Publish bool - Insecure bool - Registry string - Format string + format, registry, os, arch string + insecure, publish, all, amend bool } +// ManifestCreate creates an image-index/image-list for a multi-arch image func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestCreateFlags + cmd := &cobra.Command{ - Use: "create [ ... ]", - Short: "Creates a manifest list", - Args: cobra.MatchAll(cobra.MinimumNArgs(2)), - Example: `pack manifest create create cnbs/sample-package:hello-multiarch-universe \ - cnbs/sample-package:hello-universe \ - cnbs/sample-package:hello-universe-windows`, - Long: "manifest create generates a manifest list for a multi-arch image", + Use: "pack manifest create [ ... ] [flags]", + Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), + Short: "manifest create generates a manifest list for a multi-arch image", + Example: `pack manifest create cnbs/sample-package:hello-multiarch-universe \ + cnbs/sample-package:hello-universe \ + cnbs/sample-package:hello-universe-windows`, + Long: `Create a manifest list or image index for the image to support muti architecture for the image, it create a new ManifestList or ImageIndex with the given name and adds the list of Manifests to the newly created ImageIndex or ManifestList + + If the already exists in the registry: pack will save a local copy of the remote manifest list, + If the doestn't exist in a registry: pack will create a local representation of the manifest list that will only save on the remote registry if the user publish it`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - if err := validateManifestCreateFlags(&flags); err != nil { + imageIndex := args[0] + manifests := args[1:] + cmdFlags := cmd.Flags() + + if err := validateManifestCreateFlags(flags); err != nil { return err } - mediaType, err := validateMediaTypeFlag(flags.Format) - if err != nil { - return err + if cmdFlags.Changed("insecure") { + flags.insecure = !flags.insecure } - packHome, err := config.PackHome() - if err != nil { - return err + if cmdFlags.Changed("publish") { + flags.publish = !flags.publish } - manifestDir := filepath.Join(packHome, "manifests") + id, err := pack.CreateManifest(cmd.Context(), imageIndex, manifests, client.CreateManifestOptions{ + Format: flags.format, + Registry: flags.registry, + Insecure: flags.insecure, + Publish: flags.publish, + }) - indexName := args[0] - manifests := args[1:] - if err := pack.CreateManifest(cmd.Context(), client.CreateManifestOptions{ - ManifestName: indexName, - Manifests: manifests, - MediaType: mediaType, - Publish: flags.Publish, - Registry: flags.Registry, - ManifestDir: manifestDir, - }); err != nil { + if err != nil { return err } - - logger.Infof("Successfully created image index %s", style.Symbol(indexName)) + logger.Infof("Successfully created ImageIndex/ManifestList with imageID: '%s'", id) return nil }), } - cmd.Flags().BoolVar(&flags.Publish, "publish", false, `Publish to registry`) - cmd.Flags().BoolVar(&flags.Insecure, "insecure", false, `Allow publishing to insecure registry`) - cmd.Flags().StringVarP(&flags.Format, "format", "f", "v2s2", `Format to save image index as ("OCI" or "V2S2")`) - cmd.Flags().StringVarP(&flags.Registry, "registry", "r", "", `Registry URL to publish the image index`) + cmdFlags := cmd.Flags() + + cmdFlags.StringVarP(&flags.format, "format", "f", "v2s2", "Format to save image index as ('OCI' or 'V2S2') (default 'v2s2')") + cmdFlags.StringVarP(&flags.registry, "registry", "r", "", "Publish to registry") + cmdFlags.StringVar(&flags.os, "os", "", "If any of the specified images is a list/index, choose the one for `os`") + if err := cmdFlags.MarkHidden("os"); err != nil { + panic(fmt.Sprintf("error marking --os as hidden: %v", err)) + } + cmdFlags.StringVar(&flags.arch, "arch", "", "If any of the specified images is a list/index, choose the one for `arch`") + if err := cmdFlags.MarkHidden("arch"); err != nil { + panic(fmt.Sprintf("error marking --arch as hidden: %v", err)) + } + cmdFlags.BoolVar(&flags.insecure, "insecure", false, "Allow publishing to insecure registry") + if err := cmdFlags.MarkHidden("insecure"); err != nil { + panic(fmt.Sprintf("error marking insecure as hidden: %v", err)) + } + cmdFlags.BoolVar(&flags.publish, "publish", false, "Publish to registry") + cmdFlags.BoolVar(&flags.all, "all", false, "Add all of the list's images if the images to add are lists/index") + cmdFlags.BoolVar(&flags.amend, "amend", false, "Modify an existing list/index if one with the desired name already exists") AddHelpFlag(cmd, "create") return cmd } -func validateManifestCreateFlags(p *ManifestCreateFlags) error { - if p.Format == "" { - return errors.Errorf("--format flag received an empty value") - } +func validateManifestCreateFlags(flags ManifestCreateFlags) error { return nil } - -func validateMediaTypeFlag(format string) (imgutil.MediaTypes, error) { - var mediaType imgutil.MediaTypes - - switch format { - case "oci": - mediaType = imgutil.OCITypes - case "v2s2": - mediaType = imgutil.DockerTypes - default: - return imgutil.MissingTypes, errors.Errorf("unsupported media type given for --format") - } - - return mediaType, nil -} diff --git a/internal/commands/manifest_exists.go b/internal/commands/manifest_exists.go new file mode 100644 index 0000000000..5cf9b9462e --- /dev/null +++ b/internal/commands/manifest_exists.go @@ -0,0 +1,33 @@ +package commands + +import ( + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/pkg/logging" +) + +// ManifestDeleteFlags define flags provided to the ManifestDelete +// type ManifestDeleteFlags struct { +// } + +// ManifestExists checks if a manifest list exists in local storage +func ManifestExists(logger logging.Logger, pack PackClient) *cobra.Command { + // var flags ManifestDeleteFlags + + cmd := &cobra.Command{ + Use: "pack manifest exists [manifest-list]", + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + Short: "checks if a manifest list exists in local storage", + Example: `pack manifest exists cnbs/sample-package:hello-multiarch-universe`, + 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 + } + return nil + }), + } + + AddHelpFlag(cmd, "remove") + return cmd +} diff --git a/internal/commands/manifest_inspect.go b/internal/commands/manifest_inspect.go index 0bb09594ba..46559e5f3f 100644 --- a/internal/commands/manifest_inspect.go +++ b/internal/commands/manifest_inspect.go @@ -1,40 +1,29 @@ package commands import ( - "path/filepath" - "github.com/spf13/cobra" - "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" ) +// ManifestInspectFlags define flags provided to the ManifestInspect +// type ManifestInspectFlags struct { +// } + +// ManifestInspect shows the manifest information stored in local storage func ManifestInspect(logger logging.Logger, pack PackClient) *cobra.Command { + // var flags ManifestInspectFlags + cmd := &cobra.Command{ - Use: "inspect ", - Short: "Inspect a local manifest list", - Args: cobra.MatchAll(cobra.ExactArgs(1)), + Use: "pack manifest inspect [flags]", + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + Short: "manifest inspect shows the manifest information stored in local storage", Example: `pack manifest inspect cnbs/sample-builder:multiarch`, - Long: "manifest inspect shows the manifest information stored in local storage", + Long: `manifest inspect shows the manifest information stored in local storage. + The inspect command will help users to view how their local manifest list looks like`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - indexName := args[0] - - packHome, err := config.PackHome() - if err != nil { - return err - } - - manifestDir := filepath.Join(packHome, "manifests") - - if err := pack.InspectManifest(cmd.Context(), client.InspectManifestOptions{ - Index: indexName, - Path: manifestDir, - }); err != nil { - return err - } - - return nil + return pack.InspectManifest(cmd.Context(), args[0], client.InspectManifestOptions{}) }), } diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index 7bd3030b2d..bd00f60092 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -1,68 +1,59 @@ package commands import ( - "path/filepath" - "github.com/spf13/cobra" - "github.com/buildpacks/pack/internal/config" - "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" ) +// ManifestPushFlags define flags provided to the ManifestPush type ManifestPushFlags struct { - // Manifest list type (oci or v2s2) to use when pushing the list (default is v2s2). - Format string - - // Allow push to an insecure registry. - Insecure bool - - //// Delete the manifest list or image index from local storage if pushing succeeds. - Purge bool + format string + insecure, purge, all, quite bool } +// ManifestPush pushes a manifest list (Image index) to a registry. func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestPushFlags + cmd := &cobra.Command{ - Use: "push [OPTIONS] ", - Short: "Push a manifest list to a repository", - Args: cobra.MatchAll(cobra.ExactArgs(1)), + Use: "pack manifest push [OPTIONS] [flags]", + Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), + Short: "manifest push pushes a manifest list (Image index) to a registry.", Example: `pack manifest push cnbs/sample-package:hello-multiarch-universe`, - Long: "manifest push pushes a manifest list (Image index) to a registry.", + Long: `manifest push pushes a manifest list (Image index) to a registry. + Once a manifest list is ready to be published into the registry, the push command can be used`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - if err := validateManifestPushFlags(&flags); err != nil { + if err := parseFalgs(flags); err != nil { return err } - indexName := args[0] - packHome, err := config.PackHome() - if err != nil { - return err - } + imageID, err := pack.PushManifest(cmd.Context(), args[0], client.PushManifestOptions{ + Format: flags.format, + Insecure: flags.insecure, + Purge: flags.purge, + }) - manifestDir := filepath.Join(packHome, "manifests") - - if err := pack.PushManifest(cmd.Context(), client.PushManifestOptions{ - Index: indexName, - Path: manifestDir, - }); err != nil { + if err != nil { return err } - logger.Infof("Successfully pushed the %s image index to the repository.", style.Symbol(indexName)) + logger.Infof(imageID) return nil }), } - cmd.Flags().BoolVar(&flags.Insecure, "insecure", false, `Allow publishing to insecure registry`) - cmd.Flags().BoolVarP(&flags.Purge, "purge", "p", false, `Delete the manifest list or image index from local storage if pushing succeeds`) - cmd.Flags().StringVarP(&flags.Format, "format", "f", "", `Manifest list type (oci or v2s2) to use when pushing the list (default is v2s2)`) + cmd.Flags().StringVarP(&flags.format, "format", "f", "", "Format to save image index as ('OCI' or 'V2S2') (default 'v2s2')") + cmd.Flags().BoolVar(&flags.insecure, "insecure", false, "Allow publishing to insecure registry") + cmd.Flags().BoolVar(&flags.purge, "purge", false, "Delete the manifest list or image index from local storage if pushing succeeds") + cmd.Flags().BoolVar(&flags.all, "all", false, "Also push the images in the list") + cmd.Flags().BoolVarP(&flags.quite, "quite", "q", false, "Also push the images in the list") AddHelpFlag(cmd, "push") return cmd } -func validateManifestPushFlags(p *ManifestPushFlags) error { +func parseFalgs(flags ManifestPushFlags) error { return nil } diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index 41ee172eb0..e4998b72d9 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -1,44 +1,30 @@ package commands import ( - "path/filepath" - "github.com/spf13/cobra" - "github.com/buildpacks/pack/internal/config" - "github.com/buildpacks/pack/internal/style" - "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" ) +// ManifestDeleteFlags define flags provided to the ManifestDelete +// type ManifestDeleteFlags struct { +// } + +// ManifestDelete deletes one or more manifest lists from local storage func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { + // var flags ManifestDeleteFlags + cmd := &cobra.Command{ - Use: "remove [manifest-list] [manifest-list...]", + Use: "pack manifest remove [manifest-list] [manifest-list...] [flags]", + Args: cobra.MatchAll(cobra.MinimumNArgs(1), cobra.OnlyValidArgs), Short: "Delete one or more manifest lists from local storage", - Args: cobra.MatchAll(cobra.MinimumNArgs(1)), - Example: `pack manifest delete cnbs/sample-package:hello-multiarch-universe`, - Long: "Delete one or more manifest lists from local storage", + Example: `pack manifest remove cnbs/sample-package:hello-multiarch-universe`, + Long: `Delete one or more manifest lists from local storage. + When a manifest list exits locally, users can remove existing images from a manifest list`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - indexNames := args - - packHome, err := config.PackHome() - if err != nil { + if err := pack.DeleteManifest(cmd.Context(), args); err != nil { return err } - - manifestDir := filepath.Join(packHome, "manifests") - - for _, repoName := range indexNames { - err = pack.DeleteManifest(cmd.Context(), client.DeleteManifestOptions{ - Index: repoName, - Path: manifestDir, - }) - if err != nil { - logger.Infof("Failed to remove index '%s' from local storage\n", style.Symbol(repoName)) - } else { - logger.Infof("Successfully removed index '%s' from local storage\n", style.Symbol(repoName)) - } - } return nil }), } diff --git a/internal/commands/manifest_rm.go b/internal/commands/manifest_rm.go index 938eafecf2..99a0af17bc 100644 --- a/internal/commands/manifest_rm.go +++ b/internal/commands/manifest_rm.go @@ -1,44 +1,31 @@ package commands import ( - "path/filepath" - "github.com/spf13/cobra" - "github.com/buildpacks/pack/internal/config" - "github.com/buildpacks/pack/internal/style" - "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" ) +// ManifestRemoveFlags define flags provided to the ManifestRemove +// type ManifestRemoveFlags struct { +// } + +// ManifestRemove will remove the specified image manifest if it is already referenced in the index func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { + // var flags ManifestRemoveFlags + cmd := &cobra.Command{ - Use: "rm [manifest-list] [manifest]", - Short: "Remove an image manifest from index", - Args: cobra.MatchAll(cobra.ExactArgs(2)), + Use: "pack manifest rm [manifest-list] [manifest] [manifest...] [flags]", + Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), + Short: "manifest rm will remove the specified image manifest if it is already referenced in the index", Example: `pack manifest rm cnbs/sample-package:hello-multiarch-universe \ - cnbs/sample-package:hello-universe-windows`, - Long: "manifest remove will remove the specified image manifest if it is already referenced in the index", + cnbs/sample-package:hello-universe-windows`, + Long: `manifest rm will remove the specified image manifest if it is already referenced in the index. + Sometimes users can just experiment with the feature locally and they want to discard all the local information created by pack. 'rm' command just delete the local manifest list`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - indexName := args[0] - manifest := args[1] - - packHome, err := config.PackHome() - if err != nil { + if err := pack.RemoveManifest(cmd.Context(), args[0], args[1:]); err != nil { return err } - - manifestDir := filepath.Join(packHome, "manifests") - - if err := pack.RemoveManifest(cmd.Context(), client.RemoveManifestOptions{ - Index: indexName, - Path: manifestDir, - Manifest: manifest, - }); err != nil { - return err - } - logger.Infof("Successfully removed the manifest '%s' from image index %s", style.Symbol(manifest), style.Symbol(indexName)) - return nil }), } diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 6b3bb2b0d4..a628688b6c 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -2,40 +2,89 @@ package client import ( "context" + "fmt" + "strings" - "github.com/buildpacks/imgutil/local" - "github.com/pkg/errors" + "github.com/google/go-containerregistry/pkg/name" ) -type AddManifestOptions struct { - Index string - Path string - Manifest string - All bool +type ManifestAddOptions struct { + OS, OSVersion, OSArch, OSVariant string + OSFeatures, Features []string + Annotations map[string]string + All bool } -func (c *Client) AddManifest(ctx context.Context, opts AddManifestOptions) error { - indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) +// AddManifest implements commands.PackClient. +func (c *Client) AddManifest(ctx context.Context, index string, image string, opts ManifestAddOptions) (indexID string, err error) { + _, err = name.ParseReference(index) if err != nil { - return errors.Wrapf(err, "Get local index manifest '%s' from path '%s'", opts.Index, opts.Path) + return } - idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) + ref, err := name.ParseReference(image) if err != nil { - return errors.Wrapf(err, "Create local index from '%s' local index manifest", opts.Index) + return } - // Append manifest to local index - err = idx.Add(opts.Manifest) + imgIndex, err := c.indexFactory.FindIndex(index) if err != nil { - return errors.Wrapf(err, "Appending '%s' manifest to index '%s'", opts.Manifest, opts.Index) + return indexID, fmt.Errorf("Error while trying to find image on local storage: %v", image) } - // Store index in local storage - err = idx.Save() + digest, err := imgIndex.Add(ctx, ref, opts.All) if err != nil { - return errors.Wrapf(err, "Save local index '%s' at '%s' path", opts.Index, opts.Path) + return indexID, fmt.Errorf("Error while trying to add on manifest list: %v", err) } - return nil + if opts.OS != "" { + if _, err := imgIndex.SetOS(digest, opts.OS); err != nil { + return indexID, err + } + } + + if opts.OSArch != "" { + if _, err := imgIndex.SetArchitecture(digest, opts.OSArch); err != nil { + return indexID, err + } + } + + if opts.OSVariant != "" { + if _, err := imgIndex.SetVariant(digest, opts.OSVariant); err != nil { + return indexID, err + } + } + + if opts.OSVersion != "" { + if _, err := imgIndex.SetOSVersion(digest, opts.OSVersion); err != nil { + return indexID, err + } + } + + if len(opts.Features) != 0 { + if _, err := imgIndex.SetFeatures(digest, opts.Features); err != nil { + return indexID, 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 indexID, fmt.Errorf("no value given for annotation %q", spec[0]) + } + annotations[spec[0]] = spec[1] + } + if err := imgIndex.SetAnnotations(&digest, annotations); err != nil { + return err + } + } + + indexID, err = imgIndex.Save(index, nil, "") + if err == nil { + fmt.Printf("%s: %s\n", indexID, digest.String()) + } + + return indexID, err } diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index 79a5ef7ec9..1a5cca6bbf 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -2,40 +2,77 @@ package client import ( "context" + "fmt" + "strings" - "github.com/buildpacks/imgutil/local" - "github.com/pkg/errors" + ggcrName "github.com/google/go-containerregistry/pkg/name" ) -type AnnotateManifestOptions struct { - Index string - Path string - Manifest string - Architecture string - OS string - Variant string +type ManifestAnnotateOptions struct { + OS, OSVersion, OSArch, OSVariant string + OSFeatures, Features []string + Annotations map[string]string } -func (c *Client) AnnotateManifest(ctx context.Context, opts AnnotateManifestOptions) error { - indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) +// AnnotateManifest implements commands.PackClient. +func (c *Client) AnnotateManifest(ctx context.Context, name string, image string, opts ManifestAnnotateOptions) error { + manifestList, err := c.indexFactory.FindIndex(name) if err != nil { - return errors.Wrapf(err, "Get local index manifest '%s' from path '%s'", opts.Index, opts.Path) + return err } - idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) + digest, err := ggcrName.NewDigest(image) if err != nil { - return errors.Wrapf(err, "Create local index from '%s' local index manifest", opts.Index) + return err } - err = idx.AnnotateManifest( - opts.Manifest, - local.AnnotateFields{ - Architecture: opts.Architecture, - OS: opts.OS, - Variant: opts.Variant, - }) - if err != nil { - return errors.Wrapf(err, "Annotate manifet '%s' of index '%s", opts.Manifest, opts.Index) + if opts.OS != "" { + if err := manifestList.SetOS(digest, opts.OS); err != nil { + return err + } + } + if opts.OSVersion != "" { + if err := manifestList.SetOSVersion(digest, opts.OSVersion); err != nil { + return err + } + } + if len(opts.OSFeatures) != 0 { + if err := manifestList.SetOSFeatures(digest, opts.OSFeatures); err != nil { + return err + } + } + if opts.OSArch != "" { + if err := manifestList.SetArchitecture(digest, opts.OSArch); err != nil { + return err + } + } + if opts.OSVariant != "" { + if err := manifestList.SetVariant(digest, opts.OSVariant); err != nil { + return err + } + } + if len(opts.Features) != 0 { + if err := manifestList.SetFeatures(digest, opts.Features); 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 := manifestList.SetAnnotations(&digest, annotations); err != nil { + return err + } + } + + updatedListID, err := manifestList.Save() + if err == nil { + fmt.Printf("%s: %s\n", updatedListID, digest.String()) } return nil diff --git a/pkg/client/client.go b/pkg/client/client.go index 4b7781ad21..0a98d5dfe2 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -16,10 +16,12 @@ package client import ( "context" + "fmt" "os" "path/filepath" "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/layout" "github.com/buildpacks/imgutil/local" "github.com/buildpacks/imgutil/remote" dockerClient "github.com/docker/docker/client" @@ -72,8 +74,14 @@ type ImageFactory interface { NewImage(repoName string, local bool, imageOS string) (imgutil.Image, error) } +// IndexFactory is an interface representing the ability to create a ImageIndex/ManifestList. type IndexFactory interface { - NewIndex(CreateManifestOptions) (imgutil.ImageIndex, error) + // load ManifestList from local storage with the given name + LoadIndex(reponame string, opts ...imgutil.IndexOption) (imgutil.Index, error) + // Fetch ManifestList from Registry with the given name + FetchIndex(name string, opts ...imgutil.IndexOption) (imgutil.Index, error) + // FindIndex will find Index remotly first then on local + FindIndex(name string, opts ...imgutil.IndexOption) (imgutil.Index, error) } //go:generate mockgen -package testmocks -destination ../testmocks/mock_buildpack_downloader.go github.com/buildpacks/pack/pkg/client BuildpackDownloader @@ -122,6 +130,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 { @@ -232,8 +247,7 @@ func NewClient(opts ...Option) (*Client, error) { if client.indexFactory == nil { client.indexFactory = &indexFactory{ - dockerClient: client.docker, - keychain: client.keychain, + keychain: client.keychain, } } @@ -286,21 +300,38 @@ func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (i return remote.NewImage(repoName, f.keychain, remote.WithDefaultPlatform(platform)) } +func (f *indexFactory) LoadIndex(repoName string, opts ...imgutil.IndexOption) (img imgutil.Index, err error) { + img, err = local.NewImage(repoName, true, opts...) + if err == nil { + return + } + + img, err = layout.NewImage(repoName, true, opts...) + if err == nil { + return + } + return nil, errors.Errorf("Image: '%s' not found", repoName) +} + type indexFactory struct { - dockerClient local.DockerClient - keychain authn.Keychain + keychain authn.Keychain +} + +func (f *indexFactory) FetchIndex(name string, opts ...imgutil.IndexOption) (index imgutil.Index, err error) { + + index, err = remote.NewIndex(name, true, opts.WithKeyChain(f.keychain), opts...) + if err != nil { + return index, fmt.Errorf("ImageIndex in not available at registry") + } + + return index, err } -func (f *indexFactory) NewIndex(opts CreateManifestOptions) (imgutil.ImageIndex, error) { - if opts.Publish { - return remote.NewIndex( - opts.ManifestName, - f.keychain, - remote.WithIndexMediaTypes(opts.MediaType)) +func (f *indexFactory) FindIndex(repoName string, opts ...imgutil.IndexOption) (index imgutil.Index, err error) { + index, err = (*f).FetchIndex(repoName, true, opts.WithKeyChain(f.keychain), opts...) + if err != nil { + return index, err } - return local.NewIndex( - opts.ManifestName, - opts.ManifestDir, - local.WithIndexMediaTypes(opts.MediaType)) + return (*f).FindIndex(repoName, true, opts.WithKeyChain(f.keychain), opts...) } diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index f2064ae5ad..27b76135c0 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -2,60 +2,54 @@ package client import ( "context" + "fmt" "github.com/buildpacks/imgutil" - "github.com/pkg/errors" + ggcrName "github.com/google/go-containerregistry/pkg/name" ) type CreateManifestOptions struct { - // Name of the ManifestList. - ManifestName string - - // List of Images - Manifests []string - - // Manifest list type (oci or v2s2) to use when pushing the list (default is v2s2) - MediaType imgutil.MediaTypes - - // Skip creating index locally, directly publish to a registry. - // Requires ManifestName to be a valid registry location. - Publish bool - - // Defines the registry to publish the manifest list. - Registry string - - // Directory to store OCI layout - ManifestDir string + Format, Registry string + Insecure, Publish, all bool } -func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) error { - indexCreator := c.indexFactory - idx, err := indexCreator.NewIndex(opts) +// CreateManifest implements commands.PackClient. +func (c *Client) CreateManifest(ctx context.Context, name string, images []string, opts CreateManifestOptions) (imageID string, err error) { + index, err := c.indexFactory.FindIndex(name, parseOptsToIndexOptions(opts)) if err != nil { - if opts.Publish { - return errors.Wrapf(err, "Failed to create remote index '%s'", opts.ManifestName) - } else { - return errors.Wrapf(err, "Failed to create local index '%s'", opts.ManifestName) - } + return } - // Add every manifest to image index - for _, j := range opts.Manifests { - err := idx.Add(j) + imageID = index.RepoName() + + for _, img := range images { + ref, err := ggcrName.ParseReference(img) if err != nil { - return errors.Wrapf(err, "Appending manifest to index '%s'", opts.ManifestName) + return imageID, err } - } - - // Store index - err = idx.Save() - if err != nil { - if opts.Publish { - return errors.Wrapf(err, "Storing index '%s' in registry.", opts.ManifestName) + if opts.all { + if _, err = index.Add(ref, imgutil.WithAll()); err != nil { + return imageID, err + } } else { - return errors.Wrapf(err, "Save local index '%s' at '%s' path", opts.ManifestName, opts.ManifestDir) + if _, err = index.Add(ref); err != nil { + return imageID, err + } } } - return nil + err = index.Save() + if err == nil { + fmt.Printf("%s\n", imageID) + } + + if opts.Publish { + index.Push() + } + + return imageID, err +} + +func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []imgutil.IndexOption) { + return idxOpts } diff --git a/pkg/client/exists_manifest.go b/pkg/client/exists_manifest.go new file mode 100644 index 0000000000..caa1b7c0ee --- /dev/null +++ b/pkg/client/exists_manifest.go @@ -0,0 +1,15 @@ +package client + +import ( + "context" + + "github.com/pkg/errors" +) + +func (c *Client) ExistsManifest(ctx context.Context, image string) error { + if _, err := c.indexFactory.FindIndex(image); err != nil { + return errors.Errorf("image '%s' is not found", image) + } + + return nil +} diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index 3df389f2ae..cf3d4331cf 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -1,30 +1,63 @@ package client import ( + "bytes" "context" "encoding/json" "fmt" - "github.com/buildpacks/imgutil/local" + ggcrName "github.com/google/go-containerregistry/pkg/name" "github.com/pkg/errors" + + packErrors "github.com/buildpacks/pack/pkg/errors" ) type InspectManifestOptions struct { - Index string - Path string } -func (c *Client) InspectManifest(ctx context.Context, opts InspectManifestOptions) error { - indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) - if err == nil { - data, err := json.MarshalIndent(indexManifest, "", " ") +// InspectManifest implements commands.PackClient. +func (c *Client) InspectManifest(ctx context.Context, name string, opts InspectManifestOptions) error { + printManifest := func(manifest []byte) error { + var b bytes.Buffer + err := json.Indent(&b, manifest, "", " ") if err != nil { - return errors.Wrapf(err, "Marshal the '%s' manifest information", opts.Index) + return fmt.Errorf("rendering manifest for display: %w", err) } - c.logger.Infof("%s\n", string(data)) + fmt.Printf("%s\n", b.String()) return nil } - return fmt.Errorf("index %s not found in local storage", opts.Index) + // Before doing a remote lookup, attempt to resolve the manifest list + // locally. + manifestList, err := c.indexFactory.FindIndex(name) + if err == nil { + 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.ErrIndexUnknown) && !errors.Is(err, packErrors.ErrNotAddManifestList) { + return err + } + + _, err = ggcrName.ParseReference(name) + if err != nil { + fmt.Printf("error parsing reference to image %q: %v", name, err) + } + + index, err := c.indexFactory.FetchIndex(name) + + if err != nil { + return err + } + + return printManifest(index) + } + + return fmt.Errorf("unable to locate manifest list locally or at registry") } diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index ef2cff541e..aae111d939 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -3,32 +3,28 @@ package client import ( "context" - "github.com/buildpacks/imgutil/local" - "github.com/buildpacks/imgutil/remote" - "github.com/pkg/errors" + "github.com/buildpacks/imgutil" ) type PushManifestOptions struct { - Index string - Path string + Format string + Insecure, Purge bool } -func (c *Client) PushManifest(ctx context.Context, opts PushManifestOptions) error { - indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) +// PushManifest implements commands.PackClient. +func (c *Client) PushManifest(ctx context.Context, index string, opts PushManifestOptions) (imageID string, err error) { + manifestList, err := c.indexFactory.FindIndex(index) if err != nil { - return errors.Wrapf(err, "Get local index manifest '%s' from path '%s'", opts.Index, opts.Path) + return } - idx, err := remote.NewIndex(opts.Index, c.keychain, remote.WithManifest(indexManifest)) - if err != nil { - return errors.Wrapf(err, "Create remote index from '%s' local index manifest", opts.Index) - } + _, err = manifestList.Push(ctx, parseFalgsForImgUtil(opts)) - // Store index - err = idx.Save() - if err != nil { - return errors.Wrapf(err, "Storing index '%s' in registry. Check if all the referenced manifests are in the same repository in registry", opts.Index) - } + manifestList.Delete() + + return imageID, err +} - return nil +func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions []imgutil.IndexOption) { + return idxOptions } diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index 6333ab1e79..887eef58ad 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -2,32 +2,17 @@ package client import ( "context" - - "github.com/buildpacks/imgutil/local" - "github.com/pkg/errors" - - "github.com/buildpacks/pack/internal/style" ) -type DeleteManifestOptions struct { - Index string - Path string -} - -func (c *Client) DeleteManifest(ctx context.Context, opts DeleteManifestOptions) error { - indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) - if err != nil { - return errors.Wrapf(err, "Get local index manifest '%s' from path '%s'", opts.Index, opts.Path) - } - - idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) - if err != nil { - return errors.Wrapf(err, "Create local index from '%s' local index manifest", opts.Index) - } +// DeleteManifest implements commands.PackClient. +func (c *Client) DeleteManifest(ctx context.Context, names []string) error { + for _, name := range names { + imgIndex, err := c.indexFactory.FindIndex(name) + if err != nil { + return err + } - err = idx.Delete() - if err != nil { - return errors.Wrapf(err, "Failed to remove index '%s' from local storage\n", style.Symbol(opts.Index)) + imgIndex.Delete() } return nil diff --git a/pkg/client/rm_manifest.go b/pkg/client/rm_manifest.go index 03e2321351..110314d9a6 100644 --- a/pkg/client/rm_manifest.go +++ b/pkg/client/rm_manifest.go @@ -2,38 +2,27 @@ package client import ( "context" + "fmt" - "github.com/buildpacks/imgutil/local" - "github.com/pkg/errors" + gccrName "github.com/google/go-containerregistry/pkg/name" ) -type RemoveManifestOptions struct { - Index string - Path string - Manifest string -} - -func (c *Client) RemoveManifest(ctx context.Context, opts RemoveManifestOptions) error { - indexManifest, err := local.GetIndexManifest(opts.Index, opts.Path) - if err != nil { - return errors.Wrapf(err, "Get local index manifest '%s' from path '%s'", opts.Index, opts.Path) - } - - idx, err := local.NewIndex(opts.Index, opts.Path, local.WithManifest(indexManifest)) +// RemoveManifest implements commands.PackClient. +func (c *Client) RemoveManifest(ctx context.Context, name string, images []string) error { + imgIndex, err := c.indexFactory.FindIndex(name) if err != nil { - return errors.Wrapf(err, "Create local index from '%s' local index manifest", opts.Index) + return err } - // Append manifest to local index - err = idx.Remove(opts.Manifest) - if err != nil { - return errors.Wrapf(err, "Removing '%s' manifest from index '%s'", opts.Manifest, opts.Index) - } - - // Store index in local storage - err = idx.Save() - if err != nil { - return errors.Wrapf(err, "Save local index '%s' at '%s' path", opts.Index, opts.Path) + for _, image := range images { + _, err := gccrName.ParseReference(image) + if err != nil { + fmt.Errorf(`Invalid instance "%s": %v`, image, err) + } + if err := imgIndex.Remove(image); err != nil { + return err + } + 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 0000000000..2d6943cf86 --- /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") +) \ No newline at end of file diff --git a/pkg/testmocks/mock_build_module.go b/pkg/testmocks/mock_build_module.go index 5548ad6f6f..35045ea9f5 100644 --- a/pkg/testmocks/mock_build_module.go +++ b/pkg/testmocks/mock_build_module.go @@ -8,9 +8,8 @@ import ( io "io" reflect "reflect" - gomock "github.com/golang/mock/gomock" - buildpack "github.com/buildpacks/pack/pkg/buildpack" + gomock "github.com/golang/mock/gomock" ) // MockBuildModule is a mock of BuildModule interface. From 8fcb6733e4fe91a97543968e5be1171f933e3448 Mon Sep 17 00:00:00 2001 From: WYGIN Date: Tue, 9 Jan 2024 11:56:18 +0000 Subject: [PATCH 43/79] WIP updated code as per imgutil's ImageIndex PR Signed-off-by: WYGIN --- go.mod | 16 ++++---- go.sum | 38 ++++++++----------- internal/commands/commands.go | 12 +++--- internal/commands/manifest_add.go | 6 +-- internal/commands/manifest_create.go | 4 +- internal/commands/manifest_inspect.go | 3 +- internal/commands/manifest_push.go | 8 ++-- internal/commands/manifest_remove.go | 17 +++++---- internal/commands/manifest_rm.go | 16 ++++---- pkg/client/add_manifest.go | 43 +++++++++++----------- pkg/client/annotate_manifest.go | 8 ++-- pkg/client/client.go | 12 +++--- pkg/client/create_manifest.go | 28 ++++++-------- pkg/client/inspect_manifest.go | 53 ++------------------------- pkg/client/push_manifest.go | 24 ++++++++---- pkg/client/remove_manifest.go | 7 ++-- pkg/client/rm_manifest.go | 16 ++++---- 17 files changed, 136 insertions(+), 175 deletions(-) diff --git a/go.mod b/go.mod index fa43025b88..d80390689a 100644 --- a/go.mod +++ b/go.mod @@ -8,14 +8,14 @@ require ( github.com/buildpacks/imgutil v0.0.0-20231027205711-0eae658d5962 github.com/buildpacks/lifecycle v0.17.1 github.com/docker/cli v24.0.6+incompatible - github.com/docker/docker v24.0.6+incompatible + github.com/docker/docker v24.0.7+incompatible github.com/docker/go-connections v0.4.0 github.com/dustin/go-humanize v1.0.1 github.com/gdamore/tcell/v2 v2.6.0 github.com/ghodss/yaml v1.0.0 github.com/go-git/go-git/v5 v5.9.0 github.com/golang/mock v1.6.0 - github.com/google/go-cmp v0.5.9 + github.com/google/go-cmp v0.6.0 github.com/google/go-containerregistry v0.16.1 github.com/google/go-github/v30 v30.1.0 github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 @@ -30,11 +30,11 @@ require ( github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/sclevine/spec v1.4.0 github.com/spf13/cobra v1.7.0 - golang.org/x/crypto v0.13.0 + golang.org/x/crypto v0.14.0 golang.org/x/mod v0.12.0 golang.org/x/oauth2 v0.12.0 - golang.org/x/sync v0.3.0 - golang.org/x/term v0.12.0 + golang.org/x/sync v0.4.0 + golang.org/x/term v0.13.0 golang.org/x/text v0.13.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -114,8 +114,8 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/vbatts/tar-split v0.11.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/net v0.15.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/tools v0.13.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.31.0 // indirect @@ -126,4 +126,4 @@ require ( go 1.20 -replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20231027205711-0eae658d5962 +replace github.com/buildpacks/imgutil v0.0.0-20231027205711-0eae658d5962 => github.com/WYGIN/buildpacks-imgutil v0.0.0-20231215055622-85fd5dcb42eb diff --git a/go.sum b/go.sum index d935bfa205..bf2fd8f835 100644 --- a/go.sum +++ b/go.sum @@ -39,10 +39,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20230626185301-726f02e4225c h1:KMziTau8mg/nYMh598Lt0IZ97h0nbQqiRz7/jaE3nM8= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20230626185301-726f02e4225c/go.mod h1:mBG5M3GJW5nknCEOOqtmMHyPYnSpw/5GEiciuYU/COw= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20231027205711-0eae658d5962 h1:z6GOX2Wl2+SDLJRret/eAANd5B8tCf35TI4xoopRIbM= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20231027205711-0eae658d5962/go.mod h1:Ade+4Q1OovFw6Zdzd+/UVaqWptZSlpnZ8n/vlkgS7M8= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20231215055622-85fd5dcb42eb h1:8xlS5CkgHjOa6iQv/2Yrs+fLvKqG8LyfVzI2IIVdqks= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20231215055622-85fd5dcb42eb/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= @@ -99,8 +97,6 @@ github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230522190001-adf1bafd791a h1:rW+dV12c0WD3+O4Zs8Qt4+oqnr8ecXeyg8g3yB73ZKA= github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20230522190001-adf1bafd791a/go.mod h1:1mvdZLjy932pV2fhj1jjwUSHaF5Ogq2gk5bvi/6ngEU= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= -github.com/buildpacks/imgutil v0.0.0-20230626185301-726f02e4225c h1:HlRuSz+JGAzudNtNCfHIzXe0AEuHX6Vx8uZgmjvX02o= -github.com/buildpacks/imgutil v0.0.0-20230626185301-726f02e4225c/go.mod h1:mBG5M3GJW5nknCEOOqtmMHyPYnSpw/5GEiciuYU/COw= github.com/buildpacks/lifecycle v0.17.1 h1:sCNj83TH1YE8Z3+CKHoFx/HK+llCVF1RlQUbj3xdNBQ= github.com/buildpacks/lifecycle v0.17.1/go.mod h1:WFzcNp1WG4bwgHuXtKxMg4tdU3AguL44ZlP3knANeVs= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= @@ -128,16 +124,14 @@ github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWT github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= -github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/drac98/imgutil v1.4.0 h1:b8dtuhmfyYrWfjxRyg92MofmVt/bKgq3izY7Sc7NsMY= -github.com/drac98/imgutil v1.4.0/go.mod h1:mBG5M3GJW5nknCEOOqtmMHyPYnSpw/5GEiciuYU/COw= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= @@ -182,8 +176,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ= github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= @@ -345,8 +339,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -368,8 +362,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= @@ -380,8 +374,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -409,8 +403,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -418,8 +412,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 08bfd3933b..2a237b51a7 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -32,14 +32,14 @@ type PackClient interface { InspectExtension(client.InspectExtensionOptions) (*client.ExtensionInfo, error) PullBuildpack(context.Context, client.PullBuildpackOptions) error DownloadSBOM(name string, options client.DownloadSBOMOptions) error - CreateManifest(ctx context.Context, name string, images []string, opts client.CreateManifestOptions) (imageID string, err error) + CreateManifest(ctx context.Context, name string, images []string, opts client.CreateManifestOptions) error AnnotateManifest(ctx context.Context, name string, image string, opts client.ManifestAnnotateOptions) error ExistsManifest(ctx context.Context, image string) error - AddManifest(ctx context.Context, index string, images string, opts client.ManifestAddOptions) (imageID string, err error) - DeleteManifest(ctx context.Context, name []string) error - RemoveManifest(ctx context.Context, name string, images []string) error - PushManifest(ctx context.Context, index string, opts client.PushManifestOptions) (imageID string, err error) - InspectManifest(ctx context.Context, name string, opts client.InspectManifestOptions) error + AddManifest(ctx context.Context, index string, images string, opts client.ManifestAddOptions) error + DeleteManifest(ctx context.Context, name []string) []error + RemoveManifest(ctx context.Context, name string, images []string) []error + PushManifest(ctx context.Context, index string, opts client.PushManifestOptions) error + InspectManifest(ctx context.Context, name string) error } func AddHelpFlag(cmd *cobra.Command, commandName string) { diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 61e0b318b2..b1be695de5 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -44,7 +45,7 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { return err } - imageID, err := pack.AddManifest(cmd.Context(), imageIndex, manifests, client.ManifestAddOptions{ + err = pack.AddManifest(cmd.Context(), imageIndex, manifests, client.ManifestAddOptions{ OS: flags.os, OSVersion: flags.osVersion, OSArch: flags.osArch, @@ -56,10 +57,9 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { }) if err != nil { - return err + return errors.Wrap(err, "unable to add manifest to the index : ") } - logger.Infof(imageID) return nil }), } diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index b42c779618..a06a06073c 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -47,7 +47,7 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { flags.publish = !flags.publish } - id, err := pack.CreateManifest(cmd.Context(), imageIndex, manifests, client.CreateManifestOptions{ + err := pack.CreateManifest(cmd.Context(), imageIndex, manifests, client.CreateManifestOptions{ Format: flags.format, Registry: flags.registry, Insecure: flags.insecure, @@ -57,7 +57,7 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { if err != nil { return err } - logger.Infof("Successfully created ImageIndex/ManifestList with imageID: '%s'", id) + logger.Infof("Successfully created ImageIndex/ManifestList with imageID: '%s'", imageIndex) return nil }), diff --git a/internal/commands/manifest_inspect.go b/internal/commands/manifest_inspect.go index 46559e5f3f..6b2fe36465 100644 --- a/internal/commands/manifest_inspect.go +++ b/internal/commands/manifest_inspect.go @@ -3,7 +3,6 @@ package commands import ( "github.com/spf13/cobra" - "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" ) @@ -23,7 +22,7 @@ func ManifestInspect(logger logging.Logger, pack PackClient) *cobra.Command { Long: `manifest inspect shows the manifest information stored in local storage. The inspect command will help users to view how their local manifest list looks like`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - return pack.InspectManifest(cmd.Context(), args[0], client.InspectManifestOptions{}) + return pack.InspectManifest(cmd.Context(), args[0]) }), } diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index bd00f60092..e1b71c7e84 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -1,6 +1,8 @@ package commands import ( + "fmt" + "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -29,7 +31,7 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { return err } - imageID, err := pack.PushManifest(cmd.Context(), args[0], client.PushManifestOptions{ + err := pack.PushManifest(cmd.Context(), args[0], client.PushManifestOptions{ Format: flags.format, Insecure: flags.insecure, Purge: flags.purge, @@ -38,8 +40,8 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { if err != nil { return err } - - logger.Infof(imageID) + fmt.Sprintf("manifest '%s' is successfully pushed to the registry") + return nil }), } diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index e4998b72d9..f7772b5194 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -1,15 +1,14 @@ package commands import ( + "strings" + + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/logging" ) -// ManifestDeleteFlags define flags provided to the ManifestDelete -// type ManifestDeleteFlags struct { -// } - // ManifestDelete deletes one or more manifest lists from local storage func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { // var flags ManifestDeleteFlags @@ -22,10 +21,14 @@ func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { Long: `Delete one or more manifest lists from local storage. When a manifest list exits locally, users can remove existing images from a manifest list`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - if err := pack.DeleteManifest(cmd.Context(), args); err != nil { - return err + var errMsg strings.Builder + errs := pack.DeleteManifest(cmd.Context(), args) + + for _, err := range errs { + errMsg.WriteString(err.Error() + "\n") } - return nil + + return errors.New(errMsg.String()) }), } diff --git a/internal/commands/manifest_rm.go b/internal/commands/manifest_rm.go index 99a0af17bc..236353b68e 100644 --- a/internal/commands/manifest_rm.go +++ b/internal/commands/manifest_rm.go @@ -1,15 +1,14 @@ package commands import ( + "strings" + + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/logging" ) -// ManifestRemoveFlags define flags provided to the ManifestRemove -// type ManifestRemoveFlags struct { -// } - // ManifestRemove will remove the specified image manifest if it is already referenced in the index func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { // var flags ManifestRemoveFlags @@ -23,10 +22,13 @@ func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { Long: `manifest rm will remove the specified image manifest if it is already referenced in the index. Sometimes users can just experiment with the feature locally and they want to discard all the local information created by pack. 'rm' command just delete the local manifest list`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - if err := pack.RemoveManifest(cmd.Context(), args[0], args[1:]); err != nil { - return err + var errMsg strings.Builder + errs := pack.RemoveManifest(cmd.Context(), args[0], args[1:]) + for _, err := range errs { + errMsg.WriteString(err.Error() + "\n") } - return nil + + return errors.New(errMsg.String()) }), } diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index a628688b6c..161ac18847 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/buildpacks/imgutil" "github.com/google/go-containerregistry/pkg/name" ) @@ -16,9 +17,8 @@ type ManifestAddOptions struct { } // AddManifest implements commands.PackClient. -func (c *Client) AddManifest(ctx context.Context, index string, image string, opts ManifestAddOptions) (indexID string, err error) { - _, err = name.ParseReference(index) - if err != nil { +func (c *Client) AddManifest(ctx context.Context, index string, image string, opts ManifestAddOptions) (err error) { + if _, err = name.ParseReference(index); err != nil { return } @@ -27,43 +27,44 @@ func (c *Client) AddManifest(ctx context.Context, index string, image string, op return } + digest := ref.Context().Digest(ref.Identifier()) imgIndex, err := c.indexFactory.FindIndex(index) if err != nil { - return indexID, fmt.Errorf("Error while trying to find image on local storage: %v", image) + return fmt.Errorf("Error while trying to find image on local storage: %v", image) } - digest, err := imgIndex.Add(ctx, ref, opts.All) + err = imgIndex.Add(ref, imgutil.WithAll(opts.All)) if err != nil { - return indexID, fmt.Errorf("Error while trying to add on manifest list: %v", err) + return fmt.Errorf("Error while trying to add on manifest list: %v", err) } if opts.OS != "" { - if _, err := imgIndex.SetOS(digest, opts.OS); err != nil { - return indexID, err + if err := imgIndex.SetOS(digest, opts.OS); err != nil { + return err } } if opts.OSArch != "" { - if _, err := imgIndex.SetArchitecture(digest, opts.OSArch); err != nil { - return indexID, err + if err := imgIndex.SetArchitecture(digest, opts.OSArch); err != nil { + return err } } if opts.OSVariant != "" { - if _, err := imgIndex.SetVariant(digest, opts.OSVariant); err != nil { - return indexID, err + if err := imgIndex.SetVariant(digest, opts.OSVariant); err != nil { + return err } } if opts.OSVersion != "" { - if _, err := imgIndex.SetOSVersion(digest, opts.OSVersion); err != nil { - return indexID, err + if err := imgIndex.SetOSVersion(digest, opts.OSVersion); err != nil { + return err } } if len(opts.Features) != 0 { - if _, err := imgIndex.SetFeatures(digest, opts.Features); err != nil { - return indexID, err + if err := imgIndex.SetFeatures(digest, opts.Features); err != nil { + return err } } @@ -72,19 +73,19 @@ func (c *Client) AddManifest(ctx context.Context, index string, image string, op for _, annotationSpec := range opts.Annotations { spec := strings.SplitN(annotationSpec, "=", 2) if len(spec) != 2 { - return indexID, fmt.Errorf("no value given for annotation %q", spec[0]) + return fmt.Errorf("no value given for annotation %q", spec[0]) } annotations[spec[0]] = spec[1] } - if err := imgIndex.SetAnnotations(&digest, annotations); err != nil { + if err := imgIndex.SetAnnotations(digest, annotations); err != nil { return err } } - indexID, err = imgIndex.Save(index, nil, "") + err = imgIndex.Save() if err == nil { - fmt.Printf("%s: %s\n", indexID, digest.String()) + fmt.Println("'%s' successfully added to index: '%s'", image, index) } - return indexID, err + return err } diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index 1a5cca6bbf..090aa1dcd8 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -65,15 +65,15 @@ func (c *Client) AnnotateManifest(ctx context.Context, name string, image string } annotations[spec[0]] = spec[1] } - if err := manifestList.SetAnnotations(&digest, annotations); err != nil { + if err := manifestList.SetAnnotations(digest, annotations); err != nil { return err } } - updatedListID, err := manifestList.Save() + err = manifestList.Save() if err == nil { - fmt.Printf("%s: %s\n", updatedListID, digest.String()) + fmt.Printf("%s annotated \n", digest.String()) } - return nil + return err } diff --git a/pkg/client/client.go b/pkg/client/client.go index 0a98d5dfe2..f218c587c3 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -240,7 +240,6 @@ func NewClient(opts ...Option) (*Client, error) { if client.imageFactory == nil { client.imageFactory = &imageFactory{ - dockerClient: client.docker, keychain: client.keychain, } } @@ -301,12 +300,12 @@ func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (i } func (f *indexFactory) LoadIndex(repoName string, opts ...imgutil.IndexOption) (img imgutil.Index, err error) { - img, err = local.NewImage(repoName, true, opts...) + img, err = local.NewIndex(repoName, true, opts...) if err == nil { return } - img, err = layout.NewImage(repoName, true, opts...) + img, err = layout.NewIndex(repoName, true, opts...) if err == nil { return } @@ -318,8 +317,7 @@ type indexFactory struct { } func (f *indexFactory) FetchIndex(name string, opts ...imgutil.IndexOption) (index imgutil.Index, err error) { - - index, err = remote.NewIndex(name, true, opts.WithKeyChain(f.keychain), opts...) + index, err = remote.NewIndex(name, true, imgutil.WithKeyChain(f.keychain), opts...) if err != nil { return index, fmt.Errorf("ImageIndex in not available at registry") } @@ -328,10 +326,10 @@ func (f *indexFactory) FetchIndex(name string, opts ...imgutil.IndexOption) (ind } func (f *indexFactory) FindIndex(repoName string, opts ...imgutil.IndexOption) (index imgutil.Index, err error) { - index, err = (*f).FetchIndex(repoName, true, opts.WithKeyChain(f.keychain), opts...) + index, err = (*f).FetchIndex(repoName, true, imgutil.WithKeyChain(f.keychain), opts...) if err != nil { return index, err } - return (*f).FindIndex(repoName, true, opts.WithKeyChain(f.keychain), opts...) + return (*f).FindIndex(repoName, true, imgutil.WithKeyChain(f.keychain), opts...) } diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 27b76135c0..8314afa4bd 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -14,42 +14,38 @@ type CreateManifestOptions struct { } // CreateManifest implements commands.PackClient. -func (c *Client) CreateManifest(ctx context.Context, name string, images []string, opts CreateManifestOptions) (imageID string, err error) { - index, err := c.indexFactory.FindIndex(name, parseOptsToIndexOptions(opts)) +func (c *Client) CreateManifest(ctx context.Context, name string, images []string, opts CreateManifestOptions) (err error) { + index, err := c.indexFactory.FindIndex(name, parseOptsToIndexOptions(opts)...) if err != nil { return } - imageID = index.RepoName() - for _, img := range images { ref, err := ggcrName.ParseReference(img) if err != nil { - return imageID, err + return err } - if opts.all { - if _, err = index.Add(ref, imgutil.WithAll()); err != nil { - return imageID, err - } - } else { - if _, err = index.Add(ref); err != nil { - return imageID, err - } + if err = index.Add(ref, imgutil.WithAll(opts.all)); err != nil { + return err } } err = index.Save() if err == nil { - fmt.Printf("%s\n", imageID) + fmt.Println("%s successfully created", name) } if opts.Publish { index.Push() } - return imageID, err + return err } func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []imgutil.IndexOption) { - return idxOpts + return []imgutil.IndexOption{ + imgutil.WithFormat(opts.Format), + imgutil.AddRegistry(opts.Registry), + imgutil.WithInsecure(opts.Insecure), + } } diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index cf3d4331cf..74088a6b16 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -1,15 +1,7 @@ package client import ( - "bytes" "context" - "encoding/json" - "fmt" - - ggcrName "github.com/google/go-containerregistry/pkg/name" - "github.com/pkg/errors" - - packErrors "github.com/buildpacks/pack/pkg/errors" ) type InspectManifestOptions struct { @@ -17,47 +9,10 @@ type InspectManifestOptions struct { // InspectManifest implements commands.PackClient. func (c *Client) InspectManifest(ctx context.Context, name string, opts InspectManifestOptions) 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) - } - - fmt.Printf("%s\n", b.String()) - return nil - } - - // Before doing a remote lookup, attempt to resolve the manifest list - // locally. - manifestList, err := c.indexFactory.FindIndex(name) - if err == nil { - 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.ErrIndexUnknown) && !errors.Is(err, packErrors.ErrNotAddManifestList) { - return err - } - - _, err = ggcrName.ParseReference(name) - if err != nil { - fmt.Printf("error parsing reference to image %q: %v", name, err) - } - - index, err := c.indexFactory.FetchIndex(name) - - if err != nil { - return err - } - - return printManifest(index) + idx, err := c.indexFactory.FindIndex(name) + if err != nil { + return err } - return fmt.Errorf("unable to locate manifest list locally or at registry") + return idx.Inspect() } diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index aae111d939..6778344a56 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -12,19 +12,29 @@ type PushManifestOptions struct { } // PushManifest implements commands.PackClient. -func (c *Client) PushManifest(ctx context.Context, index string, opts PushManifestOptions) (imageID string, err error) { - manifestList, err := c.indexFactory.FindIndex(index) +func (c *Client) PushManifest(ctx context.Context, index string, opts PushManifestOptions) (err error) { + idx, err := c.indexFactory.LoadIndex(index) if err != nil { - return + return } - _, err = manifestList.Push(ctx, parseFalgsForImgUtil(opts)) + err = idx.Push() + if err != nil { + return + } - manifestList.Delete() + if opts.Purge { + if err = idx.Delete(); err != nil { + return + } + } - return imageID, err + return } func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions []imgutil.IndexOption) { - return idxOptions + return []imgutil.IndexOption{ + imgutil.WithFormat(opts.Format), + imgutil.WithInsecure(opts.Insecure), + } } diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index 887eef58ad..6a20b87aaf 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -5,15 +5,16 @@ import ( ) // DeleteManifest implements commands.PackClient. -func (c *Client) DeleteManifest(ctx context.Context, names []string) error { +func (c *Client) DeleteManifest(ctx context.Context, names []string) []error { + var errs []error for _, name := range names { imgIndex, err := c.indexFactory.FindIndex(name) if err != nil { - return err + errs = append(errs, err) } imgIndex.Delete() } - return nil + return errs } diff --git a/pkg/client/rm_manifest.go b/pkg/client/rm_manifest.go index 110314d9a6..7782c0d5d5 100644 --- a/pkg/client/rm_manifest.go +++ b/pkg/client/rm_manifest.go @@ -8,22 +8,22 @@ import ( ) // RemoveManifest implements commands.PackClient. -func (c *Client) RemoveManifest(ctx context.Context, name string, images []string) error { - imgIndex, err := c.indexFactory.FindIndex(name) +func (c *Client) RemoveManifest(ctx context.Context, name string, images []string) (errs []error) { + imgIndex, err := c.indexFactory.LoadIndex(name) if err != nil { - return err + return append(errs, err) } for _, image := range images { - _, err := gccrName.ParseReference(image) + ref, err := gccrName.ParseReference(image) if err != nil { - fmt.Errorf(`Invalid instance "%s": %v`, image, err) + errs = append(errs, fmt.Errorf(`Invalid instance "%s": %v`, image, err)) } - if err := imgIndex.Remove(image); err != nil { - return err + if err := imgIndex.Remove(ref.Context().Digest(ref.Identifier())); err != nil { + errs = append(errs, err) } fmt.Printf("Successfully removed %s from %s", image, name) } - return nil + return errs } From 9c52f2d8361f339948e2e7e6994148d5d96226ac Mon Sep 17 00:00:00 2001 From: WYGIN Date: Mon, 22 Jan 2024 08:55:55 +0000 Subject: [PATCH 44/79] WIP upgraded imgutil to latest version Signed-off-by: WYGIN --- .gitpod.yml | 18 ------ go.mod | 2 +- go.sum | 4 +- internal/builder/testmocks/mock_lifecycle.go | 3 +- internal/commands/manifest_add.go | 46 +++++++------- internal/commands/manifest_annotate.go | 18 +++--- internal/commands/manifest_create.go | 2 +- internal/commands/manifest_exists.go | 2 +- internal/commands/manifest_inspect.go | 2 +- internal/commands/manifest_push.go | 6 +- internal/commands/manifest_remove.go | 2 +- .../commands/testmocks/mock_pack_client.go | 62 ++++++++++++------- pkg/client/add_manifest.go | 10 +-- pkg/client/annotate_manifest.go | 6 +- pkg/client/build_test.go | 11 ---- pkg/client/client.go | 47 +++++++++----- pkg/client/create_manifest.go | 33 +++++++--- pkg/client/docker.go | 1 + pkg/client/inspect_manifest.go | 2 +- pkg/client/push_manifest.go | 19 ++++-- pkg/client/rm_manifest.go | 2 +- pkg/errors/errors.go | 2 +- pkg/testmocks/mock_build_module.go | 3 +- 23 files changed, 164 insertions(+), 139 deletions(-) delete mode 100644 .gitpod.yml diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index b716e56cd5..0000000000 --- a/.gitpod.yml +++ /dev/null @@ -1,18 +0,0 @@ - -tasks: - - name: Setup - before: chmod ugo+w /var/run/docker.sock - init: make build - command: chmod ugo+w /var/run/docker.sock - -github: - prebuilds: - master: true - branches: true - pullRequests: true - pullRequestsFromForks: true - addCheck: true - -vscode: - extensions: - - golang.go diff --git a/go.mod b/go.mod index d80390689a..c1e2424865 100644 --- a/go.mod +++ b/go.mod @@ -126,4 +126,4 @@ require ( go 1.20 -replace github.com/buildpacks/imgutil v0.0.0-20231027205711-0eae658d5962 => github.com/WYGIN/buildpacks-imgutil v0.0.0-20231215055622-85fd5dcb42eb +replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122051427-9ec2c8cb243a diff --git a/go.sum b/go.sum index bf2fd8f835..7da095fc5e 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20231215055622-85fd5dcb42eb h1:8xlS5CkgHjOa6iQv/2Yrs+fLvKqG8LyfVzI2IIVdqks= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20231215055622-85fd5dcb42eb/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122051427-9ec2c8cb243a h1:/qQauWgWudbUKjdD/xTEMUSG2d9CgtxZJMv5JqxRE50= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122051427-9ec2c8cb243a/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= diff --git a/internal/builder/testmocks/mock_lifecycle.go b/internal/builder/testmocks/mock_lifecycle.go index fabeab8401..88aacd786f 100644 --- a/internal/builder/testmocks/mock_lifecycle.go +++ b/internal/builder/testmocks/mock_lifecycle.go @@ -8,8 +8,9 @@ import ( io "io" reflect "reflect" - builder "github.com/buildpacks/pack/internal/builder" gomock "github.com/golang/mock/gomock" + + builder "github.com/buildpacks/pack/internal/builder" ) // MockLifecycle is a mock of Lifecycle interface. diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index b1be695de5..462cc42dab 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -15,7 +15,7 @@ import ( type ManifestAddFlags struct { os, osVersion, osArch, osVariant string osFeatures, annotations, features string - all bool + all bool } // ManifestAdd modifies a manifest list (Image index) and add a new image to the list of manifests. @@ -23,7 +23,7 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestAddFlags cmd := &cobra.Command{ - Use: "pack manifest add [OPTIONS] [flags]", + Use: "add [OPTIONS] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), Short: "manifest add modifies a manifest list (Image index) and add a new image to the list of manifests.", Example: `pack manifest add cnbs/sample-package:hello-multiarch-universe \ @@ -38,22 +38,22 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { return err } - osFeatures:= strings.Split(flags.osFeatures, ";") - features:= strings.Split(flags.features, ";") + osFeatures := strings.Split(flags.osFeatures, ";") + features := strings.Split(flags.features, ";") annotations, err := StringToKeyValueMap(flags.annotations) if err != nil { return err } err = pack.AddManifest(cmd.Context(), imageIndex, manifests, client.ManifestAddOptions{ - OS: flags.os, - OSVersion: flags.osVersion, - OSArch: flags.osArch, - OSVariant: flags.osVariant, - OSFeatures: osFeatures, - Features: features, + OS: flags.os, + OSVersion: flags.osVersion, + OSArch: flags.osArch, + OSVariant: flags.osVariant, + OSFeatures: osFeatures, + Features: features, Annotations: annotations, - All: flags.all, + All: flags.all, }) if err != nil { @@ -82,20 +82,20 @@ func validateManifestAddFlags(flags ManifestAddFlags) error { func StringToKeyValueMap(s string) (map[string]string, error) { keyValues := strings.Split(s, ";") - + m := map[string]string{} - + for _, keyValue := range keyValues { - parts := strings.Split(keyValue, "=") - if len(parts) != 2 { - return nil, fmt.Errorf("invalid key-value pair: %s", keyValue) - } - - key := parts[0] - value := parts[1] - - m[key] = value + parts := strings.Split(keyValue, "=") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid key-value pair: %s", keyValue) + } + + key := parts[0] + value := parts[1] + + m[key] = value } - + return m, nil } diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index a208fc0ff8..17250c19e9 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -20,7 +20,7 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestAnnotateFlags cmd := &cobra.Command{ - Use: "pack manifest annotate [OPTIONS] [...] [flags]", + Use: "annotate [OPTIONS] [...] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), Short: "manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.", Example: `pack manifest annotate cnbs/sample-package:hello-universe-multiarch \ @@ -28,20 +28,20 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { Long: `manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list. Sometimes a manifest list could reference an image that doesn't specify the architecture, The "annotate" command allows users to update those values before pushing the manifest list a registry`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - osFeatures:= strings.Split(flags.osFeatures, ";") - features:= strings.Split(flags.features, ";") + osFeatures := strings.Split(flags.osFeatures, ";") + features := strings.Split(flags.features, ";") annotations, err := StringToKeyValueMap(flags.annotations) if err != nil { return err } pack.AnnotateManifest(cmd.Context(), args[0], args[1], client.ManifestAnnotateOptions{ - OS: flags.os, - OSVersion: flags.osVersion, - OSArch: flags.arch, - OSVariant: flags.variant, - OSFeatures: osFeatures, - Features: features, + OS: flags.os, + OSVersion: flags.osVersion, + OSArch: flags.arch, + OSVariant: flags.variant, + OSFeatures: osFeatures, + Features: features, Annotations: annotations, }) return nil diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index a06a06073c..6eb95cfb2a 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -20,7 +20,7 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestCreateFlags cmd := &cobra.Command{ - Use: "pack manifest create [ ... ] [flags]", + Use: "create [ ... ] [flags]", Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), Short: "manifest create generates a manifest list for a multi-arch image", Example: `pack manifest create cnbs/sample-package:hello-multiarch-universe \ diff --git a/internal/commands/manifest_exists.go b/internal/commands/manifest_exists.go index 5cf9b9462e..d9dcc8b47c 100644 --- a/internal/commands/manifest_exists.go +++ b/internal/commands/manifest_exists.go @@ -15,7 +15,7 @@ func ManifestExists(logger logging.Logger, pack PackClient) *cobra.Command { // var flags ManifestDeleteFlags cmd := &cobra.Command{ - Use: "pack manifest exists [manifest-list]", + Use: "exists [manifest-list]", Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Short: "checks if a manifest list exists in local storage", Example: `pack manifest exists cnbs/sample-package:hello-multiarch-universe`, diff --git a/internal/commands/manifest_inspect.go b/internal/commands/manifest_inspect.go index 6b2fe36465..540294de3b 100644 --- a/internal/commands/manifest_inspect.go +++ b/internal/commands/manifest_inspect.go @@ -15,7 +15,7 @@ func ManifestInspect(logger logging.Logger, pack PackClient) *cobra.Command { // var flags ManifestInspectFlags cmd := &cobra.Command{ - Use: "pack manifest inspect [flags]", + Use: "inspect [flags]", Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Short: "manifest inspect shows the manifest information stored in local storage", Example: `pack manifest inspect cnbs/sample-builder:multiarch`, diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index e1b71c7e84..ede3444572 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -20,7 +20,7 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestPushFlags cmd := &cobra.Command{ - Use: "pack manifest push [OPTIONS] [flags]", + Use: "push [OPTIONS] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Short: "manifest push pushes a manifest list (Image index) to a registry.", Example: `pack manifest push cnbs/sample-package:hello-multiarch-universe`, @@ -40,8 +40,8 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { if err != nil { return err } - fmt.Sprintf("manifest '%s' is successfully pushed to the registry") - + + fmt.Printf("manifest '%s' is successfully pushed to the registry", args[0]) return nil }), } diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index f7772b5194..8c5e80e3ed 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -14,7 +14,7 @@ func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { // var flags ManifestDeleteFlags cmd := &cobra.Command{ - Use: "pack manifest remove [manifest-list] [manifest-list...] [flags]", + Use: "remove [manifest-list] [manifest-list...] [flags]", Args: cobra.MatchAll(cobra.MinimumNArgs(1), cobra.OnlyValidArgs), Short: "Delete one or more manifest lists from local storage", Example: `pack manifest remove cnbs/sample-package:hello-multiarch-universe`, diff --git a/internal/commands/testmocks/mock_pack_client.go b/internal/commands/testmocks/mock_pack_client.go index 66172c48c1..4bdf29af24 100644 --- a/internal/commands/testmocks/mock_pack_client.go +++ b/internal/commands/testmocks/mock_pack_client.go @@ -37,31 +37,31 @@ func (m *MockPackClient) EXPECT() *MockPackClientMockRecorder { } // AddManifest mocks base method. -func (m *MockPackClient) AddManifest(arg0 context.Context, arg1 client.AddManifestOptions) error { +func (m *MockPackClient) AddManifest(arg0 context.Context, arg1, arg2 string, arg3 client.ManifestAddOptions) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddManifest", arg0, arg1) + ret := m.ctrl.Call(m, "AddManifest", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // AddManifest indicates an expected call of AddManifest. -func (mr *MockPackClientMockRecorder) AddManifest(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) AddManifest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddManifest", reflect.TypeOf((*MockPackClient)(nil).AddManifest), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddManifest", reflect.TypeOf((*MockPackClient)(nil).AddManifest), arg0, arg1, arg2, arg3) } // AnnotateManifest mocks base method. -func (m *MockPackClient) AnnotateManifest(arg0 context.Context, arg1 client.AnnotateManifestOptions) error { +func (m *MockPackClient) AnnotateManifest(arg0 context.Context, arg1, arg2 string, arg3 client.ManifestAnnotateOptions) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AnnotateManifest", arg0, arg1) + ret := m.ctrl.Call(m, "AnnotateManifest", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // AnnotateManifest indicates an expected call of AnnotateManifest. -func (mr *MockPackClientMockRecorder) AnnotateManifest(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) AnnotateManifest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnnotateManifest", reflect.TypeOf((*MockPackClient)(nil).AnnotateManifest), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnnotateManifest", reflect.TypeOf((*MockPackClient)(nil).AnnotateManifest), arg0, arg1, arg2, arg3) } // Build mocks base method. @@ -93,24 +93,24 @@ func (mr *MockPackClientMockRecorder) CreateBuilder(arg0, arg1 interface{}) *gom } // CreateManifest mocks base method. -func (m *MockPackClient) CreateManifest(arg0 context.Context, arg1 client.CreateManifestOptions) error { +func (m *MockPackClient) CreateManifest(arg0 context.Context, arg1 string, arg2 []string, arg3 client.CreateManifestOptions) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateManifest", arg0, arg1) + ret := m.ctrl.Call(m, "CreateManifest", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(error) return ret0 } // CreateManifest indicates an expected call of CreateManifest. -func (mr *MockPackClientMockRecorder) CreateManifest(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) CreateManifest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateManifest", reflect.TypeOf((*MockPackClient)(nil).CreateManifest), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateManifest", reflect.TypeOf((*MockPackClient)(nil).CreateManifest), arg0, arg1, arg2, arg3) } // DeleteManifest mocks base method. -func (m *MockPackClient) DeleteManifest(arg0 context.Context, arg1 client.DeleteManifestOptions) error { +func (m *MockPackClient) DeleteManifest(arg0 context.Context, arg1 []string) []error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DeleteManifest", arg0, arg1) - ret0, _ := ret[0].(error) + ret0, _ := ret[0].([]error) return ret0 } @@ -134,6 +134,20 @@ func (mr *MockPackClientMockRecorder) DownloadSBOM(arg0, arg1 interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadSBOM", reflect.TypeOf((*MockPackClient)(nil).DownloadSBOM), arg0, arg1) } +// ExistsManifest mocks base method. +func (m *MockPackClient) ExistsManifest(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExistsManifest", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ExistsManifest indicates an expected call of ExistsManifest. +func (mr *MockPackClientMockRecorder) ExistsManifest(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExistsManifest", reflect.TypeOf((*MockPackClient)(nil).ExistsManifest), arg0, arg1) +} + // InspectBuilder mocks base method. func (m *MockPackClient) InspectBuilder(arg0 string, arg1 bool, arg2 ...client.BuilderInspectionModifier) (*client.BuilderInfo, error) { m.ctrl.T.Helper() @@ -200,7 +214,7 @@ func (mr *MockPackClientMockRecorder) InspectImage(arg0, arg1 interface{}) *gomo } // InspectManifest mocks base method. -func (m *MockPackClient) InspectManifest(arg0 context.Context, arg1 client.InspectManifestOptions) error { +func (m *MockPackClient) InspectManifest(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "InspectManifest", arg0, arg1) ret0, _ := ret[0].(error) @@ -270,17 +284,17 @@ func (mr *MockPackClientMockRecorder) PullBuildpack(arg0, arg1 interface{}) *gom } // PushManifest mocks base method. -func (m *MockPackClient) PushManifest(arg0 context.Context, arg1 client.PushManifestOptions) error { +func (m *MockPackClient) PushManifest(arg0 context.Context, arg1 string, arg2 client.PushManifestOptions) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PushManifest", arg0, arg1) + ret := m.ctrl.Call(m, "PushManifest", arg0, arg1, arg2) ret0, _ := ret[0].(error) return ret0 } // PushManifest indicates an expected call of PushManifest. -func (mr *MockPackClientMockRecorder) PushManifest(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) PushManifest(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushManifest", reflect.TypeOf((*MockPackClient)(nil).PushManifest), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushManifest", reflect.TypeOf((*MockPackClient)(nil).PushManifest), arg0, arg1, arg2) } // Rebase mocks base method. @@ -312,17 +326,17 @@ func (mr *MockPackClientMockRecorder) RegisterBuildpack(arg0, arg1 interface{}) } // RemoveManifest mocks base method. -func (m *MockPackClient) RemoveManifest(arg0 context.Context, arg1 client.RemoveManifestOptions) error { +func (m *MockPackClient) RemoveManifest(arg0 context.Context, arg1 string, arg2 []string) []error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoveManifest", arg0, arg1) - ret0, _ := ret[0].(error) + ret := m.ctrl.Call(m, "RemoveManifest", arg0, arg1, arg2) + ret0, _ := ret[0].([]error) return ret0 } // RemoveManifest indicates an expected call of RemoveManifest. -func (mr *MockPackClientMockRecorder) RemoveManifest(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) RemoveManifest(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveManifest", reflect.TypeOf((*MockPackClient)(nil).RemoveManifest), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveManifest", reflect.TypeOf((*MockPackClient)(nil).RemoveManifest), arg0, arg1, arg2) } // YankBuildpack mocks base method. diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 161ac18847..c1a9d8460f 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -10,10 +10,10 @@ import ( ) type ManifestAddOptions struct { - OS, OSVersion, OSArch, OSVariant string - OSFeatures, Features []string - Annotations map[string]string - All bool + OS, OSVersion, OSArch, OSVariant string + OSFeatures, Features []string + Annotations map[string]string + All bool } // AddManifest implements commands.PackClient. @@ -84,7 +84,7 @@ func (c *Client) AddManifest(ctx context.Context, index string, image string, op err = imgIndex.Save() if err == nil { - fmt.Println("'%s' successfully added to index: '%s'", image, index) + fmt.Printf("'%s' successfully added to index: '%s'", image, index) } return err diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index 090aa1dcd8..1c13452081 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -9,9 +9,9 @@ import ( ) type ManifestAnnotateOptions struct { - OS, OSVersion, OSArch, OSVariant string - OSFeatures, Features []string - Annotations map[string]string + OS, OSVersion, OSArch, OSVariant string + OSFeatures, Features []string + Annotations map[string]string } // AnnotateManifest implements commands.PackClient. diff --git a/pkg/client/build_test.go b/pkg/client/build_test.go index 06b215104b..8de2449a00 100644 --- a/pkg/client/build_test.go +++ b/pkg/client/build_test.go @@ -26,11 +26,9 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/heroku/color" "github.com/onsi/gomega/ghttp" - "github.com/pkg/errors" "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/pack/internal/build" "github.com/buildpacks/pack/internal/builder" cfg "github.com/buildpacks/pack/internal/config" ifakes "github.com/buildpacks/pack/internal/fakes" @@ -3326,12 +3324,3 @@ func setAPIs(t *testing.T, image *fakes.Image, buildpackAPIs []string, platformA h.AssertNil(t, err) h.AssertNil(t, image.SetLabel(builderMDLabelName, string(builderMDLabelBytes))) } - -type executeFailsLifecycle struct { //nolint - Opts build.LifecycleOptions -} - -func (f *executeFailsLifecycle) Execute(_ context.Context, opts build.LifecycleOptions) error { //nolint - f.Opts = opts - return errors.New("") -} diff --git a/pkg/client/client.go b/pkg/client/client.go index f218c587c3..5ccfb71050 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -21,6 +21,7 @@ import ( "path/filepath" "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/index" "github.com/buildpacks/imgutil/layout" "github.com/buildpacks/imgutil/local" "github.com/buildpacks/imgutil/remote" @@ -76,12 +77,14 @@ type ImageFactory interface { // IndexFactory is an interface representing the ability to create a ImageIndex/ManifestList. type IndexFactory interface { + // create ManifestList locally + CreateIndex(repoName string, opts ...index.Option) (imgutil.ImageIndex, error) // load ManifestList from local storage with the given name - LoadIndex(reponame string, opts ...imgutil.IndexOption) (imgutil.Index, error) + LoadIndex(reponame string, opts ...index.Option) (imgutil.ImageIndex, error) // Fetch ManifestList from Registry with the given name - FetchIndex(name string, opts ...imgutil.IndexOption) (imgutil.Index, error) + FetchIndex(name string, opts ...index.Option) (imgutil.ImageIndex, error) // FindIndex will find Index remotly first then on local - FindIndex(name string, opts ...imgutil.IndexOption) (imgutil.Index, error) + FindIndex(name string, opts ...index.Option) (imgutil.ImageIndex, error) } //go:generate mockgen -package testmocks -destination ../testmocks/mock_buildpack_downloader.go github.com/buildpacks/pack/pkg/client BuildpackDownloader @@ -240,13 +243,14 @@ func NewClient(opts ...Option) (*Client, error) { if client.imageFactory == nil { client.imageFactory = &imageFactory{ - keychain: client.keychain, + keychain: client.keychain, } } if client.indexFactory == nil { client.indexFactory = &indexFactory{ - keychain: client.keychain, + keychain: client.keychain, + xdgRuntimePath: os.Getenv("XDG_RUNTIME_DIR"), } } @@ -299,13 +303,14 @@ func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (i return remote.NewImage(repoName, f.keychain, remote.WithDefaultPlatform(platform)) } -func (f *indexFactory) LoadIndex(repoName string, opts ...imgutil.IndexOption) (img imgutil.Index, err error) { - img, err = local.NewIndex(repoName, true, opts...) +func (f *indexFactory) LoadIndex(repoName string, opts ...index.Option) (img imgutil.ImageIndex, err error) { + opts = append(opts, index.WithKeychain(f.keychain)) + img, err = local.NewIndex(repoName, opts...) if err == nil { return } - img, err = layout.NewIndex(repoName, true, opts...) + img, err = layout.NewIndex(repoName, opts...) if err == nil { return } @@ -313,23 +318,31 @@ func (f *indexFactory) LoadIndex(repoName string, opts ...imgutil.IndexOption) ( } type indexFactory struct { - keychain authn.Keychain + keychain authn.Keychain + xdgRuntimePath string } -func (f *indexFactory) FetchIndex(name string, opts ...imgutil.IndexOption) (index imgutil.Index, err error) { - index, err = remote.NewIndex(name, true, imgutil.WithKeyChain(f.keychain), opts...) +func (f *indexFactory) FetchIndex(name string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { + opts = append(opts, index.WithKeychain(f.keychain)) + idx, err = remote.NewIndex(name, opts...) if err != nil { - return index, fmt.Errorf("ImageIndex in not available at registry") + return idx, fmt.Errorf("ImageIndex in not available at registry") } - return index, err + return idx, err } -func (f *indexFactory) FindIndex(repoName string, opts ...imgutil.IndexOption) (index imgutil.Index, err error) { - index, err = (*f).FetchIndex(repoName, true, imgutil.WithKeyChain(f.keychain), opts...) +func (f *indexFactory) FindIndex(repoName string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { + opts = append(opts, index.WithKeychain(f.keychain)) + idx, err = f.FetchIndex(repoName, opts...) if err != nil { - return index, err + return idx, err } - return (*f).FindIndex(repoName, true, imgutil.WithKeyChain(f.keychain), opts...) + return f.FindIndex(repoName, opts...) +} + +func (f *indexFactory) CreateIndex(repoName string, opts ...index.Option) (imgutil.ImageIndex, error) { + opts = append(opts, index.WithKeychain(f.keychain)) + return index.NewIndex(repoName, opts...) } diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 8314afa4bd..eef920dd93 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -5,17 +5,19 @@ import ( "fmt" "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/index" ggcrName "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/types" ) type CreateManifestOptions struct { - Format, Registry string + Format, Registry string Insecure, Publish, all bool } // CreateManifest implements commands.PackClient. func (c *Client) CreateManifest(ctx context.Context, name string, images []string, opts CreateManifestOptions) (err error) { - index, err := c.indexFactory.FindIndex(name, parseOptsToIndexOptions(opts)...) + index, err := c.indexFactory.CreateIndex(name, parseOptsToIndexOptions(opts)...) if err != nil { return } @@ -32,20 +34,33 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin err = index.Save() if err == nil { - fmt.Println("%s successfully created", name) + fmt.Printf("%s successfully created", name) } if opts.Publish { - index.Push() + var format types.MediaType + switch opts.Format { + case "oci": + format = types.OCIImageIndex + default: + format = types.DockerManifestList + } + index.Push(imgutil.WithInsecure(opts.Insecure), imgutil.WithFormat(format)) } return err } -func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []imgutil.IndexOption) { - return []imgutil.IndexOption{ - imgutil.WithFormat(opts.Format), - imgutil.AddRegistry(opts.Registry), - imgutil.WithInsecure(opts.Insecure), +func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []index.Option) { + var format types.MediaType + switch opts.Format { + case "oci": + format = types.OCIImageIndex + default: + format = types.DockerManifestList + } + return []index.Option{ + index.WithFormat(format), + index.WithInsecure(opts.Insecure), } } diff --git a/pkg/client/docker.go b/pkg/client/docker.go index f637066e11..e74ae7f832 100644 --- a/pkg/client/docker.go +++ b/pkg/client/docker.go @@ -30,4 +30,5 @@ type DockerClient interface { ContainerWait(ctx context.Context, container string, condition containertypes.WaitCondition) (<-chan containertypes.WaitResponse, <-chan error) ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error + ServerVersion(ctx context.Context) (types.Version, error) } diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index 74088a6b16..ab88a958c5 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -8,7 +8,7 @@ type InspectManifestOptions struct { } // InspectManifest implements commands.PackClient. -func (c *Client) InspectManifest(ctx context.Context, name string, opts InspectManifestOptions) error { +func (c *Client) InspectManifest(ctx context.Context, name string) error { idx, err := c.indexFactory.FindIndex(name) if err != nil { return err diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index 6778344a56..411f42af5e 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -4,6 +4,7 @@ import ( "context" "github.com/buildpacks/imgutil" + "github.com/google/go-containerregistry/pkg/v1/types" ) type PushManifestOptions struct { @@ -15,10 +16,10 @@ type PushManifestOptions struct { func (c *Client) PushManifest(ctx context.Context, index string, opts PushManifestOptions) (err error) { idx, err := c.indexFactory.LoadIndex(index) if err != nil { - return + return } - err = idx.Push() + err = idx.Push(parseFalgsForImgUtil(opts)...) if err != nil { return } @@ -32,9 +33,17 @@ func (c *Client) PushManifest(ctx context.Context, index string, opts PushManife return } -func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions []imgutil.IndexOption) { - return []imgutil.IndexOption{ - imgutil.WithFormat(opts.Format), +func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions []imgutil.IndexPushOption) { + var format types.MediaType + switch opts.Format { + case "oci": + format = types.OCIImageIndex + default: + format = types.DockerManifestList + } + + return []imgutil.IndexPushOption{ + imgutil.WithFormat(format), imgutil.WithInsecure(opts.Insecure), } } diff --git a/pkg/client/rm_manifest.go b/pkg/client/rm_manifest.go index 7782c0d5d5..35ebb92c25 100644 --- a/pkg/client/rm_manifest.go +++ b/pkg/client/rm_manifest.go @@ -17,7 +17,7 @@ func (c *Client) RemoveManifest(ctx context.Context, name string, images []strin for _, image := range images { ref, err := gccrName.ParseReference(image) if err != nil { - errs = append(errs, fmt.Errorf(`Invalid instance "%s": %v`, image, err)) + errs = append(errs, fmt.Errorf(`invalid instance "%s": %v`, image, err)) } if err := imgIndex.Remove(ref.Context().Digest(ref.Identifier())); err != nil { errs = append(errs, err) diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 2d6943cf86..669d3e8ab7 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -6,4 +6,4 @@ 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") -) \ No newline at end of file +) diff --git a/pkg/testmocks/mock_build_module.go b/pkg/testmocks/mock_build_module.go index 35045ea9f5..5548ad6f6f 100644 --- a/pkg/testmocks/mock_build_module.go +++ b/pkg/testmocks/mock_build_module.go @@ -8,8 +8,9 @@ import ( io "io" reflect "reflect" - buildpack "github.com/buildpacks/pack/pkg/buildpack" gomock "github.com/golang/mock/gomock" + + buildpack "github.com/buildpacks/pack/pkg/buildpack" ) // MockBuildModule is a mock of BuildModule interface. From 13ee7f05720d979c7929daefc70606737091de0d Mon Sep 17 00:00:00 2001 From: WYGIN Date: Mon, 22 Jan 2024 11:52:04 +0000 Subject: [PATCH 45/79] WIP fix some bugs of pack manifest cli Signed-off-by: WYGIN --- go.mod | 2 +- go.sum | 4 ++-- internal/commands/manifest.go | 1 + internal/commands/manifest_add.go | 13 ++++++++----- internal/commands/manifest_annotate.go | 12 ++++++++---- internal/commands/manifest_exists.go | 2 +- internal/commands/manifest_remove.go | 13 ++++++++----- internal/commands/manifest_rm.go | 19 ++++++++++++------- pkg/client/add_manifest.go | 17 ++++++++--------- pkg/client/annotate_manifest.go | 2 +- pkg/client/client.go | 2 +- pkg/client/create_manifest.go | 8 +++++++- pkg/client/exists_manifest.go | 4 +++- pkg/client/inspect_manifest.go | 2 +- pkg/client/remove_manifest.go | 11 +++++++++-- pkg/client/rm_manifest.go | 13 ++++++++++--- 16 files changed, 81 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index c1e2424865..b629812a0c 100644 --- a/go.mod +++ b/go.mod @@ -126,4 +126,4 @@ require ( go 1.20 -replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122051427-9ec2c8cb243a +replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122094119-324a62a0cf35 diff --git a/go.sum b/go.sum index 7da095fc5e..83a4b84659 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122051427-9ec2c8cb243a h1:/qQauWgWudbUKjdD/xTEMUSG2d9CgtxZJMv5JqxRE50= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122051427-9ec2c8cb243a/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122094119-324a62a0cf35 h1:xuIyaun/3aisz58vnTDGIuG+y0RHOBZqOJhiwYbOxX8= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122094119-324a62a0cf35/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index d7f671148a..f244f27939 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -20,6 +20,7 @@ func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command cmd.AddCommand(ManifestInspect(logger, client)) cmd.AddCommand(ManifestPush(logger, client)) cmd.AddCommand(ManifestRemove(logger, client)) + cmd.AddCommand(ManifestExists(logger, client)) AddHelpFlag(cmd, "manifest") return cmd diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 462cc42dab..66f3daf542 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -31,7 +31,8 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { Long: `manifest add modifies a manifest list (Image index) and add a new image to the list of manifests. When a manifest list exits locally, user can add a new image to the manifest list using this command`, - RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { + var annotations = map[string]string(nil) imageIndex := args[0] manifests := args[1] if err := validateManifestAddFlags(flags); err != nil { @@ -40,9 +41,11 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { osFeatures := strings.Split(flags.osFeatures, ";") features := strings.Split(flags.features, ";") - annotations, err := StringToKeyValueMap(flags.annotations) - if err != nil { - return err + if flags.annotations != "" { + annotations, err = StringToKeyValueMap(flags.annotations) + if err != nil { + return err + } } err = pack.AddManifest(cmd.Context(), imageIndex, manifests, client.ManifestAddOptions{ @@ -57,7 +60,7 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { }) if err != nil { - return errors.Wrap(err, "unable to add manifest to the index : ") + return errors.Wrap(err, "unable to add manifest to the index: ") } return nil diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 17250c19e9..00b26fbb68 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -27,12 +27,16 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { cnbs/sample-package:hello-universe --arch amd64`, Long: `manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list. Sometimes a manifest list could reference an image that doesn't specify the architecture, The "annotate" command allows users to update those values before pushing the manifest list a registry`, - RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { + var annotations = map[string]string(nil) osFeatures := strings.Split(flags.osFeatures, ";") features := strings.Split(flags.features, ";") - annotations, err := StringToKeyValueMap(flags.annotations) - if err != nil { - return err + + if flags.annotations != "" { + annotations, err = StringToKeyValueMap(flags.annotations) + if err != nil { + return err + } } pack.AnnotateManifest(cmd.Context(), args[0], args[1], client.ManifestAnnotateOptions{ diff --git a/internal/commands/manifest_exists.go b/internal/commands/manifest_exists.go index d9dcc8b47c..d0417a9b22 100644 --- a/internal/commands/manifest_exists.go +++ b/internal/commands/manifest_exists.go @@ -28,6 +28,6 @@ func ManifestExists(logger logging.Logger, pack PackClient) *cobra.Command { }), } - AddHelpFlag(cmd, "remove") + AddHelpFlag(cmd, "exists") return cmd } diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index 8c5e80e3ed..e7ec96c324 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -1,8 +1,6 @@ package commands import ( - "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" @@ -21,14 +19,19 @@ func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { Long: `Delete one or more manifest lists from local storage. When a manifest list exits locally, users can remove existing images from a manifest list`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - var errMsg strings.Builder + var errMsg = "" errs := pack.DeleteManifest(cmd.Context(), args) for _, err := range errs { - errMsg.WriteString(err.Error() + "\n") + if err != nil { + errMsg += err.Error() + "\n" + } } - return errors.New(errMsg.String()) + if errMsg != "" { + return errors.New(errMsg) + } + return nil }), } diff --git a/internal/commands/manifest_rm.go b/internal/commands/manifest_rm.go index 236353b68e..13ee1d3d7f 100644 --- a/internal/commands/manifest_rm.go +++ b/internal/commands/manifest_rm.go @@ -1,8 +1,6 @@ package commands import ( - "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" @@ -14,7 +12,7 @@ func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { // var flags ManifestRemoveFlags cmd := &cobra.Command{ - Use: "pack manifest rm [manifest-list] [manifest] [manifest...] [flags]", + Use: "rm [manifest-list] [manifest] [manifest...] [flags]", Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), Short: "manifest rm will remove the specified image manifest if it is already referenced in the index", Example: `pack manifest rm cnbs/sample-package:hello-multiarch-universe \ @@ -22,13 +20,20 @@ func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { Long: `manifest rm will remove the specified image manifest if it is already referenced in the index. Sometimes users can just experiment with the feature locally and they want to discard all the local information created by pack. 'rm' command just delete the local manifest list`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - var errMsg strings.Builder - errs := pack.RemoveManifest(cmd.Context(), args[0], args[1:]) + var errMsg = "" + name := args[0] + images := args[1:] + errs := pack.RemoveManifest(cmd.Context(), name, images) for _, err := range errs { - errMsg.WriteString(err.Error() + "\n") + if err != nil { + errMsg += err.Error() + "\n" + } } - return errors.New(errMsg.String()) + if errMsg != "" { + return errors.New(errMsg) + } + return nil }), } diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index c1a9d8460f..70c3db5d43 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -24,11 +24,10 @@ func (c *Client) AddManifest(ctx context.Context, index string, image string, op ref, err := name.ParseReference(image) if err != nil { - return + return err } - digest := ref.Context().Digest(ref.Identifier()) - imgIndex, err := c.indexFactory.FindIndex(index) + imgIndex, err := c.indexFactory.LoadIndex(index) if err != nil { return fmt.Errorf("Error while trying to find image on local storage: %v", image) } @@ -39,31 +38,31 @@ func (c *Client) AddManifest(ctx context.Context, index string, image string, op } if opts.OS != "" { - if err := imgIndex.SetOS(digest, opts.OS); err != nil { + if err := imgIndex.SetOS(ref.(name.Digest), opts.OS); err != nil { return err } } if opts.OSArch != "" { - if err := imgIndex.SetArchitecture(digest, opts.OSArch); err != nil { + if err := imgIndex.SetArchitecture(ref.(name.Digest), opts.OSArch); err != nil { return err } } if opts.OSVariant != "" { - if err := imgIndex.SetVariant(digest, opts.OSVariant); err != nil { + if err := imgIndex.SetVariant(ref.(name.Digest), opts.OSVariant); err != nil { return err } } if opts.OSVersion != "" { - if err := imgIndex.SetOSVersion(digest, opts.OSVersion); err != nil { + if err := imgIndex.SetOSVersion(ref.(name.Digest), opts.OSVersion); err != nil { return err } } if len(opts.Features) != 0 { - if err := imgIndex.SetFeatures(digest, opts.Features); err != nil { + if err := imgIndex.SetFeatures(ref.(name.Digest), opts.Features); err != nil { return err } } @@ -77,7 +76,7 @@ func (c *Client) AddManifest(ctx context.Context, index string, image string, op } annotations[spec[0]] = spec[1] } - if err := imgIndex.SetAnnotations(digest, annotations); err != nil { + if err := imgIndex.SetAnnotations(ref.(name.Digest), annotations); err != nil { return err } } diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index 1c13452081..6b94afede8 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -16,7 +16,7 @@ type ManifestAnnotateOptions struct { // AnnotateManifest implements commands.PackClient. func (c *Client) AnnotateManifest(ctx context.Context, name string, image string, opts ManifestAnnotateOptions) error { - manifestList, err := c.indexFactory.FindIndex(name) + manifestList, err := c.indexFactory.LoadIndex(name) if err != nil { return err } diff --git a/pkg/client/client.go b/pkg/client/client.go index 5ccfb71050..8a129cefd1 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -314,7 +314,7 @@ func (f *indexFactory) LoadIndex(repoName string, opts ...index.Option) (img img if err == nil { return } - return nil, errors.Errorf("Image: '%s' not found", repoName) + return nil, errors.Wrap(err, errors.Errorf("Image: '%s' not found", repoName).Error()) } type indexFactory struct { diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index eef920dd93..3d7c228819 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -45,7 +45,13 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin default: format = types.DockerManifestList } - index.Push(imgutil.WithInsecure(opts.Insecure), imgutil.WithFormat(format)) + + err = index.Push(imgutil.WithInsecure(opts.Insecure), imgutil.WithFormat(format)) + if err != nil { + return err + } + + fmt.Printf("successfully pushed '%s' to registry \n", name) } return err diff --git a/pkg/client/exists_manifest.go b/pkg/client/exists_manifest.go index caa1b7c0ee..1fc09f5fb0 100644 --- a/pkg/client/exists_manifest.go +++ b/pkg/client/exists_manifest.go @@ -2,14 +2,16 @@ package client import ( "context" + "fmt" "github.com/pkg/errors" ) func (c *Client) ExistsManifest(ctx context.Context, image string) error { - if _, err := c.indexFactory.FindIndex(image); err != nil { + if _, err := c.indexFactory.LoadIndex(image); err != nil { return errors.Errorf("image '%s' is not found", image) } + fmt.Printf("index '%s' exists \n", image) return nil } diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index ab88a958c5..e964a3fb44 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -9,7 +9,7 @@ type InspectManifestOptions struct { // InspectManifest implements commands.PackClient. func (c *Client) InspectManifest(ctx context.Context, name string) error { - idx, err := c.indexFactory.FindIndex(name) + idx, err := c.indexFactory.LoadIndex(name) if err != nil { return err } diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index 6a20b87aaf..51d3468349 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -2,18 +2,25 @@ package client import ( "context" + "fmt" ) // DeleteManifest implements commands.PackClient. func (c *Client) DeleteManifest(ctx context.Context, names []string) []error { var errs []error for _, name := range names { - imgIndex, err := c.indexFactory.FindIndex(name) + imgIndex, err := c.indexFactory.LoadIndex(name) if err != nil { errs = append(errs, err) } - imgIndex.Delete() + if err = imgIndex.Delete(); err != nil { + errs = append(errs, err) + } + + if len(errs) == 0 { + fmt.Printf("successfully deleted '%s' \n", name) + } } return errs diff --git a/pkg/client/rm_manifest.go b/pkg/client/rm_manifest.go index 35ebb92c25..7a8f9853e5 100644 --- a/pkg/client/rm_manifest.go +++ b/pkg/client/rm_manifest.go @@ -15,14 +15,21 @@ func (c *Client) RemoveManifest(ctx context.Context, name string, images []strin } for _, image := range images { - ref, err := gccrName.ParseReference(image) + digest, err := gccrName.NewDigest(image, gccrName.WeakValidation, gccrName.Insecure) if err != nil { errs = append(errs, fmt.Errorf(`invalid instance "%s": %v`, image, err)) } - if err := imgIndex.Remove(ref.Context().Digest(ref.Identifier())); err != nil { + if err = imgIndex.Remove(digest); err != nil { errs = append(errs, err) } - fmt.Printf("Successfully removed %s from %s", image, name) + + if err = imgIndex.Save(); err != nil { + errs = append(errs, err) + } + + if len(errs) == 0 { + fmt.Printf("Successfully removed %s from %s \n", image, name) + } } return errs From 5d5d8ec5c07a3d2ad665019aa841c5fa4d63dc43 Mon Sep 17 00:00:00 2001 From: sai kiran Date: Tue, 6 Feb 2024 14:44:42 +0000 Subject: [PATCH 46/79] WIP fix manifest cli Signed-off-by: sai kiran --- go.mod | 2 +- go.sum | 4 +- internal/commands/manifest_add.go | 25 +++--- internal/commands/manifest_annotate.go | 19 ++-- internal/commands/manifest_create.go | 43 +++------ internal/commands/manifest_exists.go | 5 +- internal/commands/manifest_push.go | 24 +---- internal/commands/manifest_remove.go | 1 - pkg/client/add_manifest.go | 75 +++++++--------- pkg/client/annotate_manifest.go | 36 +++----- pkg/client/client.go | 10 ++- pkg/client/create_manifest.go | 24 +++-- pkg/client/create_manifest_test.go | 70 +++++++++++++++ pkg/client/inspect_manifest.go | 6 +- pkg/client/push_manifest.go | 22 +++-- pkg/client/remove_manifest.go | 8 +- pkg/client/rm_manifest.go | 6 +- pkg/testmocks/mock_index_factory.go | 116 +++++++++++++++++++++++++ 18 files changed, 327 insertions(+), 169 deletions(-) create mode 100644 pkg/client/create_manifest_test.go create mode 100644 pkg/testmocks/mock_index_factory.go diff --git a/go.mod b/go.mod index b629812a0c..b0228a556d 100644 --- a/go.mod +++ b/go.mod @@ -126,4 +126,4 @@ require ( go 1.20 -replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122094119-324a62a0cf35 +replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240206124516-e158f8d2cc9e diff --git a/go.sum b/go.sum index 83a4b84659..628b02c388 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122094119-324a62a0cf35 h1:xuIyaun/3aisz58vnTDGIuG+y0RHOBZqOJhiwYbOxX8= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240122094119-324a62a0cf35/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240206124516-e158f8d2cc9e h1:GfRaphr8VlfROQ6TswtGgRMQd7rhHCxMsY9QLDjnMp8= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240206124516-e158f8d2cc9e/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 66f3daf542..762ac6eb73 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -4,7 +4,6 @@ import ( "fmt" "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -32,15 +31,25 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { When a manifest list exits locally, user can add a new image to the manifest list using this command`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { - var annotations = map[string]string(nil) + var ( + annotations = make(map[string]string, 0) + features = make([]string, 0) + osFeatures = make([]string, 0) + ) imageIndex := args[0] manifests := args[1] if err := validateManifestAddFlags(flags); err != nil { return err } - osFeatures := strings.Split(flags.osFeatures, ";") - features := strings.Split(flags.features, ";") + if flags.features != "" { + features = strings.Split(flags.features, ";") + } + + if flags.osFeatures != "" { + features = strings.Split(flags.osFeatures, ";") + } + if flags.annotations != "" { annotations, err = StringToKeyValueMap(flags.annotations) if err != nil { @@ -48,7 +57,7 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { } } - err = pack.AddManifest(cmd.Context(), imageIndex, manifests, client.ManifestAddOptions{ + return pack.AddManifest(cmd.Context(), imageIndex, manifests, client.ManifestAddOptions{ OS: flags.os, OSVersion: flags.osVersion, OSArch: flags.osArch, @@ -58,12 +67,6 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { Annotations: annotations, All: flags.all, }) - - if err != nil { - return errors.Wrap(err, "unable to add manifest to the index: ") - } - - return nil }), } diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 00b26fbb68..56f65762b0 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -28,9 +28,19 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { Long: `manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list. Sometimes a manifest list could reference an image that doesn't specify the architecture, The "annotate" command allows users to update those values before pushing the manifest list a registry`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { - var annotations = map[string]string(nil) - osFeatures := strings.Split(flags.osFeatures, ";") - features := strings.Split(flags.features, ";") + var ( + annotations = make(map[string]string, 0) + features = make([]string, 0) + osFeatures = make([]string, 0) + ) + + if flags.features != "" { + features = strings.Split(flags.features, ";") + } + + if flags.osFeatures != "" { + features = strings.Split(flags.osFeatures, ";") + } if flags.annotations != "" { annotations, err = StringToKeyValueMap(flags.annotations) @@ -39,7 +49,7 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { } } - pack.AnnotateManifest(cmd.Context(), args[0], args[1], client.ManifestAnnotateOptions{ + return pack.AnnotateManifest(cmd.Context(), args[0], args[1], client.ManifestAnnotateOptions{ OS: flags.os, OSVersion: flags.osVersion, OSArch: flags.arch, @@ -48,7 +58,6 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { Features: features, Annotations: annotations, }) - return nil }), } diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 6eb95cfb2a..d005fd7c99 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -33,33 +33,18 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { RunE: logError(logger, func(cmd *cobra.Command, args []string) error { imageIndex := args[0] manifests := args[1:] - cmdFlags := cmd.Flags() - - if err := validateManifestCreateFlags(flags); err != nil { - return err - } - - if cmdFlags.Changed("insecure") { - flags.insecure = !flags.insecure - } - - if cmdFlags.Changed("publish") { - flags.publish = !flags.publish - } - - err := pack.CreateManifest(cmd.Context(), imageIndex, manifests, client.CreateManifestOptions{ - Format: flags.format, - Registry: flags.registry, - Insecure: flags.insecure, - Publish: flags.publish, - }) - - if err != nil { - return err - } - logger.Infof("Successfully created ImageIndex/ManifestList with imageID: '%s'", imageIndex) - - return nil + return pack.CreateManifest( + cmd.Context(), + imageIndex, + manifests, + client.CreateManifestOptions{ + Format: flags.format, + Registry: flags.registry, + Insecure: flags.insecure, + Publish: flags.publish, + All: flags.all, + }, + ) }), } @@ -86,7 +71,3 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { AddHelpFlag(cmd, "create") return cmd } - -func validateManifestCreateFlags(flags ManifestCreateFlags) error { - return nil -} diff --git a/internal/commands/manifest_exists.go b/internal/commands/manifest_exists.go index d0417a9b22..616e5d89c1 100644 --- a/internal/commands/manifest_exists.go +++ b/internal/commands/manifest_exists.go @@ -21,10 +21,7 @@ func ManifestExists(logger logging.Logger, pack PackClient) *cobra.Command { Example: `pack manifest exists cnbs/sample-package:hello-multiarch-universe`, 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 - } - return nil + return pack.ExistsManifest(cmd.Context(), args[0]) }), } diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index ede3444572..84d1fbd85a 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -1,8 +1,6 @@ package commands import ( - "fmt" - "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -11,8 +9,8 @@ import ( // ManifestPushFlags define flags provided to the ManifestPush type ManifestPushFlags struct { - format string - insecure, purge, all, quite bool + format string + insecure, purge, all bool } // ManifestPush pushes a manifest list (Image index) to a registry. @@ -27,22 +25,11 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { Long: `manifest push pushes a manifest list (Image index) to a registry. Once a manifest list is ready to be published into the registry, the push command can be used`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - if err := parseFalgs(flags); err != nil { - return err - } - - err := pack.PushManifest(cmd.Context(), args[0], client.PushManifestOptions{ + return pack.PushManifest(cmd.Context(), args[0], client.PushManifestOptions{ Format: flags.format, Insecure: flags.insecure, Purge: flags.purge, }) - - if err != nil { - return err - } - - fmt.Printf("manifest '%s' is successfully pushed to the registry", args[0]) - return nil }), } @@ -50,12 +37,7 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().BoolVar(&flags.insecure, "insecure", false, "Allow publishing to insecure registry") cmd.Flags().BoolVar(&flags.purge, "purge", false, "Delete the manifest list or image index from local storage if pushing succeeds") cmd.Flags().BoolVar(&flags.all, "all", false, "Also push the images in the list") - cmd.Flags().BoolVarP(&flags.quite, "quite", "q", false, "Also push the images in the list") AddHelpFlag(cmd, "push") return cmd } - -func parseFalgs(flags ManifestPushFlags) error { - return nil -} diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index e7ec96c324..ca7d65460d 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -21,7 +21,6 @@ func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { RunE: logError(logger, func(cmd *cobra.Command, args []string) error { var errMsg = "" errs := pack.DeleteManifest(cmd.Context(), args) - for _, err := range errs { if err != nil { errMsg += err.Error() + "\n" diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 70c3db5d43..1f8dc5256c 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -3,7 +3,6 @@ package client import ( "context" "fmt" - "strings" "github.com/buildpacks/imgutil" "github.com/google/go-containerregistry/pkg/name" @@ -17,74 +16,60 @@ type ManifestAddOptions struct { } // AddManifest implements commands.PackClient. -func (c *Client) AddManifest(ctx context.Context, index string, image string, opts ManifestAddOptions) (err error) { - if _, err = name.ParseReference(index); err != nil { - return - } - - ref, err := name.ParseReference(image) +func (c *Client) AddManifest(ctx context.Context, ii string, image string, opts ManifestAddOptions) (err error) { + idx, err := c.indexFactory.LoadIndex(ii) if err != nil { return err } - imgIndex, err := c.indexFactory.LoadIndex(index) - if err != nil { - return fmt.Errorf("Error while trying to find image on local storage: %v", image) - } - - err = imgIndex.Add(ref, imgutil.WithAll(opts.All)) - if err != nil { - return fmt.Errorf("Error while trying to add on manifest list: %v", err) + var ops = make([]imgutil.IndexAddOption, 0) + if opts.All { + ops = append(ops, imgutil.WithAll(opts.All)) } if opts.OS != "" { - if err := imgIndex.SetOS(ref.(name.Digest), opts.OS); err != nil { - return err - } + ops = append(ops, imgutil.WithOS(opts.OS)) } if opts.OSArch != "" { - if err := imgIndex.SetArchitecture(ref.(name.Digest), opts.OSArch); err != nil { - return err - } + ops = append(ops, imgutil.WithArchitecture(opts.OSArch)) } if opts.OSVariant != "" { - if err := imgIndex.SetVariant(ref.(name.Digest), opts.OSVariant); err != nil { - return err - } + ops = append(ops, imgutil.WithVariant(opts.OSVariant)) } if opts.OSVersion != "" { - if err := imgIndex.SetOSVersion(ref.(name.Digest), opts.OSVersion); err != nil { - return err - } + ops = append(ops, imgutil.WithOSVersion(opts.OSVersion)) } if len(opts.Features) != 0 { - if err := imgIndex.SetFeatures(ref.(name.Digest), opts.Features); err != nil { - return err - } + ops = append(ops, imgutil.WithFeatures(opts.Features)) + } + + if len(opts.OSFeatures) != 0 { + ops = append(ops, imgutil.WithOSFeatures(opts.OSFeatures)) } 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 := imgIndex.SetAnnotations(ref.(name.Digest), annotations); err != nil { - return err - } + ops = append(ops, imgutil.WithAnnotations(opts.Annotations)) + } + + ref, err := name.ParseReference(image, name.Insecure, name.WeakValidation) + if err != nil { + return err } - err = imgIndex.Save() - if err == nil { - fmt.Printf("'%s' successfully added to index: '%s'", image, index) + err = idx.Add(ref, ops...) + if err != nil { + return err + } + + err = idx.Save() + if err != nil { + return err } - return err + fmt.Printf("successfully added to index: '%s'\n", image) + return nil } diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index 6b94afede8..c277cba693 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -3,7 +3,6 @@ package client import ( "context" "fmt" - "strings" ggcrName "github.com/google/go-containerregistry/pkg/name" ) @@ -16,64 +15,57 @@ type ManifestAnnotateOptions struct { // AnnotateManifest implements commands.PackClient. func (c *Client) AnnotateManifest(ctx context.Context, name string, image string, opts ManifestAnnotateOptions) error { - manifestList, err := c.indexFactory.LoadIndex(name) + idx, err := c.indexFactory.LoadIndex(name) if err != nil { return err } - digest, err := ggcrName.NewDigest(image) + digest, err := ggcrName.NewDigest(image, ggcrName.Insecure, ggcrName.WeakValidation) if err != nil { return err } if opts.OS != "" { - if err := manifestList.SetOS(digest, opts.OS); err != nil { + if err := idx.SetOS(digest, opts.OS); err != nil { return err } } if opts.OSVersion != "" { - if err := manifestList.SetOSVersion(digest, opts.OSVersion); err != nil { + if err := idx.SetOSVersion(digest, opts.OSVersion); err != nil { return err } } if len(opts.OSFeatures) != 0 { - if err := manifestList.SetOSFeatures(digest, opts.OSFeatures); err != nil { + if err := idx.SetOSFeatures(digest, opts.OSFeatures); err != nil { return err } } if opts.OSArch != "" { - if err := manifestList.SetArchitecture(digest, opts.OSArch); err != nil { + if err := idx.SetArchitecture(digest, opts.OSArch); err != nil { return err } } if opts.OSVariant != "" { - if err := manifestList.SetVariant(digest, opts.OSVariant); err != nil { + if err := idx.SetVariant(digest, opts.OSVariant); err != nil { return err } } if len(opts.Features) != 0 { - if err := manifestList.SetFeatures(digest, opts.Features); err != nil { + if err := idx.SetFeatures(digest, opts.Features); 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 := manifestList.SetAnnotations(digest, annotations); err != nil { + if err := idx.SetAnnotations(digest, opts.Annotations); err != nil { return err } } - err = manifestList.Save() - if err == nil { - fmt.Printf("%s annotated \n", digest.String()) + err = idx.Save() + if err != nil { + return err } - return err + fmt.Printf("successfully annotated image '%s' in index '%s'\n", image, name) + return nil } diff --git a/pkg/client/client.go b/pkg/client/client.go index 8a129cefd1..28c3dab4ba 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -75,6 +75,8 @@ type ImageFactory interface { NewImage(repoName string, local bool, imageOS string) (imgutil.Image, error) } +//go:generate mockgen -package testmocks -destination ../testmocks/mock_index_factory.go github.com/buildpacks/pack/pkg/client IndexFactory + // IndexFactory is an interface representing the ability to create a ImageIndex/ManifestList. type IndexFactory interface { // create ManifestList locally @@ -83,7 +85,7 @@ type IndexFactory interface { LoadIndex(reponame string, opts ...index.Option) (imgutil.ImageIndex, error) // Fetch ManifestList from Registry with the given name FetchIndex(name string, opts ...index.Option) (imgutil.ImageIndex, error) - // FindIndex will find Index remotly first then on local + // FindIndex will find Index locally then on remote FindIndex(name string, opts ...index.Option) (imgutil.ImageIndex, error) } @@ -334,12 +336,12 @@ func (f *indexFactory) FetchIndex(name string, opts ...index.Option) (idx imguti func (f *indexFactory) FindIndex(repoName string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { opts = append(opts, index.WithKeychain(f.keychain)) - idx, err = f.FetchIndex(repoName, opts...) - if err != nil { + idx, err = f.LoadIndex(repoName, opts...) + if err == nil { return idx, err } - return f.FindIndex(repoName, opts...) + return f.FetchIndex(repoName, opts...) } func (f *indexFactory) CreateIndex(repoName string, opts ...index.Option) (imgutil.ImageIndex, error) { diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 3d7c228819..83ad67dee9 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -12,31 +12,43 @@ import ( type CreateManifestOptions struct { Format, Registry string - Insecure, Publish, all bool + Insecure, Publish, All bool } // CreateManifest implements commands.PackClient. func (c *Client) CreateManifest(ctx context.Context, name string, images []string, opts CreateManifestOptions) (err error) { - index, err := c.indexFactory.CreateIndex(name, parseOptsToIndexOptions(opts)...) + ops := parseOptsToIndexOptions(opts) + _, err = c.indexFactory.LoadIndex(name, ops...) + if err == nil { + return fmt.Errorf("image index with name: '%s' exists", name) + } + + _, err = c.indexFactory.CreateIndex(name, ops...) if err != nil { return } + index, err := c.indexFactory.LoadIndex(name, ops...) + if err != nil { + return err + } + for _, img := range images { ref, err := ggcrName.ParseReference(img) if err != nil { return err } - if err = index.Add(ref, imgutil.WithAll(opts.all)); err != nil { + if err = index.Add(ref, imgutil.WithAll(opts.All)); err != nil { return err } } err = index.Save() - if err == nil { - fmt.Printf("%s successfully created", name) + if err != nil { + return err } + fmt.Printf("successfully created index: '%s'\n", name) if opts.Publish { var format types.MediaType switch opts.Format { @@ -50,7 +62,7 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin if err != nil { return err } - + fmt.Printf("successfully pushed '%s' to registry \n", name) } diff --git a/pkg/client/create_manifest_test.go b/pkg/client/create_manifest_test.go new file mode 100644 index 0000000000..a1b62675d5 --- /dev/null +++ b/pkg/client/create_manifest_test.go @@ -0,0 +1,70 @@ +package client_test + +import ( + "bytes" + "context" + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/pack/pkg/client" + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestCreateManifest(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + spec.Run(t, "build", testCreateManifest, spec.Report(report.Terminal{})) +} + +func testCreateManifest(t *testing.T, when spec.G, it spec.S) { + var ( + mockController *gomock.Controller + mockIndexFactory *testmocks.MockIndexFactory + out bytes.Buffer + logger logging.Logger + subject *client.Client + err error + tmpDir string + // fakeIndex *fakes.Index + ) + when("#CreateManifest", func() { + it.Before(func() { + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + // fakeIndex, err = fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) + // h.AssertNil(t, err) + + subject, err = client.NewClient( + client.WithLogger(logger), + client.WithIndexFactory(mockIndexFactory), + ) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) + when("", func() { + it("should create manifest", func() { + err := subject.CreateManifest( + context.TODO(), + "pack/imgutil", + []string{"busybox:1.36-musl"}, + client.CreateManifestOptions{ + Insecure: true, + }, + ) + h.AssertNil(t, err) + }) + }) + }) +} diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index e964a3fb44..37f69b69ae 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -9,10 +9,14 @@ type InspectManifestOptions struct { // InspectManifest implements commands.PackClient. func (c *Client) InspectManifest(ctx context.Context, name string) error { - idx, err := c.indexFactory.LoadIndex(name) + idx, err := c.indexFactory.FindIndex(name) if err != nil { return err } + if err = idx.Save(); err != nil { + return err + } + return idx.Inspect() } diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index 411f42af5e..d7050bfce2 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -2,6 +2,7 @@ package client import ( "context" + "fmt" "github.com/buildpacks/imgutil" "github.com/google/go-containerregistry/pkg/v1/types" @@ -30,20 +31,25 @@ func (c *Client) PushManifest(ctx context.Context, index string, opts PushManife } } + fmt.Printf("successfully pushed index: '%s'\n", index) return } func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions []imgutil.IndexPushOption) { - var format types.MediaType switch opts.Format { case "oci": - format = types.OCIImageIndex + return []imgutil.IndexPushOption{ + imgutil.WithFormat(types.OCIImageIndex), + imgutil.WithInsecure(opts.Insecure), + } + case "v2s2": + return []imgutil.IndexPushOption{ + imgutil.WithFormat(types.DockerManifestList), + imgutil.WithInsecure(opts.Insecure), + } default: - format = types.DockerManifestList - } - - return []imgutil.IndexPushOption{ - imgutil.WithFormat(format), - imgutil.WithInsecure(opts.Insecure), + return []imgutil.IndexPushOption{ + imgutil.WithInsecure(opts.Insecure), + } } } diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index 51d3468349..fb23eb6036 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -17,10 +17,10 @@ func (c *Client) DeleteManifest(ctx context.Context, names []string) []error { if err = imgIndex.Delete(); err != nil { errs = append(errs, err) } - - if len(errs) == 0 { - fmt.Printf("successfully deleted '%s' \n", name) - } + } + + if len(errs) == 0 { + fmt.Printf("successfully deleted indexes \n") } return errs diff --git a/pkg/client/rm_manifest.go b/pkg/client/rm_manifest.go index 7a8f9853e5..ceb7abf9c6 100644 --- a/pkg/client/rm_manifest.go +++ b/pkg/client/rm_manifest.go @@ -26,10 +26,10 @@ func (c *Client) RemoveManifest(ctx context.Context, name string, images []strin if err = imgIndex.Save(); err != nil { errs = append(errs, err) } + } - if len(errs) == 0 { - fmt.Printf("Successfully removed %s from %s \n", image, name) - } + if len(errs) == 0 { + fmt.Printf("Successfully removed images from index: '%s' \n", name) } return errs diff --git a/pkg/testmocks/mock_index_factory.go b/pkg/testmocks/mock_index_factory.go new file mode 100644 index 0000000000..dc38d11f6a --- /dev/null +++ b/pkg/testmocks/mock_index_factory.go @@ -0,0 +1,116 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/buildpacks/pack/pkg/client (interfaces: IndexFactory) + +// Package testmocks is a generated GoMock package. +package testmocks + +import ( + reflect "reflect" + + imgutil "github.com/buildpacks/imgutil" + index "github.com/buildpacks/imgutil/index" + gomock "github.com/golang/mock/gomock" +) + +// MockIndexFactory is a mock of IndexFactory interface. +type MockIndexFactory struct { + ctrl *gomock.Controller + recorder *MockIndexFactoryMockRecorder +} + +// MockIndexFactoryMockRecorder is the mock recorder for MockIndexFactory. +type MockIndexFactoryMockRecorder struct { + mock *MockIndexFactory +} + +// NewMockIndexFactory creates a new mock instance. +func NewMockIndexFactory(ctrl *gomock.Controller) *MockIndexFactory { + mock := &MockIndexFactory{ctrl: ctrl} + mock.recorder = &MockIndexFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIndexFactory) EXPECT() *MockIndexFactoryMockRecorder { + return m.recorder +} + +// CreateIndex mocks base method. +func (m *MockIndexFactory) CreateIndex(arg0 string, arg1 ...index.Option) (imgutil.ImageIndex, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateIndex", varargs...) + ret0, _ := ret[0].(imgutil.ImageIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateIndex indicates an expected call of CreateIndex. +func (mr *MockIndexFactoryMockRecorder) CreateIndex(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateIndex", reflect.TypeOf((*MockIndexFactory)(nil).CreateIndex), varargs...) +} + +// FetchIndex mocks base method. +func (m *MockIndexFactory) FetchIndex(arg0 string, arg1 ...index.Option) (imgutil.ImageIndex, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "FetchIndex", varargs...) + ret0, _ := ret[0].(imgutil.ImageIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchIndex indicates an expected call of FetchIndex. +func (mr *MockIndexFactoryMockRecorder) FetchIndex(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchIndex", reflect.TypeOf((*MockIndexFactory)(nil).FetchIndex), varargs...) +} + +// FindIndex mocks base method. +func (m *MockIndexFactory) FindIndex(arg0 string, arg1 ...index.Option) (imgutil.ImageIndex, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "FindIndex", varargs...) + ret0, _ := ret[0].(imgutil.ImageIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FindIndex indicates an expected call of FindIndex. +func (mr *MockIndexFactoryMockRecorder) FindIndex(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindIndex", reflect.TypeOf((*MockIndexFactory)(nil).FindIndex), varargs...) +} + +// LoadIndex mocks base method. +func (m *MockIndexFactory) LoadIndex(arg0 string, arg1 ...index.Option) (imgutil.ImageIndex, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "LoadIndex", varargs...) + ret0, _ := ret[0].(imgutil.ImageIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LoadIndex indicates an expected call of LoadIndex. +func (mr *MockIndexFactoryMockRecorder) LoadIndex(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadIndex", reflect.TypeOf((*MockIndexFactory)(nil).LoadIndex), varargs...) +} From c74fe0f8c15bd75fda28742acd3b58315a70b16c Mon Sep 17 00:00:00 2001 From: WYGIN Date: Fri, 9 Feb 2024 09:06:51 +0000 Subject: [PATCH 47/79] WIP fix bugs related to OSFeatures and Push index Signed-off-by: WYGIN --- go.mod | 2 +- go.sum | 4 +- internal/commands/manifest_annotate.go | 13 +++-- pkg/client/annotate_manifest.go | 12 ++++- pkg/client/client.go | 43 ++++++++++++++-- pkg/client/create_manifest_test.go | 68 +++++++++++++++++++++----- pkg/client/remove_manifest.go | 10 ++-- 7 files changed, 124 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index b0228a556d..afe46f9931 100644 --- a/go.mod +++ b/go.mod @@ -126,4 +126,4 @@ require ( go 1.20 -replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240206124516-e158f8d2cc9e +replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240209055731-cdd16f850196 diff --git a/go.sum b/go.sum index 628b02c388..f2d641b7e6 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240206124516-e158f8d2cc9e h1:GfRaphr8VlfROQ6TswtGgRMQd7rhHCxMsY9QLDjnMp8= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240206124516-e158f8d2cc9e/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240209055731-cdd16f850196 h1:aQsvLde7NoUWRpiY9k4zlQBx7gFPIAPtFF1XDnN5zcc= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240209055731-cdd16f850196/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 56f65762b0..9553b33c5f 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -11,8 +11,8 @@ import ( // ManifestAnnotateFlags define flags provided to the ManifestAnnotate type ManifestAnnotateFlags struct { - os, arch, variant, osVersion string - features, osFeatures, annotations string + os, arch, variant, osVersion string + features, osFeatures, urls, annotations string } // ManifestAnnotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list. @@ -32,6 +32,7 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { annotations = make(map[string]string, 0) features = make([]string, 0) osFeatures = make([]string, 0) + urls = make([]string, 0) ) if flags.features != "" { @@ -39,7 +40,11 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { } if flags.osFeatures != "" { - features = strings.Split(flags.osFeatures, ";") + osFeatures = strings.Split(flags.osFeatures, ";") + } + + if flags.urls != "" { + urls = strings.Split(flags.urls, ";") } if flags.annotations != "" { @@ -56,6 +61,7 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { OSVariant: flags.variant, OSFeatures: osFeatures, Features: features, + URLs: urls, Annotations: annotations, }) }), @@ -66,6 +72,7 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().StringVar(&flags.variant, "variant", "", "Set the architecture") cmd.Flags().StringVar(&flags.osVersion, "os-version", "", "override the os `version` of the specified image") cmd.Flags().StringVar(&flags.features, "features", "", "override the `features` of the specified image") + cmd.Flags().StringVar(&flags.urls, "urls", "", "override the `urls` of the specified image") cmd.Flags().StringVar(&flags.osFeatures, "os-features", "", "override the os `features` of the specified image") cmd.Flags().StringVar(&flags.annotations, "annotations", "", "set an `annotation` for the specified image") diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index c277cba693..fb2ecdd172 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -9,7 +9,7 @@ import ( type ManifestAnnotateOptions struct { OS, OSVersion, OSArch, OSVariant string - OSFeatures, Features []string + OSFeatures, Features, URLs []string Annotations map[string]string } @@ -55,6 +55,16 @@ func (c *Client) AnnotateManifest(ctx context.Context, name string, image string return err } } + if len(opts.OSFeatures) != 0 { + if err := idx.SetOSFeatures(digest, opts.OSFeatures); err != nil { + return err + } + } + if len(opts.URLs) != 0 { + if err := idx.SetURLs(digest, opts.URLs); err != nil { + return err + } + } if len(opts.Annotations) != 0 { if err := idx.SetAnnotations(digest, opts.Annotations); err != nil { return err diff --git a/pkg/client/client.go b/pkg/client/client.go index 28c3dab4ba..5306568cca 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -39,6 +39,10 @@ import ( "github.com/buildpacks/pack/pkg/logging" ) +const ( + xdgRuntimePath = "XDG_RUNTIME_DIR" +) + //go:generate mockgen -package testmocks -destination ../testmocks/mock_docker_client.go github.com/docker/docker/client CommonAPIClient //go:generate mockgen -package testmocks -destination ../testmocks/mock_image_fetcher.go github.com/buildpacks/pack/pkg/client ImageFetcher @@ -306,7 +310,11 @@ func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (i } func (f *indexFactory) LoadIndex(repoName string, opts ...index.Option) (img imgutil.ImageIndex, err error) { - opts = append(opts, index.WithKeychain(f.keychain)) + opts, err = withXDGPath(opts, f.keychain) + if err != nil { + return nil, err + } + img, err = local.NewIndex(repoName, opts...) if err == nil { return @@ -325,7 +333,11 @@ type indexFactory struct { } func (f *indexFactory) FetchIndex(name string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { - opts = append(opts, index.WithKeychain(f.keychain)) + opts, err = withXDGPath(opts, f.keychain) + if err != nil { + return nil, err + } + idx, err = remote.NewIndex(name, opts...) if err != nil { return idx, fmt.Errorf("ImageIndex in not available at registry") @@ -335,7 +347,11 @@ func (f *indexFactory) FetchIndex(name string, opts ...index.Option) (idx imguti } func (f *indexFactory) FindIndex(repoName string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { - opts = append(opts, index.WithKeychain(f.keychain)) + opts, err = withXDGPath(opts, f.keychain) + if err != nil { + return nil, err + } + idx, err = f.LoadIndex(repoName, opts...) if err == nil { return idx, err @@ -345,6 +361,25 @@ func (f *indexFactory) FindIndex(repoName string, opts ...index.Option) (idx img } func (f *indexFactory) CreateIndex(repoName string, opts ...index.Option) (imgutil.ImageIndex, error) { - opts = append(opts, index.WithKeychain(f.keychain)) + opts, err := withXDGPath(opts, f.keychain) + if err != nil { + return nil, err + } + return index.NewIndex(repoName, opts...) } + +func withXDGPath(ops []index.Option, keychain authn.Keychain) ([]index.Option, error) { + xdgPath, ok := os.LookupEnv(xdgRuntimePath) + if ok { + ops = append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(xdgPath)) + return ops, nil + } + home, err := iconfig.PackHome() + if err != nil { + return ops, err + } + + ops = append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(home)) + return ops, nil +} diff --git a/pkg/client/create_manifest_test.go b/pkg/client/create_manifest_test.go index a1b62675d5..8c7b0da5ab 100644 --- a/pkg/client/create_manifest_test.go +++ b/pkg/client/create_manifest_test.go @@ -1,17 +1,22 @@ -package client_test +package client import ( "bytes" "context" + "errors" "os" "testing" "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/authn" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/pack/pkg/client" + "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/pack/pkg/logging" "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" @@ -29,37 +34,76 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { mockIndexFactory *testmocks.MockIndexFactory out bytes.Buffer logger logging.Logger - subject *client.Client + subject *Client err error tmpDir string // fakeIndex *fakes.Index ) when("#CreateManifest", func() { + var ( + // xdgPath = "xdgPath" + // ops = []index.Option{ + // index.WithKeychain(authn.DefaultKeychain), + // index.WithXDGRuntimePath(xdgPath), + // } + prepareMockImageFactoryForCreateIndex = func() { + img, err := fakes.ImageIndex(1024, 1, 1, v1.Descriptor{}) + h.AssertNil(t, err) + // idx := imgutil.Index{ + // ImageIndex: img, + // Options: imgutil.IndexOptions{ + + // }, + // } + // imgIdx := (imgutil.ImageIndex)(&idx) + + // err = img.Save() + // h.AssertNil(t, err) + mockIndexFactory.EXPECT(). + CreateIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(img, err) + mockIndexFactory.EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + After( + mockIndexFactory.EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + Times(1). + Return( + imgutil.ImageIndex(nil), + errors.New("no image exists"), + ), + ). + Return(img, err) + } + ) it.Before(func() { logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) mockController = gomock.NewController(t) mockIndexFactory = testmocks.NewMockIndexFactory(mockController) - // fakeIndex, err = fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) - // h.AssertNil(t, err) - - subject, err = client.NewClient( - client.WithLogger(logger), - client.WithIndexFactory(mockIndexFactory), + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) h.AssertNil(t, err) }) it.After(func() { mockController.Finish() h.AssertNil(t, os.RemoveAll(tmpDir)) }) - when("", func() { - it("should create manifest", func() { + when("should", func() { + it("create manifest", func() { + prepareMockImageFactoryForCreateIndex() err := subject.CreateManifest( context.TODO(), "pack/imgutil", []string{"busybox:1.36-musl"}, - client.CreateManifestOptions{ + CreateManifestOptions{ Insecure: true, }, ) diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index fb23eb6036..941ea18f82 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -10,13 +10,13 @@ func (c *Client) DeleteManifest(ctx context.Context, names []string) []error { var errs []error for _, name := range names { imgIndex, err := c.indexFactory.LoadIndex(name) - if err != nil { - errs = append(errs, err) - } - - if err = imgIndex.Delete(); err != nil { + if err == nil { + if err := imgIndex.Delete(); err == nil { + continue + } errs = append(errs, err) } + errs = append(errs, err) } if len(errs) == 0 { From 6597bc817e57ca41b5c6cc108d71c7b5584509b3 Mon Sep 17 00:00:00 2001 From: WYGIN Date: Mon, 12 Feb 2024 14:19:08 +0000 Subject: [PATCH 48/79] WIP fix bugs related to imgutils Signed-off-by: WYGIN --- go.mod | 2 +- go.sum | 4 +- internal/commands/config.go | 1 + internal/commands/config_index_full_mode.go | 54 ++++++++++++ internal/commands/manifest_add.go | 6 +- internal/commands/manifest_annotate.go | 7 +- internal/commands/manifest_create.go | 9 +- internal/commands/manifest_exists.go | 2 +- internal/commands/manifest_inspect.go | 2 +- internal/commands/manifest_push.go | 4 +- internal/commands/manifest_remove.go | 2 +- internal/commands/manifest_rm.go | 5 +- internal/config/config.go | 1 + pkg/client/client.go | 28 +++--- pkg/client/create_manifest.go | 46 ++++++---- pkg/client/create_manifest_test.go | 94 ++++++++++++++++++--- pkg/client/inspect_manifest.go | 10 +-- pkg/client/rm_manifest.go | 4 +- 18 files changed, 209 insertions(+), 72 deletions(-) create mode 100644 internal/commands/config_index_full_mode.go diff --git a/go.mod b/go.mod index afe46f9931..030204d96c 100644 --- a/go.mod +++ b/go.mod @@ -126,4 +126,4 @@ require ( go 1.20 -replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240209055731-cdd16f850196 +replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240212141424-fad6445eb228 diff --git a/go.sum b/go.sum index f2d641b7e6..392c5499c0 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240209055731-cdd16f850196 h1:aQsvLde7NoUWRpiY9k4zlQBx7gFPIAPtFF1XDnN5zcc= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240209055731-cdd16f850196/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240212141424-fad6445eb228 h1:oGe44vZnGMPfWWq8UgL4gyhmt27kGNzPoHc4dZvPmWA= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240212141424-fad6445eb228/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= diff --git a/internal/commands/config.go b/internal/commands/config.go index a2b087dabc..9ff3f684c6 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -24,6 +24,7 @@ func NewConfigCommand(logger logging.Logger, cfg config.Config, cfgPath string, cmd.AddCommand(ConfigTrustedBuilder(logger, cfg, cfgPath)) cmd.AddCommand(ConfigLifecycleImage(logger, cfg, cfgPath)) cmd.AddCommand(ConfigRegistryMirrors(logger, cfg, cfgPath)) + cmd.AddCommand(ConfigImageIndexFullMode(logger, cfg, cfgPath)) AddHelpFlag(cmd, "config") return cmd diff --git a/internal/commands/config_index_full_mode.go b/internal/commands/config_index_full_mode.go new file mode 100644 index 0000000000..a27b700544 --- /dev/null +++ b/internal/commands/config_index_full_mode.go @@ -0,0 +1,54 @@ +package commands + +import ( + "strconv" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/internal/config" + "github.com/buildpacks/pack/internal/style" + "github.com/buildpacks/pack/pkg/logging" +) + +func ConfigImageIndexFullMode(logger logging.Logger, cfg config.Config, cfgPath string) *cobra.Command { + cmd := &cobra.Command{ + Use: "index-full-mode []", + Args: cobra.MaximumNArgs(1), + Short: "List and set the current 'index-full-mode' value from the config", + Long: "ImageIndex FullMode features in pack are gated, and require you adding setting `index-full-mode=true` to the Pack Config, either manually, or using this command.\n\n" + + "* Running `pack config index-full-mode` prints whether ImageIndexFullMode features are currently enabled.\n" + + "* Running `pack config index-full-mode ` enables or disables ImageIndexFullMode features.", + RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + switch { + case len(args) == 0: + if cfg.ImageIndexFullMode { + logger.Infof("ImageIndexFullMode features are enabled! To turn them off, run `pack config index-full-mode false`") + } else { + logger.Info("ImageIndexFullMode features aren't currently enabled. To enable them, run `pack config index-full-mode true`") + } + default: + val, err := strconv.ParseBool(args[0]) + if err != nil { + return errors.Wrapf(err, "invalid value %s provided", style.Symbol(args[0])) + } + cfg.ImageIndexFullMode = val + + if err = config.Write(cfg, cfgPath); err != nil { + return errors.Wrap(err, "writing to config") + } + + if cfg.ImageIndexFullMode { + logger.Info("ImageIndexFullMode features enabled!") + } else { + logger.Info("ImageIndexFullMode features disabled") + } + } + + return nil + }), + } + + AddHelpFlag(cmd, "index-full-mode") + return cmd +} diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 762ac6eb73..aa82429113 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -24,12 +24,10 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "add [OPTIONS] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), - Short: "manifest add modifies a manifest list (Image index) and add a new image to the list of manifests.", + Short: "Add an image to a manifest list or image index.", Example: `pack manifest add cnbs/sample-package:hello-multiarch-universe \ cnbs/sample-package:hello-universe-riscv-linux`, - Long: `manifest add modifies a manifest list (Image index) and add a new image to the list of manifests. - - When a manifest list exits locally, user can add a new image to the manifest list using this command`, + Long: `manifest add modifies a manifest list (Image index) and add a new image to the list of manifests.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { var ( annotations = make(map[string]string, 0) diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 9553b33c5f..cc9f45b065 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -22,11 +22,10 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "annotate [OPTIONS] [...] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), - Short: "manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.", + Short: "Add or update information about an entry in a manifest list or image index.", Example: `pack manifest annotate cnbs/sample-package:hello-universe-multiarch \ - cnbs/sample-package:hello-universe --arch amd64`, - Long: `manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list. - Sometimes a manifest list could reference an image that doesn't specify the architecture, The "annotate" command allows users to update those values before pushing the manifest list a registry`, + cnbs/sample-package:hello-universe --arch amd64`, + Long: `manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { var ( annotations = make(map[string]string, 0) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index d005fd7c99..576e5dca81 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -22,14 +22,11 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "create [ ... ] [flags]", Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), - Short: "manifest create generates a manifest list for a multi-arch image", + Short: "Create a manifest list or image index.", Example: `pack manifest create cnbs/sample-package:hello-multiarch-universe \ cnbs/sample-package:hello-universe \ cnbs/sample-package:hello-universe-windows`, - Long: `Create a manifest list or image index for the image to support muti architecture for the image, it create a new ManifestList or ImageIndex with the given name and adds the list of Manifests to the newly created ImageIndex or ManifestList - - If the already exists in the registry: pack will save a local copy of the remote manifest list, - If the doestn't exist in a registry: pack will create a local representation of the manifest list that will only save on the remote registry if the user publish it`, + Long: `Generate manifest list for a multi-arch image which will be stored locally for manipulating images within index`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { imageIndex := args[0] manifests := args[1:] @@ -50,7 +47,7 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmdFlags := cmd.Flags() - cmdFlags.StringVarP(&flags.format, "format", "f", "v2s2", "Format to save image index as ('OCI' or 'V2S2') (default 'v2s2')") + cmdFlags.StringVarP(&flags.format, "format", "f", "v2s2", "Format to save image index as ('OCI' or 'V2S2')") cmdFlags.StringVarP(&flags.registry, "registry", "r", "", "Publish to registry") cmdFlags.StringVar(&flags.os, "os", "", "If any of the specified images is a list/index, choose the one for `os`") if err := cmdFlags.MarkHidden("os"); err != nil { diff --git a/internal/commands/manifest_exists.go b/internal/commands/manifest_exists.go index 616e5d89c1..617f4602b7 100644 --- a/internal/commands/manifest_exists.go +++ b/internal/commands/manifest_exists.go @@ -17,7 +17,7 @@ func ManifestExists(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "exists [manifest-list]", Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), - Short: "checks if a manifest list exists in local storage", + Short: "Check if the given manifest list exists in local storage", Example: `pack manifest exists cnbs/sample-package:hello-multiarch-universe`, Long: `Checks if a manifest list exists in local storage`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { diff --git a/internal/commands/manifest_inspect.go b/internal/commands/manifest_inspect.go index 540294de3b..7ddc053a33 100644 --- a/internal/commands/manifest_inspect.go +++ b/internal/commands/manifest_inspect.go @@ -17,7 +17,7 @@ func ManifestInspect(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "inspect [flags]", Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), - Short: "manifest inspect shows the manifest information stored in local storage", + Short: "Display a manifest list or image index.", Example: `pack manifest inspect cnbs/sample-builder:multiarch`, Long: `manifest inspect shows the manifest information stored in local storage. The inspect command will help users to view how their local manifest list looks like`, diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index 84d1fbd85a..afc1799118 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -20,7 +20,7 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "push [OPTIONS] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), - Short: "manifest push pushes a manifest list (Image index) to a registry.", + Short: "Push a manifest list or image index to a registry.", Example: `pack manifest push cnbs/sample-package:hello-multiarch-universe`, Long: `manifest push pushes a manifest list (Image index) to a registry. Once a manifest list is ready to be published into the registry, the push command can be used`, @@ -33,7 +33,7 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { }), } - cmd.Flags().StringVarP(&flags.format, "format", "f", "", "Format to save image index as ('OCI' or 'V2S2') (default 'v2s2')") + cmd.Flags().StringVarP(&flags.format, "format", "f", "", "Format to save image index as ('OCI' or 'V2S2')") cmd.Flags().BoolVar(&flags.insecure, "insecure", false, "Allow publishing to insecure registry") cmd.Flags().BoolVar(&flags.purge, "purge", false, "Delete the manifest list or image index from local storage if pushing succeeds") cmd.Flags().BoolVar(&flags.all, "all", false, "Also push the images in the list") diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index ca7d65460d..ce5a6ba9d6 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -14,7 +14,7 @@ func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "remove [manifest-list] [manifest-list...] [flags]", Args: cobra.MatchAll(cobra.MinimumNArgs(1), cobra.OnlyValidArgs), - Short: "Delete one or more manifest lists from local storage", + Short: "Remove an image from a manifest list or image index.", Example: `pack manifest remove cnbs/sample-package:hello-multiarch-universe`, Long: `Delete one or more manifest lists from local storage. When a manifest list exits locally, users can remove existing images from a manifest list`, diff --git a/internal/commands/manifest_rm.go b/internal/commands/manifest_rm.go index 13ee1d3d7f..415e34bfd2 100644 --- a/internal/commands/manifest_rm.go +++ b/internal/commands/manifest_rm.go @@ -14,10 +14,11 @@ func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "rm [manifest-list] [manifest] [manifest...] [flags]", Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), - Short: "manifest rm will remove the specified image manifest if it is already referenced in the index", + Short: "Remove manifest list or image index from local storage.", Example: `pack manifest rm cnbs/sample-package:hello-multiarch-universe \ - cnbs/sample-package:hello-universe-windows`, + cnbs/sample-package@sha256:42969d8175941c21ab739d3064e9cd7e93c972a0a6050602938ed501d156e452`, Long: `manifest rm will remove the specified image manifest if it is already referenced in the index. + User must pass digest of the image in oder to delete it from index. Sometimes users can just experiment with the feature locally and they want to discard all the local information created by pack. 'rm' command just delete the local manifest list`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { var errMsg = "" diff --git a/internal/config/config.go b/internal/config/config.go index de370ef727..36d4af66b0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,7 @@ type Config struct { LifecycleImage string `toml:"lifecycle-image,omitempty"` RegistryMirrors map[string]string `toml:"registry-mirrors,omitempty"` LayoutRepositoryDir string `toml:"layout-repo-dir,omitempty"` + ImageIndexFullMode bool `toml:"index-full-mode,omitempty"` } type Registry struct { diff --git a/pkg/client/client.go b/pkg/client/client.go index 5306568cca..8560140332 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -310,7 +310,7 @@ func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (i } func (f *indexFactory) LoadIndex(repoName string, opts ...index.Option) (img imgutil.ImageIndex, err error) { - opts, err = withXDGPath(opts, f.keychain) + opts, err = withOptions(opts, f.keychain) if err != nil { return nil, err } @@ -333,7 +333,7 @@ type indexFactory struct { } func (f *indexFactory) FetchIndex(name string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { - opts, err = withXDGPath(opts, f.keychain) + opts, err = withOptions(opts, f.keychain) if err != nil { return nil, err } @@ -347,7 +347,7 @@ func (f *indexFactory) FetchIndex(name string, opts ...index.Option) (idx imguti } func (f *indexFactory) FindIndex(repoName string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { - opts, err = withXDGPath(opts, f.keychain) + opts, err = withOptions(opts, f.keychain) if err != nil { return nil, err } @@ -361,7 +361,7 @@ func (f *indexFactory) FindIndex(repoName string, opts ...index.Option) (idx img } func (f *indexFactory) CreateIndex(repoName string, opts ...index.Option) (imgutil.ImageIndex, error) { - opts, err := withXDGPath(opts, f.keychain) + opts, err := withOptions(opts, f.keychain) if err != nil { return nil, err } @@ -369,17 +369,23 @@ func (f *indexFactory) CreateIndex(repoName string, opts ...index.Option) (imgut return index.NewIndex(repoName, opts...) } -func withXDGPath(ops []index.Option, keychain authn.Keychain) ([]index.Option, error) { - xdgPath, ok := os.LookupEnv(xdgRuntimePath) - if ok { - ops = append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(xdgPath)) - return ops, nil - } +func withOptions(ops []index.Option, keychain authn.Keychain) ([]index.Option, error) { home, err := iconfig.PackHome() if err != nil { return ops, err } - ops = append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(home)) + config, err := getConfig() + if err != nil { + return nil, err + } + + xdgPath, ok := os.LookupEnv(xdgRuntimePath) + if ok { + ops = append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(xdgPath), index.WithManifestOnly(!config.ImageIndexFullMode)) + return ops, nil + } + + ops = append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(filepath.Join(home, "manifests")), index.WithManifestOnly(!config.ImageIndexFullMode)) return ops, nil } diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 83ad67dee9..9f43514c0a 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -3,11 +3,13 @@ package client import ( "context" "fmt" + "sync" "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/index" ggcrName "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/types" + "golang.org/x/sync/errgroup" ) type CreateManifestOptions struct { @@ -20,7 +22,7 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin ops := parseOptsToIndexOptions(opts) _, err = c.indexFactory.LoadIndex(name, ops...) if err == nil { - return fmt.Errorf("image index with name: '%s' exists", name) + return fmt.Errorf("exits in your local storage, use 'pack manifest remove' if you want to delete it") } _, err = c.indexFactory.CreateIndex(name, ops...) @@ -33,14 +35,19 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin return err } + var errGroup, _ = errgroup.WithContext(ctx) + var wg sync.WaitGroup for _, img := range images { - ref, err := ggcrName.ParseReference(img) - if err != nil { - return err - } - if err = index.Add(ref, imgutil.WithAll(opts.All)); err != nil { - return err - } + img := img + wg.Add(1) + errGroup.Go(func() error { + return addImage(index, img, &wg, opts) + }) + } + + wg.Wait() + if err = errGroup.Wait(); err != nil { + return err } err = index.Save() @@ -50,15 +57,7 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin fmt.Printf("successfully created index: '%s'\n", name) if opts.Publish { - var format types.MediaType - switch opts.Format { - case "oci": - format = types.OCIImageIndex - default: - format = types.DockerManifestList - } - - err = index.Push(imgutil.WithInsecure(opts.Insecure), imgutil.WithFormat(format)) + err = index.Push(imgutil.WithInsecure(opts.Insecure)) if err != nil { return err } @@ -82,3 +81,16 @@ func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []index.Option index.WithInsecure(opts.Insecure), } } + +func addImage(index imgutil.ImageIndex, img string, wg *sync.WaitGroup, opts CreateManifestOptions) error { + ref, err := ggcrName.ParseReference(img) + if err != nil { + return err + } + if err = index.Add(ref, imgutil.WithAll(opts.All)); err != nil { + return err + } + + wg.Done() + return nil +} diff --git a/pkg/client/create_manifest_test.go b/pkg/client/create_manifest_test.go index 8c7b0da5ab..71f85510ca 100644 --- a/pkg/client/create_manifest_test.go +++ b/pkg/client/create_manifest_test.go @@ -10,6 +10,7 @@ import ( "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/types" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" @@ -46,23 +47,37 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { // index.WithKeychain(authn.DefaultKeychain), // index.WithXDGRuntimePath(xdgPath), // } - prepareMockImageFactoryForCreateIndex = func() { - img, err := fakes.ImageIndex(1024, 1, 1, v1.Descriptor{}) + prepareMockImageFactoryForValidCreateIndex = func() { + idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) h.AssertNil(t, err) - // idx := imgutil.Index{ - // ImageIndex: img, - // Options: imgutil.IndexOptions{ - // }, - // } - // imgIdx := (imgutil.ImageIndex)(&idx) + mockIndexFactory.EXPECT(). + CreateIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(idx, err) + mockIndexFactory.EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + After( + mockIndexFactory.EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + Times(1). + Return( + imgutil.ImageIndex(nil), + errors.New("no image exists"), + ), + ). + Return(idx, err) + } + + prepareMockImageFactoryForValidCreateIndexWithAll = func() { + idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}, fakes.WithIndex(true)) + h.AssertNil(t, err) - // err = img.Save() - // h.AssertNil(t, err) mockIndexFactory.EXPECT(). CreateIndex(gomock.Any(), gomock.Any()). AnyTimes(). - Return(img, err) + Return(idx, err) mockIndexFactory.EXPECT(). LoadIndex(gomock.Any(), gomock.Any()). AnyTimes(). @@ -75,7 +90,22 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { errors.New("no image exists"), ), ). - Return(img, err) + Return(idx, err) + } + + prepareMockImageFactoryForInvalidCreateIndexExistsLoadIndex = func() { + idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) + h.AssertNil(t, err) + + mockIndexFactory.EXPECT(). + CreateIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(idx, err) + + mockIndexFactory.EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(idx, err) } ) it.Before(func() { @@ -98,7 +128,45 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { }) when("should", func() { it("create manifest", func() { - prepareMockImageFactoryForCreateIndex() + prepareMockImageFactoryForValidCreateIndex() + err := subject.CreateManifest( + context.TODO(), + "pack/imgutil", + []string{"busybox:1.36-musl"}, + CreateManifestOptions{ + Insecure: true, + }, + ) + h.AssertNil(t, err) + }) + it("create manifests ignoring all option", func() { + prepareMockImageFactoryForValidCreateIndex() + err := subject.CreateManifest( + context.TODO(), + "pack/imgutil", + []string{"busybox:1.36-musl"}, + CreateManifestOptions{ + Insecure: true, + All: true, + }, + ) + h.AssertNil(t, err) + }) + it("create manifests with all nested images", func() { + prepareMockImageFactoryForValidCreateIndexWithAll() + err := subject.CreateManifest( + context.TODO(), + "pack/imgutil", + []string{"busybox:1.36-musl"}, + CreateManifestOptions{ + Insecure: true, + All: true, + }, + ) + h.AssertNil(t, err) + }) + it("return an error when index exists already", func() { + prepareMockImageFactoryForInvalidCreateIndexExistsLoadIndex() err := subject.CreateManifest( context.TODO(), "pack/imgutil", diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index 37f69b69ae..a906acd344 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -2,11 +2,9 @@ package client import ( "context" + "fmt" ) -type InspectManifestOptions struct { -} - // InspectManifest implements commands.PackClient. func (c *Client) InspectManifest(ctx context.Context, name string) error { idx, err := c.indexFactory.FindIndex(name) @@ -14,9 +12,11 @@ func (c *Client) InspectManifest(ctx context.Context, name string) error { return err } - if err = idx.Save(); err != nil { + mfest, err := idx.Inspect() + if err != nil { return err } - return idx.Inspect() + fmt.Println(mfest) + return nil } diff --git a/pkg/client/rm_manifest.go b/pkg/client/rm_manifest.go index ceb7abf9c6..9aa95e4e5c 100644 --- a/pkg/client/rm_manifest.go +++ b/pkg/client/rm_manifest.go @@ -15,11 +15,11 @@ func (c *Client) RemoveManifest(ctx context.Context, name string, images []strin } for _, image := range images { - digest, err := gccrName.NewDigest(image, gccrName.WeakValidation, gccrName.Insecure) + ref, err := gccrName.ParseReference(image, gccrName.WeakValidation, gccrName.Insecure) if err != nil { errs = append(errs, fmt.Errorf(`invalid instance "%s": %v`, image, err)) } - if err = imgIndex.Remove(digest); err != nil { + if err = imgIndex.Remove(ref); err != nil { errs = append(errs, err) } From c9e585000d9291311f26339ff05c00c9e609a280 Mon Sep 17 00:00:00 2001 From: WYGIN Date: Mon, 19 Feb 2024 15:07:22 +0000 Subject: [PATCH 49/79] WIP: added couple of tests Signed-off-by: WYGIN --- go.mod | 8 +- go.sum | 6 +- pkg/client/add_manifest_test.go | 251 ++++++++++++++++++ pkg/client/annotate_manifest_test.go | 380 +++++++++++++++++++++++++++ pkg/client/create_manifest_test.go | 229 ++++++++-------- pkg/client/docker.go | 1 - pkg/client/inspect_manifest_test.go | 111 ++++++++ tools/go.sum | 14 + 8 files changed, 872 insertions(+), 128 deletions(-) create mode 100644 pkg/client/add_manifest_test.go create mode 100644 pkg/client/annotate_manifest_test.go create mode 100644 pkg/client/inspect_manifest_test.go diff --git a/go.mod b/go.mod index e093be77e1..47dec1ae88 100644 --- a/go.mod +++ b/go.mod @@ -135,8 +135,8 @@ require ( go 1.21 replace ( - github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240212141424-fad6445eb228 - - // Pin moby/buildkit until docker/docker is upgraded - github.com/moby/buildkit => github.com/moby/buildkit v0.11.6 + github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240214072401-d071ec2816c6 + + // Pin moby/buildkit until docker/docker is upgraded + github.com/moby/buildkit => github.com/moby/buildkit v0.11.6 ) diff --git a/go.sum b/go.sum index 80ce619381..580486a5a8 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7 github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240212141424-fad6445eb228 h1:oGe44vZnGMPfWWq8UgL4gyhmt27kGNzPoHc4dZvPmWA= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240212141424-fad6445eb228/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240214072401-d071ec2816c6 h1:ezBkLrGhz9n8AEaBcVmpeVE3/aLX/FOoCbC6pSAU/3U= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240214072401-d071ec2816c6/go.mod h1:7zUmt4wkVJNuXCZhQndEd3kvVGmWLVyzRFIQXTaeXlU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -91,8 +91,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/buildpacks/imgutil v0.0.0-20240118145509-e94a1b7de8a9 h1:kxe31xfMWJAIAzDfGQ3lL0j8QSSRfEHyLg7dRWIHA8I= -github.com/buildpacks/imgutil v0.0.0-20240118145509-e94a1b7de8a9/go.mod h1:PsazEB9yz+NG/cgm0Z1oQ0Xq6rD/U7eNMt5Su41afYY= github.com/buildpacks/lifecycle v0.18.4 h1:LGl/4guzU+57hn08W8RwjLLizYtuNfCZHtxn8TP2+bE= github.com/buildpacks/lifecycle v0.18.4/go.mod h1:DxxfyFaCi9ovbbP2fhcKBlImfbTPiPEtM5UqSlD1TJ8= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= diff --git a/pkg/client/add_manifest_test.go b/pkg/client/add_manifest_test.go new file mode 100644 index 0000000000..3b15004fcb --- /dev/null +++ b/pkg/client/add_manifest_test.go @@ -0,0 +1,251 @@ +package client + +import ( + "bytes" + "context" + "errors" + "os" + "testing" + + "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" + "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestAddManifest(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + spec.Run(t, "build", testAddManifest, spec.Report(report.Terminal{})) +} + +func testAddManifest(t *testing.T, when spec.G, it spec.S) { + var ( + mockController *gomock.Controller + mockIndexFactory *testmocks.MockIndexFactory + out bytes.Buffer + logger logging.Logger + subject *Client + err error + tmpDir string + ) + + when("#Add", func() { + it.Before(func() { + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) + it("should return an error if index doesn't exists locally", func() { + prepareIndexWithoutLocallyExists(t, *mockIndexFactory) + err = subject.AddManifest( + context.TODO(), + "pack/index", + "pack/image", + ManifestAddOptions{}, + ) + + h.AssertEq(t, err.Error(), "index not found locally") + }) + it("should add the given image", func() { + digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") + h.AssertNil(t, err) + + idx := prepareLoadIndex(t, *mockIndexFactory) + err = subject.AddManifest( + context.TODO(), + "pack/index", + digest.Identifier(), + ManifestAddOptions{}, + ) + h.AssertNil(t, err) + + _, err = idx.OS(digest) + h.AssertNil(t, err) + }) + it("should add index with OS and Arch specific", func() { + digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") + h.AssertNil(t, err) + + idx := prepareLoadIndex( + t, + *mockIndexFactory, + ) + + err = subject.AddManifest( + context.TODO(), + "pack/index", + digest.Name(), + ManifestAddOptions{ + OS: "some-os", + OSArch: "some-arch", + }, + ) + h.AssertNil(t, err) + + os, err := idx.OS(digest) + h.AssertNil(t, err) + h.AssertEq(t, os, "some-os") + + arch, err := idx.Architecture(digest) + h.AssertNil(t, err) + h.AssertEq(t, arch, "some-arch") + }) + it("should add with variant", func() { + digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") + h.AssertNil(t, err) + + idx := prepareLoadIndex( + t, + *mockIndexFactory, + ) + + err = subject.AddManifest( + context.TODO(), + "pack/index", + digest.Name(), + ManifestAddOptions{ + OSVariant: "some-variant", + }, + ) + h.AssertNil(t, err) + + variant, err := idx.Variant(digest) + h.AssertNil(t, err) + h.AssertEq(t, variant, "some-variant") + }) + it("should add with osVersion", func() { + digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") + h.AssertNil(t, err) + + idx := prepareLoadIndex( + t, + *mockIndexFactory, + ) + + err = subject.AddManifest( + context.TODO(), + "pack/index", + digest.Name(), + ManifestAddOptions{ + OSVersion: "some-os-version", + }, + ) + h.AssertNil(t, err) + + osVersion, err := idx.OSVersion(digest) + h.AssertNil(t, err) + h.AssertEq(t, osVersion, "some-os-version") + }) + it("should add with features", func() { + digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") + h.AssertNil(t, err) + + idx := prepareLoadIndex( + t, + *mockIndexFactory, + ) + + err = subject.AddManifest( + context.TODO(), + "pack/index", + digest.Name(), + ManifestAddOptions{ + Features: []string{"some-features"}, + }, + ) + h.AssertNil(t, err) + + features, err := idx.Features(digest) + h.AssertNil(t, err) + h.AssertEq(t, features, []string{"some-features"}) + }) + it("should add with osFeatures", func() { + digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") + h.AssertNil(t, err) + + idx := prepareLoadIndex( + t, + *mockIndexFactory, + ) + + err = subject.AddManifest( + context.TODO(), + "pack/index", + digest.Name(), + ManifestAddOptions{ + Features: []string{"some-os-features"}, + }, + ) + h.AssertNil(t, err) + + osFeatures, err := idx.Features(digest) + h.AssertNil(t, err) + h.AssertEq(t, osFeatures, []string{"some-os-features"}) + }) + it("should add with annotations", func() { + digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") + h.AssertNil(t, err) + + idx := prepareLoadIndex( + t, + *mockIndexFactory, + ) + + err = subject.AddManifest( + context.TODO(), + "pack/index", + digest.Name(), + ManifestAddOptions{ + Annotations: map[string]string{"some-key":"some-value"}, + }, + ) + h.AssertNil(t, err) + + annos, err := idx.Annotations(digest) + h.AssertNil(t, err) + h.AssertEq(t, annos, map[string]string{"some-key":"some-value"}) + }) + }) +} + +func prepareIndexWithoutLocallyExists(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) { + mockIndexFactory. + EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + Return(nil, errors.New("index not found locally")) +} + +func prepareLoadIndex(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) (imgutil.ImageIndex) { + idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) + h.AssertNil(t, err) + + mockIndexFactory. + EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + Return(idx, nil) + + return idx +} diff --git a/pkg/client/annotate_manifest_test.go b/pkg/client/annotate_manifest_test.go new file mode 100644 index 0000000000..dfd0efe621 --- /dev/null +++ b/pkg/client/annotate_manifest_test.go @@ -0,0 +1,380 @@ +package client + +import ( + "bytes" + "context" + "os" + "testing" + + "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" + "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestAnnotateManifest(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + spec.Run(t, "build", testAnnotateManifest, spec.Report(report.Terminal{})) +} + +func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { + var ( + mockController *gomock.Controller + mockIndexFactory *testmocks.MockIndexFactory + out bytes.Buffer + logger logging.Logger + subject *Client + err error + tmpDir string + ) + + when("#Add", func() { + it.Before(func() { + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) + when("successful when", func() { + it("should return an error if index doesn't exists locally", func() { + prepareIndexWithoutLocallyExists(t, *mockIndexFactory) + err = subject.AnnotateManifest( + context.TODO(), + "pack/index", + "pack/image", + ManifestAnnotateOptions{}, + ) + + h.AssertEq(t, err.Error(), "index not found locally") + }) + it("should set OS for given image", func() { + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + digest.Name(), + ManifestAnnotateOptions{ + OS: "some-os", + }, + ) + h.AssertNil(t, err) + + os, err := idx.OS(digest) + h.AssertNil(t, err) + h.AssertEq(t, os, "some-os") + }) + it("should set Arch for given image", func() { + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + digest.Name(), + ManifestAnnotateOptions{ + OSArch: "some-arch", + }, + ) + h.AssertNil(t, err) + + arch, err := idx.Architecture(digest) + h.AssertNil(t, err) + h.AssertEq(t, arch, "some-arch") + }) + it("should set Variant for given image", func() { + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + digest.Name(), + ManifestAnnotateOptions{ + OSVariant: "some-variant", + }, + ) + h.AssertNil(t, err) + + variant, err := idx.Variant(digest) + h.AssertNil(t, err) + h.AssertEq(t, variant, "some-variant") + }) + it("should set OSVersion for given image", func() { + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + digest.Name(), + ManifestAnnotateOptions{ + OSVersion: "some-osVersion", + }, + ) + h.AssertNil(t, err) + + osVersion, err := idx.OSVersion(digest) + h.AssertNil(t, err) + h.AssertEq(t, osVersion, "some-osVersion") + }) + it("should set Features for given image", func() { + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + digest.Name(), + ManifestAnnotateOptions{ + Features: []string{"some-features"}, + }, + ) + h.AssertNil(t, err) + + features, err := idx.Features(digest) + h.AssertNil(t, err) + h.AssertEq(t, features, []string{"some-features"}) + }) + it("should set OSFeatures for given image", func() { + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + digest.Name(), + ManifestAnnotateOptions{ + OSFeatures: []string{"some-osFeatures"}, + }, + ) + h.AssertNil(t, err) + + osFeatures, err := idx.OSFeatures(digest) + h.AssertNil(t, err) + h.AssertEq(t, osFeatures, []string{"some-osFeatures"}) + }) + it("should set URLs for given image", func() { + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + digest.Name(), + ManifestAnnotateOptions{ + URLs: []string{"some-urls"}, + }, + ) + h.AssertNil(t, err) + + urls, err := idx.URLs(digest) + h.AssertNil(t, err) + h.AssertEq(t, urls, []string{"some-urls"}) + }) + it("should set Annotations for given image", func() { + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + digest.Name(), + ManifestAnnotateOptions{ + Annotations: map[string]string{"some-key":"some-value"}, + }, + ) + h.AssertNil(t, err) + + annos, err := idx.Annotations(digest) + h.AssertNil(t, err) + h.AssertEq(t, annos, map[string]string{"some-key":"some-value"}) + }) + }) + when("return an error when", func() { + it("has no Index locally by given Name", func() { + prepareIndexWithoutLocallyExists(t, *mockIndexFactory) + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + "", + ManifestAnnotateOptions{}, + ) + h.AssertEq(t, err.Error(), "index not found locally") + }) + it("has no image with given digest for OS", func() { + prepareLoadIndex(t, *mockIndexFactory) + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + ManifestAnnotateOptions{ + OS: "some-os", + }, + ) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + }) + it("has no image with given digest for Arch", func() { + prepareLoadIndex(t, *mockIndexFactory) + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + ManifestAnnotateOptions{ + OSArch: "some-arch", + }, + ) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + }) + it("has no image with given digest for Variant", func() { + prepareLoadIndex(t, *mockIndexFactory) + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + ManifestAnnotateOptions{ + OSVariant: "some-variant", + }, + ) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + }) + it("has no image with given digest for osVersion", func() { + prepareLoadIndex(t, *mockIndexFactory) + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + ManifestAnnotateOptions{ + OSVersion: "some-osVersion", + }, + ) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + }) + it("has no image with given digest for Features", func() { + prepareLoadIndex(t, *mockIndexFactory) + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + ManifestAnnotateOptions{ + Features: []string{"some-features"}, + }, + ) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + }) + it("has no image with given digest for OSFeatures", func() { + prepareLoadIndex(t, *mockIndexFactory) + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + ManifestAnnotateOptions{ + OSFeatures: []string{"some-osFeatures"}, + }, + ) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + }) + it("has no image with given digest for URLs", func() { + prepareLoadIndex(t, *mockIndexFactory) + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + ManifestAnnotateOptions{ + URLs: []string{"some-urls"}, + }, + ) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + }) + it("has no image with given digest for Annotations", func() { + prepareLoadIndex(t, *mockIndexFactory) + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + ManifestAnnotateOptions{ + Annotations: map[string]string{"some-key":"some-value"}, + }, + ) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + }) + }) + }) +} diff --git a/pkg/client/create_manifest_test.go b/pkg/client/create_manifest_test.go index 71f85510ca..51c5cc7d55 100644 --- a/pkg/client/create_manifest_test.go +++ b/pkg/client/create_manifest_test.go @@ -38,76 +38,8 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { subject *Client err error tmpDir string - // fakeIndex *fakes.Index ) when("#CreateManifest", func() { - var ( - // xdgPath = "xdgPath" - // ops = []index.Option{ - // index.WithKeychain(authn.DefaultKeychain), - // index.WithXDGRuntimePath(xdgPath), - // } - prepareMockImageFactoryForValidCreateIndex = func() { - idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) - h.AssertNil(t, err) - - mockIndexFactory.EXPECT(). - CreateIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(idx, err) - mockIndexFactory.EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - After( - mockIndexFactory.EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - Times(1). - Return( - imgutil.ImageIndex(nil), - errors.New("no image exists"), - ), - ). - Return(idx, err) - } - - prepareMockImageFactoryForValidCreateIndexWithAll = func() { - idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}, fakes.WithIndex(true)) - h.AssertNil(t, err) - - mockIndexFactory.EXPECT(). - CreateIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(idx, err) - mockIndexFactory.EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - After( - mockIndexFactory.EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - Times(1). - Return( - imgutil.ImageIndex(nil), - errors.New("no image exists"), - ), - ). - Return(idx, err) - } - - prepareMockImageFactoryForInvalidCreateIndexExistsLoadIndex = func() { - idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) - h.AssertNil(t, err) - - mockIndexFactory.EXPECT(). - CreateIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(idx, err) - - mockIndexFactory.EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(idx, err) - } - ) it.Before(func() { logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) mockController = gomock.NewController(t) @@ -126,57 +58,116 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { mockController.Finish() h.AssertNil(t, os.RemoveAll(tmpDir)) }) - when("should", func() { - it("create manifest", func() { - prepareMockImageFactoryForValidCreateIndex() - err := subject.CreateManifest( - context.TODO(), - "pack/imgutil", - []string{"busybox:1.36-musl"}, - CreateManifestOptions{ - Insecure: true, - }, - ) - h.AssertNil(t, err) - }) - it("create manifests ignoring all option", func() { - prepareMockImageFactoryForValidCreateIndex() - err := subject.CreateManifest( - context.TODO(), - "pack/imgutil", - []string{"busybox:1.36-musl"}, - CreateManifestOptions{ - Insecure: true, - All: true, - }, - ) - h.AssertNil(t, err) - }) - it("create manifests with all nested images", func() { - prepareMockImageFactoryForValidCreateIndexWithAll() - err := subject.CreateManifest( - context.TODO(), - "pack/imgutil", - []string{"busybox:1.36-musl"}, - CreateManifestOptions{ - Insecure: true, - All: true, - }, - ) - h.AssertNil(t, err) - }) - it("return an error when index exists already", func() { - prepareMockImageFactoryForInvalidCreateIndexExistsLoadIndex() - err := subject.CreateManifest( - context.TODO(), - "pack/imgutil", - []string{"busybox:1.36-musl"}, - CreateManifestOptions{ - Insecure: true, - }, - ) - h.AssertNil(t, err) - }) + it("create manifest", func() { + prepareMockImageFactoryForValidCreateIndex(t, mockIndexFactory) + err := subject.CreateManifest( + context.TODO(), + "pack/imgutil", + []string{"busybox:1.36-musl"}, + CreateManifestOptions{ + Insecure: true, + }, + ) + h.AssertNil(t, err) + }) + it("create manifests ignoring all option", func() { + prepareMockImageFactoryForValidCreateIndex(t, mockIndexFactory) + err := subject.CreateManifest( + context.TODO(), + "pack/imgutil", + []string{"busybox:1.36-musl"}, + CreateManifestOptions{ + Insecure: true, + All: true, + }, + ) + h.AssertNil(t, err) + }) + it("create manifests with all nested images", func() { + prepareMockImageFactoryForValidCreateIndexWithAll(t, mockIndexFactory) + err := subject.CreateManifest( + context.TODO(), + "pack/imgutil", + []string{"busybox:1.36-musl"}, + CreateManifestOptions{ + Insecure: true, + All: true, + }, + ) + h.AssertNil(t, err) + }) + it("return an error when index exists already", func() { + prepareMockImageFactoryForInvalidCreateIndexExistsLoadIndex(t, mockIndexFactory) + err := subject.CreateManifest( + context.TODO(), + "pack/imgutil", + []string{"busybox:1.36-musl"}, + CreateManifestOptions{ + Insecure: true, + }, + ) + h.AssertEq(t, err.Error(), "exits in your local storage, use 'pack manifest remove' if you want to delete it") }) }) } + +func prepareMockImageFactoryForValidCreateIndex(t *testing.T, mockIndexFactory *testmocks.MockIndexFactory) { + idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) + h.AssertNil(t, err) + + mockIndexFactory.EXPECT(). + CreateIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(idx, err) + mockIndexFactory.EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + After( + mockIndexFactory.EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + Times(1). + Return( + imgutil.ImageIndex(nil), + errors.New("no image exists"), + ), + ). + Return(idx, err) +} + +func prepareMockImageFactoryForValidCreateIndexWithAll(t *testing.T, mockIndexFactory *testmocks.MockIndexFactory) { + idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) + h.AssertNil(t, err) + + mockIndexFactory.EXPECT(). + CreateIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(idx, err) + mockIndexFactory.EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + After( + mockIndexFactory.EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + Times(1). + Return( + imgutil.ImageIndex(nil), + errors.New("no image exists"), + ), + ). + Return(idx, err) +} + +func prepareMockImageFactoryForInvalidCreateIndexExistsLoadIndex(t *testing.T, mockIndexFactory *testmocks.MockIndexFactory) { + idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) + h.AssertNil(t, err) + + mockIndexFactory.EXPECT(). + CreateIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(idx, err) + + mockIndexFactory.EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(idx, err) +} diff --git a/pkg/client/docker.go b/pkg/client/docker.go index 5c25471092..dfdf2c6139 100644 --- a/pkg/client/docker.go +++ b/pkg/client/docker.go @@ -31,5 +31,4 @@ type DockerClient interface { ContainerWait(ctx context.Context, container string, condition containertypes.WaitCondition) (<-chan containertypes.WaitResponse, <-chan error) ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error - ServerVersion(ctx context.Context) (types.Version, error) } diff --git a/pkg/client/inspect_manifest_test.go b/pkg/client/inspect_manifest_test.go new file mode 100644 index 0000000000..5069dae3a5 --- /dev/null +++ b/pkg/client/inspect_manifest_test.go @@ -0,0 +1,111 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "os" + "testing" + + "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" + "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/authn" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestInspectManifest(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + spec.Run(t, "build", testInspectManifest, spec.Report(report.Terminal{})) +} + +func testInspectManifest(t *testing.T, when spec.G, it spec.S) { + var ( + mockController *gomock.Controller + mockIndexFactory *testmocks.MockIndexFactory + out bytes.Buffer + logger logging.Logger + subject *Client + err error + tmpDir string + ) + + when("#Add", func() { + it.Before(func() { + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) + it("should return an error when index not found", func() { + prepareFindIndexWithError(t, *mockIndexFactory) + + err := subject.InspectManifest( + context.TODO(), + "some/name", + ) + h.AssertEq(t, err.Error(), "index not found") + }) + it("should return formatted IndexManifest", func() { + idx := prepareFindIndex(t, *mockIndexFactory) + err := subject.InspectManifest( + context.TODO(), + "some/name", + ) + h.AssertNil(t, err) + + ii, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := ii.IndexManifest() + h.AssertNil(t, err) + h.AssertNotNil(t, mfest) + + mfestBytes, err := json.MarshalIndent(mfest, "", " ") + h.AssertNil(t, err) + h.AssertEq(t, mfestBytes, out.Bytes()) + }) + }) +} + +func prepareFindIndexWithError(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) { + mockIndexFactory. + EXPECT(). + FindIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil, errors.New("index not found")) +} + +func prepareFindIndex(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) (imgutil.ImageIndex) { + idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) + h.AssertNil(t, err) + + mockIndexFactory. + EXPECT(). + FindIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(idx, nil) + + return idx +} diff --git a/tools/go.sum b/tools/go.sum index 3511f5a3bf..3617a95580 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -72,6 +72,7 @@ github.com/ashanbrown/forbidigo v1.5.1/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1 github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -130,6 +131,7 @@ github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4 github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= @@ -146,6 +148,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= @@ -159,6 +162,7 @@ github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlN github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= +github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= @@ -269,6 +273,7 @@ github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3 github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= +github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -317,9 +322,11 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= github.com/kunwardeep/paralleltest v1.0.6 h1:FCKYMF1OF2+RveWlABsdnmsvJrei5aoyZoaGS+Ugg8g= @@ -375,6 +382,7 @@ github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4N github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.9.5 h1:TzssWan6orBiLYVqewCG8faud9qlFntJE30ACpzmGME= github.com/nishanths/exhaustive v0.9.5/go.mod h1:IbwrGdVMizvDcIxPYGVdQn5BqWJaOwpCvg4RGb8r/TA= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= @@ -384,7 +392,9 @@ github.com/nunnatsa/ginkgolinter v0.9.0/go.mod h1:FHaMLURXP7qImeH6bvxWJUpyH+2tuq github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI= +github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU= github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q= +github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= @@ -436,6 +446,7 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4l github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= @@ -547,6 +558,7 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= @@ -652,6 +664,7 @@ golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -934,6 +947,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= From 5afa60a9286e5e0652365df94df2ddff716fb0c4 Mon Sep 17 00:00:00 2001 From: WYGIN Date: Tue, 27 Feb 2024 11:04:19 +0000 Subject: [PATCH 50/79] added tests for client directive Signed-off-by: WYGIN --- go.mod | 2 +- go.sum | 4 +- pkg/client/add_manifest_test.go | 44 +++++++------- pkg/client/annotate_manifest_test.go | 79 +++++++++++++------------ pkg/client/inspect_manifest_test.go | 13 ++-- pkg/client/push_manifest_test.go | 77 ++++++++++++++++++++++++ pkg/client/remove_manifest_test.go | 72 +++++++++++++++++++++++ pkg/client/rm_manifest_test.go | 88 ++++++++++++++++++++++++++++ 8 files changed, 310 insertions(+), 69 deletions(-) create mode 100644 pkg/client/push_manifest_test.go create mode 100644 pkg/client/remove_manifest_test.go create mode 100644 pkg/client/rm_manifest_test.go diff --git a/go.mod b/go.mod index 47dec1ae88..2ead90c9f3 100644 --- a/go.mod +++ b/go.mod @@ -135,7 +135,7 @@ require ( go 1.21 replace ( - github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240214072401-d071ec2816c6 + github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240219150011-2f201867f6c1 // Pin moby/buildkit until docker/docker is upgraded github.com/moby/buildkit => github.com/moby/buildkit v0.11.6 diff --git a/go.sum b/go.sum index 580486a5a8..76051e6297 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7 github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240214072401-d071ec2816c6 h1:ezBkLrGhz9n8AEaBcVmpeVE3/aLX/FOoCbC6pSAU/3U= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240214072401-d071ec2816c6/go.mod h1:7zUmt4wkVJNuXCZhQndEd3kvVGmWLVyzRFIQXTaeXlU= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240219150011-2f201867f6c1 h1:bjszsCM4clSyyZpOSlBwg++kUj+C28bSF+eKVKoCxvA= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240219150011-2f201867f6c1/go.mod h1:7zUmt4wkVJNuXCZhQndEd3kvVGmWLVyzRFIQXTaeXlU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= diff --git a/pkg/client/add_manifest_test.go b/pkg/client/add_manifest_test.go index 3b15004fcb..cdf316eabf 100644 --- a/pkg/client/add_manifest_test.go +++ b/pkg/client/add_manifest_test.go @@ -9,9 +9,6 @@ import ( "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/fakes" - "github.com/buildpacks/pack/pkg/logging" - "github.com/buildpacks/pack/pkg/testmocks" - h "github.com/buildpacks/pack/testhelpers" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" @@ -20,6 +17,10 @@ import ( "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" + + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" ) func TestAddManifest(t *testing.T) { @@ -90,7 +91,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) idx := prepareLoadIndex( - t, + t, *mockIndexFactory, ) @@ -99,7 +100,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { "pack/index", digest.Name(), ManifestAddOptions{ - OS: "some-os", + OS: "some-os", OSArch: "some-arch", }, ) @@ -118,8 +119,8 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) idx := prepareLoadIndex( - t, - *mockIndexFactory, + t, + *mockIndexFactory, ) err = subject.AddManifest( @@ -141,8 +142,8 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) idx := prepareLoadIndex( - t, - *mockIndexFactory, + t, + *mockIndexFactory, ) err = subject.AddManifest( @@ -164,8 +165,8 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) idx := prepareLoadIndex( - t, - *mockIndexFactory, + t, + *mockIndexFactory, ) err = subject.AddManifest( @@ -187,8 +188,8 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) idx := prepareLoadIndex( - t, - *mockIndexFactory, + t, + *mockIndexFactory, ) err = subject.AddManifest( @@ -210,8 +211,8 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) idx := prepareLoadIndex( - t, - *mockIndexFactory, + t, + *mockIndexFactory, ) err = subject.AddManifest( @@ -219,33 +220,34 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { "pack/index", digest.Name(), ManifestAddOptions{ - Annotations: map[string]string{"some-key":"some-value"}, + Annotations: map[string]string{"some-key": "some-value"}, }, ) h.AssertNil(t, err) annos, err := idx.Annotations(digest) h.AssertNil(t, err) - h.AssertEq(t, annos, map[string]string{"some-key":"some-value"}) + h.AssertEq(t, annos, map[string]string{"some-key": "some-value"}) }) }) } -func prepareIndexWithoutLocallyExists(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) { +func prepareIndexWithoutLocallyExists(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) { mockIndexFactory. EXPECT(). LoadIndex(gomock.Any(), gomock.Any()). Return(nil, errors.New("index not found locally")) } -func prepareLoadIndex(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) (imgutil.ImageIndex) { +func prepareLoadIndex(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) h.AssertNil(t, err) - + mockIndexFactory. EXPECT(). LoadIndex(gomock.Any(), gomock.Any()). - Return(idx, nil) + Return(idx, nil). + AnyTimes() return idx } diff --git a/pkg/client/annotate_manifest_test.go b/pkg/client/annotate_manifest_test.go index dfd0efe621..d965a1a774 100644 --- a/pkg/client/annotate_manifest_test.go +++ b/pkg/client/annotate_manifest_test.go @@ -8,15 +8,16 @@ import ( "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/fakes" - "github.com/buildpacks/pack/pkg/logging" - "github.com/buildpacks/pack/pkg/testmocks" - h "github.com/buildpacks/pack/testhelpers" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" + + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" ) func TestAnnotateManifest(t *testing.T) { @@ -64,20 +65,20 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { "pack/image", ManifestAnnotateOptions{}, ) - + h.AssertEq(t, err.Error(), "index not found locally") }) it("should set OS for given image", func() { idx := prepareLoadIndex(t, *mockIndexFactory) imgIdx, ok := idx.(*fakes.Index) h.AssertEq(t, ok, true) - + mfest, err := imgIdx.IndexManifest() h.AssertNil(t, err) - + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) h.AssertNil(t, err) - + err = subject.AnnotateManifest( context.TODO(), "some/repo", @@ -87,7 +88,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }, ) h.AssertNil(t, err) - + os, err := idx.OS(digest) h.AssertNil(t, err) h.AssertEq(t, os, "some-os") @@ -96,13 +97,13 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { idx := prepareLoadIndex(t, *mockIndexFactory) imgIdx, ok := idx.(*fakes.Index) h.AssertEq(t, ok, true) - + mfest, err := imgIdx.IndexManifest() h.AssertNil(t, err) - + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) h.AssertNil(t, err) - + err = subject.AnnotateManifest( context.TODO(), "some/repo", @@ -112,7 +113,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }, ) h.AssertNil(t, err) - + arch, err := idx.Architecture(digest) h.AssertNil(t, err) h.AssertEq(t, arch, "some-arch") @@ -121,13 +122,13 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { idx := prepareLoadIndex(t, *mockIndexFactory) imgIdx, ok := idx.(*fakes.Index) h.AssertEq(t, ok, true) - + mfest, err := imgIdx.IndexManifest() h.AssertNil(t, err) - + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) h.AssertNil(t, err) - + err = subject.AnnotateManifest( context.TODO(), "some/repo", @@ -137,7 +138,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }, ) h.AssertNil(t, err) - + variant, err := idx.Variant(digest) h.AssertNil(t, err) h.AssertEq(t, variant, "some-variant") @@ -146,13 +147,13 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { idx := prepareLoadIndex(t, *mockIndexFactory) imgIdx, ok := idx.(*fakes.Index) h.AssertEq(t, ok, true) - + mfest, err := imgIdx.IndexManifest() h.AssertNil(t, err) - + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) h.AssertNil(t, err) - + err = subject.AnnotateManifest( context.TODO(), "some/repo", @@ -162,7 +163,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }, ) h.AssertNil(t, err) - + osVersion, err := idx.OSVersion(digest) h.AssertNil(t, err) h.AssertEq(t, osVersion, "some-osVersion") @@ -171,13 +172,13 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { idx := prepareLoadIndex(t, *mockIndexFactory) imgIdx, ok := idx.(*fakes.Index) h.AssertEq(t, ok, true) - + mfest, err := imgIdx.IndexManifest() h.AssertNil(t, err) - + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) h.AssertNil(t, err) - + err = subject.AnnotateManifest( context.TODO(), "some/repo", @@ -187,7 +188,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }, ) h.AssertNil(t, err) - + features, err := idx.Features(digest) h.AssertNil(t, err) h.AssertEq(t, features, []string{"some-features"}) @@ -196,13 +197,13 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { idx := prepareLoadIndex(t, *mockIndexFactory) imgIdx, ok := idx.(*fakes.Index) h.AssertEq(t, ok, true) - + mfest, err := imgIdx.IndexManifest() h.AssertNil(t, err) - + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) h.AssertNil(t, err) - + err = subject.AnnotateManifest( context.TODO(), "some/repo", @@ -212,7 +213,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }, ) h.AssertNil(t, err) - + osFeatures, err := idx.OSFeatures(digest) h.AssertNil(t, err) h.AssertEq(t, osFeatures, []string{"some-osFeatures"}) @@ -221,13 +222,13 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { idx := prepareLoadIndex(t, *mockIndexFactory) imgIdx, ok := idx.(*fakes.Index) h.AssertEq(t, ok, true) - + mfest, err := imgIdx.IndexManifest() h.AssertNil(t, err) - + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) h.AssertNil(t, err) - + err = subject.AnnotateManifest( context.TODO(), "some/repo", @@ -237,7 +238,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }, ) h.AssertNil(t, err) - + urls, err := idx.URLs(digest) h.AssertNil(t, err) h.AssertEq(t, urls, []string{"some-urls"}) @@ -246,26 +247,26 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { idx := prepareLoadIndex(t, *mockIndexFactory) imgIdx, ok := idx.(*fakes.Index) h.AssertEq(t, ok, true) - + mfest, err := imgIdx.IndexManifest() h.AssertNil(t, err) - + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) h.AssertNil(t, err) - + err = subject.AnnotateManifest( context.TODO(), "some/repo", digest.Name(), ManifestAnnotateOptions{ - Annotations: map[string]string{"some-key":"some-value"}, + Annotations: map[string]string{"some-key": "some-value"}, }, ) h.AssertNil(t, err) - + annos, err := idx.Annotations(digest) h.AssertNil(t, err) - h.AssertEq(t, annos, map[string]string{"some-key":"some-value"}) + h.AssertEq(t, annos, map[string]string{"some-key": "some-value"}) }) }) when("return an error when", func() { @@ -370,7 +371,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { "some/repo", "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", ManifestAnnotateOptions{ - Annotations: map[string]string{"some-key":"some-value"}, + Annotations: map[string]string{"some-key": "some-value"}, }, ) h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) diff --git a/pkg/client/inspect_manifest_test.go b/pkg/client/inspect_manifest_test.go index 5069dae3a5..08b2732a78 100644 --- a/pkg/client/inspect_manifest_test.go +++ b/pkg/client/inspect_manifest_test.go @@ -10,9 +10,6 @@ import ( "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/fakes" - "github.com/buildpacks/pack/pkg/logging" - "github.com/buildpacks/pack/pkg/testmocks" - h "github.com/buildpacks/pack/testhelpers" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -20,6 +17,10 @@ import ( "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" + + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" ) func TestInspectManifest(t *testing.T) { @@ -89,7 +90,7 @@ func testInspectManifest(t *testing.T, when spec.G, it spec.S) { }) } -func prepareFindIndexWithError(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) { +func prepareFindIndexWithError(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) { mockIndexFactory. EXPECT(). FindIndex(gomock.Any(), gomock.Any()). @@ -97,10 +98,10 @@ func prepareFindIndexWithError(t *testing.T, mockIndexFactory testmocks.MockInde Return(nil, errors.New("index not found")) } -func prepareFindIndex(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) (imgutil.ImageIndex) { +func prepareFindIndex(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) h.AssertNil(t, err) - + mockIndexFactory. EXPECT(). FindIndex(gomock.Any(), gomock.Any()). diff --git a/pkg/client/push_manifest_test.go b/pkg/client/push_manifest_test.go new file mode 100644 index 0000000000..19837c16da --- /dev/null +++ b/pkg/client/push_manifest_test.go @@ -0,0 +1,77 @@ +package client + +import ( + "bytes" + "context" + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/imgutil" + + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestPushManifest(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + spec.Run(t, "build", testPushManifest, spec.Report(report.Terminal{})) +} + +func testPushManifest(t *testing.T, when spec.G, it spec.S) { + var ( + mockController *gomock.Controller + mockIndexFactory *testmocks.MockIndexFactory + out bytes.Buffer + logger logging.Logger + subject *Client + err error + tmpDir string + ) + when("#Push", func() { + it.Before(func() { + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) + it("should not have local image index", func() { + prepareLoadIndexWithError(t, *mockIndexFactory) + + err := subject.PushManifest(context.TODO(), "some-index", PushManifestOptions{}) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + }) + it("should push index to registry", func() { + prepareLoadIndex(t, *mockIndexFactory) + + err := subject.PushManifest(context.TODO(), "some-index", PushManifestOptions{}) + h.AssertNil(t, err) + }) + }) +} + +func prepareLoadIndexWithError(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) { + mockIndexFactory. + EXPECT(). + LoadIndex(gomock.Any(), gomock.Any()). + Return(nil, imgutil.ErrNoImageOrIndexFoundWithGivenDigest) +} diff --git a/pkg/client/remove_manifest_test.go b/pkg/client/remove_manifest_test.go new file mode 100644 index 0000000000..b919032fef --- /dev/null +++ b/pkg/client/remove_manifest_test.go @@ -0,0 +1,72 @@ +package client + +import ( + "bytes" + "context" + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestDeleteManifest(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + spec.Run(t, "build", testDeleteManifest, spec.Report(report.Terminal{})) +} + +func testDeleteManifest(t *testing.T, when spec.G, it spec.S) { + var ( + mockController *gomock.Controller + mockIndexFactory *testmocks.MockIndexFactory + out bytes.Buffer + logger logging.Logger + subject *Client + err error + tmpDir string + ) + + when("#Add", func() { + it.Before(func() { + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) + it("should delete local index", func() { + prepareLoadIndex(t, *mockIndexFactory) + + errs := subject.DeleteManifest(context.TODO(), []string{"some-index"}) + h.AssertEq(t, len(errs), 0) + }) + it("should return an error when index is already deleted", func() { + prepareLoadIndex(t, *mockIndexFactory) + + errs := subject.DeleteManifest(context.TODO(), []string{"some-index"}) + h.AssertEq(t, len(errs), 0) + + errs = subject.DeleteManifest(context.TODO(), []string{"some-index"}) + h.AssertNotEq(t, len(errs), 0) + }) + }) +} diff --git a/pkg/client/rm_manifest_test.go b/pkg/client/rm_manifest_test.go new file mode 100644 index 0000000000..c732f0a987 --- /dev/null +++ b/pkg/client/rm_manifest_test.go @@ -0,0 +1,88 @@ +package client + +import ( + "bytes" + "context" + "os" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/imgutil/fakes" + + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestRemoveManifest(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + spec.Run(t, "build", testRemoveManifest, spec.Report(report.Terminal{})) +} + +func testRemoveManifest(t *testing.T, when spec.G, it spec.S) { + var ( + mockController *gomock.Controller + mockIndexFactory *testmocks.MockIndexFactory + out bytes.Buffer + logger logging.Logger + subject *Client + err error + tmpDir string + ) + + when("#Add", func() { + it.Before(func() { + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) + it("should remove local index", func() { + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + errs := subject.RemoveManifest(context.TODO(), "some-index", []string{digest.Identifier()}) + h.AssertEq(t, len(errs), 0) + }) + it("should remove image", func() { + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + errs := subject.RemoveManifest(context.TODO(), "some-index", []string{digest.Identifier()}) + h.AssertEq(t, len(errs), 0) + }) + }) +} From 6d65501144bcccd78366c0621aec20af6a17ae38 Mon Sep 17 00:00:00 2001 From: WYGIN Date: Wed, 28 Feb 2024 09:46:46 +0000 Subject: [PATCH 51/79] added tests in commands package Signed-off-by: WYGIN Date: Wed Feb 28 09:46:46 2024 +0000 Changes to be committed: modified: internal/commands/manifest.go modified: internal/commands/manifest_add.go new file: internal/commands/manifest_add_test.go modified: internal/commands/manifest_annotate.go new file: internal/commands/manifest_annotate_test.go modified: internal/commands/manifest_create.go new file: internal/commands/manifest_create_test.go modified: internal/commands/manifest_exists.go new file: internal/commands/manifest_exists_test.go modified: internal/commands/manifest_inspect.go new file: internal/commands/manifest_inspect_test.go modified: internal/commands/manifest_push.go new file: internal/commands/manifest_push_test.go modified: internal/commands/manifest_remove.go new file: internal/commands/manifest_remove_test.go modified: internal/commands/manifest_rm.go new file: internal/commands/manifest_rm_test.go new file: internal/commands/manifest_test.go --- internal/commands/manifest.go | 2 +- internal/commands/manifest_add.go | 18 ++- internal/commands/manifest_add_test.go | 133 ++++++++++++++++++++ internal/commands/manifest_annotate.go | 14 ++- internal/commands/manifest_annotate_test.go | 116 +++++++++++++++++ internal/commands/manifest_create.go | 20 ++- internal/commands/manifest_create_test.go | 88 +++++++++++++ internal/commands/manifest_exists.go | 4 - internal/commands/manifest_exists_test.go | 88 +++++++++++++ internal/commands/manifest_inspect.go | 8 +- internal/commands/manifest_inspect_test.go | 75 +++++++++++ internal/commands/manifest_push.go | 5 +- internal/commands/manifest_push_test.go | 93 ++++++++++++++ internal/commands/manifest_remove.go | 40 +++--- internal/commands/manifest_remove_test.go | 89 +++++++++++++ internal/commands/manifest_rm.go | 18 +-- internal/commands/manifest_rm_test.go | 92 ++++++++++++++ internal/commands/manifest_test.go | 55 ++++++++ 18 files changed, 900 insertions(+), 58 deletions(-) create mode 100644 internal/commands/manifest_add_test.go create mode 100644 internal/commands/manifest_annotate_test.go create mode 100644 internal/commands/manifest_create_test.go create mode 100644 internal/commands/manifest_exists_test.go create mode 100644 internal/commands/manifest_inspect_test.go create mode 100644 internal/commands/manifest_push_test.go create mode 100644 internal/commands/manifest_remove_test.go create mode 100644 internal/commands/manifest_rm_test.go create mode 100644 internal/commands/manifest_test.go diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index f244f27939..6ec1367d81 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -9,7 +9,7 @@ import ( func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "manifest", - Short: "Interact with image index or manifest list", + Short: "Interact with image index", RunE: nil, } diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index aa82429113..c7a88d2c29 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -1,6 +1,7 @@ package commands import ( + "errors" "fmt" "strings" @@ -72,6 +73,7 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().StringVar(&flags.os, "os", "", "Set the operating system") cmd.Flags().StringVar(&flags.osArch, "arch", "", "Set the architecture") cmd.Flags().StringVar(&flags.osVariant, "variant", "", "Set the architecture variant") + cmd.Flags().StringVar(&flags.osVersion, "os-version", "", "Set the os-version") cmd.Flags().StringVar(&flags.osFeatures, "os-features", "", "Set the OSFeatures") cmd.Flags().StringVar(&flags.features, "features", "", "Set the Features") cmd.Flags().StringVar(&flags.annotations, "annotations", "", "Set the annotations") @@ -81,16 +83,18 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { } func validateManifestAddFlags(flags ManifestAddFlags) error { + if (flags.os != "" && flags.osArch == "") || (flags.os == "" && flags.osArch != "") { + return errors.New("'os' or 'arch' is undefined") + } return nil } func StringToKeyValueMap(s string) (map[string]string, error) { keyValues := strings.Split(s, ";") - m := map[string]string{} - + var annosMap = make(map[string]string) for _, keyValue := range keyValues { - parts := strings.Split(keyValue, "=") + parts := strings.SplitN(keyValue, "=", 2) if len(parts) != 2 { return nil, fmt.Errorf("invalid key-value pair: %s", keyValue) } @@ -98,8 +102,12 @@ func StringToKeyValueMap(s string) (map[string]string, error) { key := parts[0] value := parts[1] - m[key] = value + if key == "" || value == "" { + return nil, fmt.Errorf("key(%s) or value(%s) is undefined", key, value) + } + + annosMap[key] = value } - return m, nil + return annosMap, nil } diff --git a/internal/commands/manifest_add_test.go b/internal/commands/manifest_add_test.go new file mode 100644 index 0000000000..2232ed1e28 --- /dev/null +++ b/internal/commands/manifest_add_test.go @@ -0,0 +1,133 @@ +package commands_test + +import ( + "bytes" + "testing" + + "github.com/golang/mock/gomock" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/internal/commands" + "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/logging" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestManifestAddCommand(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + + spec.Run(t, "Commands", testManifestAddCommand, spec.Random(), spec.Report(report.Terminal{})) +} + +func testManifestAddCommand(t *testing.T, when spec.G, it spec.S) { + var ( + command *cobra.Command + logger *logging.LogWithWriters + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + ) + + it.Before(func() { + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + mockController = gomock.NewController(t) + mockClient = testmocks.NewMockPackClient(mockController) + + command = commands.ManifestAdd(logger, mockClient) + }) + it("should add image with current platform specs", func() { + prepareAddManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox:1.36-musl"}) + err := command.Execute() + h.AssertNil(t, err) + h.AssertEq(t, outBuf.String(), "") + }) + it("should add images with given platform", func() { + prepareAddManifest(t, mockClient) + + command.SetArgs([]string{ + "some-index", + "busybox:1.36-musl", + "--os", + "linux", + "--arch", + "arm", + "--variant", + "v6", + "--os-version", + "22.04", + }) + err := command.Execute() + h.AssertNil(t, err) + h.AssertEq(t, outBuf.String(), "") + }) + it("should add return an error when platform's os and arch not defined", func() { + prepareAddManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--os", "linux"}) + err := command.Execute() + h.AssertEq(t, err.Error(), "'os' or 'arch' is undefined") + h.AssertEq(t, outBuf.String(), "ERROR: 'os' or 'arch' is undefined\n") + }) + it("should add all images", func() { + prepareAddManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--all"}) + err := command.Execute() + h.AssertNil(t, err) + h.AssertEq(t, outBuf.String(), "") + }) + it("should return an error when features defined invalidly", func() { + prepareAddManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--features"}) + err := command.Execute() + h.AssertEq(t, err.Error(), "flag needs an argument: --features") + }) + it("should return an error when osFeatures defined invalidly", func() { + prepareAddManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--os-features"}) + err := command.Execute() + h.AssertEq(t, err.Error(), "flag needs an argument: --os-features") + }) + it("should return an error when invalid arg passed", func() { + prepareAddManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--urls"}) + err := command.Execute() + h.AssertEq(t, err.Error(), "unknown flag: --urls") + }) + it("should return an error when annotations defined invalidly", func() { + prepareAddManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--annotations", "some-key="}) + err := command.Execute() + h.AssertEq(t, err.Error(), "key(some-key) or value() is undefined") + }) + it("should have help flag", func() { + prepareAddManifest(t, mockClient) + + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) +} + +func prepareAddManifest(t *testing.T, mockClient *testmocks.MockPackClient) { + mockClient. + EXPECT(). + AddManifest( + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + AnyTimes(). + Return(nil) +} diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index cc9f45b065..3341e4f11a 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -3,6 +3,7 @@ package commands import ( "strings" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -20,7 +21,7 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestAnnotateFlags cmd := &cobra.Command{ - Use: "annotate [OPTIONS] [...] [flags]", + Use: "annotate [OPTIONS] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), Short: "Add or update information about an entry in a manifest list or image index.", Example: `pack manifest annotate cnbs/sample-package:hello-universe-multiarch \ @@ -34,6 +35,10 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { urls = make([]string, 0) ) + if err := validateManifestAnnotateFlags(flags); err != nil { + return err + } + if flags.features != "" { features = strings.Split(flags.features, ";") } @@ -78,3 +83,10 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { AddHelpFlag(cmd, "annotate") return cmd } + +func validateManifestAnnotateFlags(flags ManifestAnnotateFlags) error { + if (flags.os != "" && flags.arch == "") || (flags.os == "" && flags.arch != "") { + return errors.New("'os' or 'arch' is undefined") + } + return nil +} diff --git a/internal/commands/manifest_annotate_test.go b/internal/commands/manifest_annotate_test.go new file mode 100644 index 0000000000..3d2de44728 --- /dev/null +++ b/internal/commands/manifest_annotate_test.go @@ -0,0 +1,116 @@ +package commands_test + +import ( + "bytes" + "testing" + + "github.com/golang/mock/gomock" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/internal/commands" + "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/logging" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestManifestAnnotationsCommand(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + + spec.Run(t, "Commands", testManifestAnnotateCommand, spec.Random(), spec.Report(report.Terminal{})) +} + +func testManifestAnnotateCommand(t *testing.T, when spec.G, it spec.S) { + var ( + command *cobra.Command + logger *logging.LogWithWriters + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + ) + + it.Before(func() { + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + mockController = gomock.NewController(t) + mockClient = testmocks.NewMockPackClient(mockController) + + command = commands.ManifestAnnotate(logger, mockClient) + }) + it("should annotate images with given flags", func() { + prepareAnnotateManifest(t, mockClient) + + command.SetArgs([]string{ + "some-index", + "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", + "--os", + "linux", + "--arch", + "arm", + "--variant", + "v6", + "--os-version", + "22.04", + }) + h.AssertNil(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) + it("should return an error when platform's os and arch not defined", func() { + prepareAnnotateManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--os", "linux"}) + err := command.Execute() + h.AssertEq(t, err.Error(), "'os' or 'arch' is undefined") + h.AssertEq(t, outBuf.String(), "ERROR: 'os' or 'arch' is undefined\n") + }) + it("should return an error when features defined invalidly", func() { + prepareAnnotateManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--features"}) + err := command.Execute() + h.AssertEq(t, err.Error(), "flag needs an argument: --features") + }) + it("should return an error when osFeatures defined invalidly", func() { + prepareAnnotateManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--os-features"}) + err := command.Execute() + h.AssertEq(t, err.Error(), "flag needs an argument: --os-features") + }) + it("should return an error when urls defined invalidly", func() { + prepareAnnotateManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--urls"}) + err := command.Execute() + h.AssertEq(t, err.Error(), "flag needs an argument: --urls") + }) + it("should return an error when annotations defined invalidly", func() { + prepareAnnotateManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--annotations", "some-key="}) + err := command.Execute() + h.AssertEq(t, err.Error(), "key(some-key) or value() is undefined") + }) + it("should have help flag", func() { + prepareAnnotateManifest(t, mockClient) + + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) +} + +func prepareAnnotateManifest(t *testing.T, mockClient *testmocks.MockPackClient) { + mockClient. + EXPECT(). + AnnotateManifest( + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + AnyTimes(). + Return(nil) +} diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 576e5dca81..e04e34b976 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -3,6 +3,7 @@ package commands import ( "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -11,8 +12,8 @@ import ( // ManifestCreateFlags define flags provided to the ManifestCreate type ManifestCreateFlags struct { - format, registry, os, arch string - insecure, publish, all, amend bool + format, os, arch string + insecure, publish, all bool } // ManifestCreate creates an image-index/image-list for a multi-arch image @@ -30,13 +31,17 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { RunE: logError(logger, func(cmd *cobra.Command, args []string) error { imageIndex := args[0] manifests := args[1:] + + if err := validateManifestCreateFlags(flags); err != nil { + return err + } + return pack.CreateManifest( cmd.Context(), imageIndex, manifests, client.CreateManifestOptions{ Format: flags.format, - Registry: flags.registry, Insecure: flags.insecure, Publish: flags.publish, All: flags.all, @@ -48,7 +53,6 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmdFlags := cmd.Flags() cmdFlags.StringVarP(&flags.format, "format", "f", "v2s2", "Format to save image index as ('OCI' or 'V2S2')") - cmdFlags.StringVarP(&flags.registry, "registry", "r", "", "Publish to registry") cmdFlags.StringVar(&flags.os, "os", "", "If any of the specified images is a list/index, choose the one for `os`") if err := cmdFlags.MarkHidden("os"); err != nil { panic(fmt.Sprintf("error marking --os as hidden: %v", err)) @@ -63,8 +67,14 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { } cmdFlags.BoolVar(&flags.publish, "publish", false, "Publish to registry") cmdFlags.BoolVar(&flags.all, "all", false, "Add all of the list's images if the images to add are lists/index") - cmdFlags.BoolVar(&flags.amend, "amend", false, "Modify an existing list/index if one with the desired name already exists") AddHelpFlag(cmd, "create") return cmd } + +func validateManifestCreateFlags(flags ManifestCreateFlags) error { + if (flags.os != "" && flags.arch == "") || (flags.os == "" && flags.arch != "") { + return errors.New("'os' or 'arch' is undefined") + } + return nil +} diff --git a/internal/commands/manifest_create_test.go b/internal/commands/manifest_create_test.go new file mode 100644 index 0000000000..890bfe302f --- /dev/null +++ b/internal/commands/manifest_create_test.go @@ -0,0 +1,88 @@ +package commands_test + +import ( + "bytes" + "testing" + + "github.com/golang/mock/gomock" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/internal/commands" + "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/logging" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestManifestCreateCommand(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + + spec.Run(t, "Commands", testManifestCreateCommand, spec.Random(), spec.Report(report.Terminal{})) +} + +func testManifestCreateCommand(t *testing.T, when spec.G, it spec.S) { + var ( + command *cobra.Command + logger *logging.LogWithWriters + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + ) + + it.Before(func() { + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + mockController = gomock.NewController(t) + mockClient = testmocks.NewMockPackClient(mockController) + + command = commands.ManifestCreate(logger, mockClient) + }) + it("should annotate images with given flags", func() { + prepareCreateManifest(t, mockClient) + + command.SetArgs([]string{ + "some-index", + "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", + "--os", + "linux", + "--arch", + "arm", + "--format", + "v2s2", + "--insecure", + "--publish", + }) + h.AssertNil(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) + it("should return an error when platform's os and arch not defined", func() { + prepareCreateManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--os", "linux"}) + err := command.Execute() + h.AssertEq(t, err.Error(), "'os' or 'arch' is undefined") + h.AssertEq(t, outBuf.String(), "ERROR: 'os' or 'arch' is undefined\n") + }) + it("should have help flag", func() { + prepareCreateManifest(t, mockClient) + + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) +} + +func prepareCreateManifest(t *testing.T, mockClient *testmocks.MockPackClient) { + mockClient. + EXPECT(). + CreateManifest( + gomock.Any(), + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + AnyTimes(). + Return(nil) +} diff --git a/internal/commands/manifest_exists.go b/internal/commands/manifest_exists.go index 617f4602b7..7b022f274b 100644 --- a/internal/commands/manifest_exists.go +++ b/internal/commands/manifest_exists.go @@ -6,10 +6,6 @@ import ( "github.com/buildpacks/pack/pkg/logging" ) -// ManifestDeleteFlags define flags provided to the ManifestDelete -// type ManifestDeleteFlags struct { -// } - // ManifestExists checks if a manifest list exists in local storage func ManifestExists(logger logging.Logger, pack PackClient) *cobra.Command { // var flags ManifestDeleteFlags diff --git a/internal/commands/manifest_exists_test.go b/internal/commands/manifest_exists_test.go new file mode 100644 index 0000000000..7ee7081d97 --- /dev/null +++ b/internal/commands/manifest_exists_test.go @@ -0,0 +1,88 @@ +package commands_test + +import ( + "bytes" + "testing" + + "github.com/golang/mock/gomock" + "github.com/heroku/color" + "github.com/pkg/errors" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/internal/commands" + "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/logging" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestManifestExistsCommand(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + + spec.Run(t, "Commands", testManifestExistsCommand, spec.Random(), spec.Report(report.Terminal{})) +} + +func testManifestExistsCommand(t *testing.T, when spec.G, it spec.S) { + var ( + command *cobra.Command + logger *logging.LogWithWriters + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + ) + + it.Before(func() { + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + mockController = gomock.NewController(t) + mockClient = testmocks.NewMockPackClient(mockController) + + command = commands.ManifestExists(logger, mockClient) + }) + it("should annotate images with given flags", func() { + prepareExistsManifest(t, mockClient) + + command.SetArgs([]string{ + "some-index", + }) + h.AssertNil(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) + it("should return an error when index doesn't exists", func() { + prepareNotExistsManifest(t, mockClient) + + command.SetArgs([]string{"some-other-index"}) + err := command.Execute() + h.AssertEq(t, err.Error(), "no index found with given name") + }) + it("should have help flag", func() { + prepareExistsManifest(t, mockClient) + + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) +} + +func prepareExistsManifest(t *testing.T, mockClient *testmocks.MockPackClient) { + mockClient. + EXPECT(). + ExistsManifest( + gomock.Any(), + gomock.Any(), + ). + AnyTimes(). + Return(nil) +} + +func prepareNotExistsManifest(t *testing.T, mockClient *testmocks.MockPackClient) { + mockClient. + EXPECT(). + ExistsManifest( + gomock.Any(), + gomock.Any(), + ). + AnyTimes(). + Return(errors.New("no index found with given name")) +} diff --git a/internal/commands/manifest_inspect.go b/internal/commands/manifest_inspect.go index 7ddc053a33..6a61934787 100644 --- a/internal/commands/manifest_inspect.go +++ b/internal/commands/manifest_inspect.go @@ -6,16 +6,10 @@ import ( "github.com/buildpacks/pack/pkg/logging" ) -// ManifestInspectFlags define flags provided to the ManifestInspect -// type ManifestInspectFlags struct { -// } - // ManifestInspect shows the manifest information stored in local storage func ManifestInspect(logger logging.Logger, pack PackClient) *cobra.Command { - // var flags ManifestInspectFlags - cmd := &cobra.Command{ - Use: "inspect [flags]", + Use: "inspect ", Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), Short: "Display a manifest list or image index.", Example: `pack manifest inspect cnbs/sample-builder:multiarch`, diff --git a/internal/commands/manifest_inspect_test.go b/internal/commands/manifest_inspect_test.go new file mode 100644 index 0000000000..87c0b4f95f --- /dev/null +++ b/internal/commands/manifest_inspect_test.go @@ -0,0 +1,75 @@ +package commands_test + +import ( + "bytes" + "testing" + + "github.com/golang/mock/gomock" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/internal/commands" + "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/logging" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestManifestInspectCommand(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + + spec.Run(t, "Commands", testManifestInspectCommand, spec.Random(), spec.Report(report.Terminal{})) +} + +func testManifestInspectCommand(t *testing.T, when spec.G, it spec.S) { + var ( + command *cobra.Command + logger *logging.LogWithWriters + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + ) + + it.Before(func() { + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + mockController = gomock.NewController(t) + mockClient = testmocks.NewMockPackClient(mockController) + + command = commands.ManifestInspect(logger, mockClient) + }) + it("should annotate images with given flags", func() { + prepareInspectManifest(t, mockClient) + + command.SetArgs([]string{ + "some-index", + }) + h.AssertNil(t, command.Execute()) + }) + it("should return an error when index not passed", func() { + prepareInspectManifest(t, mockClient) + + command.SetArgs([]string(nil)) + err := command.Execute() + h.AssertNotNil(t, err) + }) + it("should have help flag", func() { + prepareInspectManifest(t, mockClient) + + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) +} + +func prepareInspectManifest(t *testing.T, mockClient *testmocks.MockPackClient) { + mockClient. + EXPECT(). + InspectManifest( + gomock.Any(), + gomock.Any(), + ). + AnyTimes(). + Return(nil) +} diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index afc1799118..a62abec273 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -9,8 +9,8 @@ import ( // ManifestPushFlags define flags provided to the ManifestPush type ManifestPushFlags struct { - format string - insecure, purge, all bool + format string + insecure, purge bool } // ManifestPush pushes a manifest list (Image index) to a registry. @@ -36,7 +36,6 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().StringVarP(&flags.format, "format", "f", "", "Format to save image index as ('OCI' or 'V2S2')") cmd.Flags().BoolVar(&flags.insecure, "insecure", false, "Allow publishing to insecure registry") cmd.Flags().BoolVar(&flags.purge, "purge", false, "Delete the manifest list or image index from local storage if pushing succeeds") - cmd.Flags().BoolVar(&flags.all, "all", false, "Also push the images in the list") AddHelpFlag(cmd, "push") return cmd diff --git a/internal/commands/manifest_push_test.go b/internal/commands/manifest_push_test.go new file mode 100644 index 0000000000..8a431a64c2 --- /dev/null +++ b/internal/commands/manifest_push_test.go @@ -0,0 +1,93 @@ +package commands_test + +import ( + "bytes" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/internal/commands" + "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/logging" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestManifestPushCommand(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + + spec.Run(t, "Commands", testManifestPushCommand, spec.Random(), spec.Report(report.Terminal{})) +} + +func testManifestPushCommand(t *testing.T, when spec.G, it spec.S) { + var ( + command *cobra.Command + logger *logging.LogWithWriters + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + ) + + it.Before(func() { + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + mockController = gomock.NewController(t) + mockClient = testmocks.NewMockPackClient(mockController) + + command = commands.ManifestPush(logger, mockClient) + }) + it("should annotate images with given flags", func() { + preparePushManifest(t, mockClient) + + command.SetArgs([]string{ + "some-index", + "-f", + "v2s2", + "--purge", + "--insecure", + }) + h.AssertNil(t, command.Execute()) + }) + it("should return an error when index not exists locally", func() { + preparePushManifestWithError(t, mockClient) + + command.SetArgs([]string{"some-index"}) + err := command.Execute() + h.AssertNotNil(t, err) + }) + it("should have help flag", func() { + preparePushManifest(t, mockClient) + + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) +} + +func preparePushManifest(t *testing.T, mockClient *testmocks.MockPackClient) { + mockClient. + EXPECT(). + PushManifest( + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + AnyTimes(). + Return(nil) +} + +func preparePushManifestWithError(t *testing.T, mockClient *testmocks.MockPackClient) { + mockClient. + EXPECT(). + PushManifest( + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + AnyTimes(). + Return(errors.New("unable to push Image")) +} diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index ce5a6ba9d6..49e42a2c98 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -9,31 +9,41 @@ import ( // ManifestDelete deletes one or more manifest lists from local storage func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { - // var flags ManifestDeleteFlags - cmd := &cobra.Command{ - Use: "remove [manifest-list] [manifest-list...] [flags]", + Use: "remove [manifest-list] [manifest-list...]", Args: cobra.MatchAll(cobra.MinimumNArgs(1), cobra.OnlyValidArgs), Short: "Remove an image from a manifest list or image index.", Example: `pack manifest remove cnbs/sample-package:hello-multiarch-universe`, Long: `Delete one or more manifest lists from local storage. When a manifest list exits locally, users can remove existing images from a manifest list`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - var errMsg = "" - errs := pack.DeleteManifest(cmd.Context(), args) - for _, err := range errs { - if err != nil { - errMsg += err.Error() + "\n" - } - } - - if errMsg != "" { - return errors.New(errMsg) - } - return nil + return NewErrors(pack.DeleteManifest(cmd.Context(), args)).Error() }), } AddHelpFlag(cmd, "remove") return cmd } + +type Errors struct { + errs []error +} + +func NewErrors(errs []error) Errors { + return Errors{ + errs: errs, + } +} + +func (e Errors) Error() error { + var errMsg string + if len(e.errs) > 0 { + for _, err := range e.errs { + errMsg += err.Error() + } + + return errors.New(errMsg) + } + + return nil +} diff --git a/internal/commands/manifest_remove_test.go b/internal/commands/manifest_remove_test.go new file mode 100644 index 0000000000..312e584a34 --- /dev/null +++ b/internal/commands/manifest_remove_test.go @@ -0,0 +1,89 @@ +package commands_test + +import ( + "bytes" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/internal/commands" + "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/logging" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestManifestDeleteCommand(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + + spec.Run(t, "Commands", testManifestDeleteCommand, spec.Random(), spec.Report(report.Terminal{})) +} + +func testManifestDeleteCommand(t *testing.T, when spec.G, it spec.S) { + var ( + command *cobra.Command + logger *logging.LogWithWriters + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + ) + + it.Before(func() { + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + mockController = gomock.NewController(t) + mockClient = testmocks.NewMockPackClient(mockController) + + command = commands.ManifestDelete(logger, mockClient) + }) + it("should delete index", func() { + prepareDeleteManifest(t, mockClient) + + command.SetArgs([]string{ + "some-index", + }) + h.AssertNil(t, command.Execute()) + }) + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) + it("should return an error", func() { + prepareDeleteManifest(t, mockClient) + + command.SetArgs([]string{"some-index"}) + err := command.Execute() + h.AssertNil(t, err) + + err = command.Execute() + h.AssertNotNil(t, err) + }) +} + +func prepareDeleteManifest(t *testing.T, mockClient *testmocks.MockPackClient) { + mockClient. + EXPECT(). + DeleteManifest( + gomock.Any(), + gomock.Any(), + ). + AnyTimes(). + After( + mockClient. + EXPECT(). + DeleteManifest( + gomock.Any(), + gomock.Any(), + ). + Times(1). + Return(nil), + ). + Return([]error{ + errors.New("image index doesn't exists"), + }) +} diff --git a/internal/commands/manifest_rm.go b/internal/commands/manifest_rm.go index 415e34bfd2..33bf998576 100644 --- a/internal/commands/manifest_rm.go +++ b/internal/commands/manifest_rm.go @@ -1,7 +1,6 @@ package commands import ( - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/logging" @@ -9,8 +8,6 @@ import ( // ManifestRemove will remove the specified image manifest if it is already referenced in the index func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { - // var flags ManifestRemoveFlags - cmd := &cobra.Command{ Use: "rm [manifest-list] [manifest] [manifest...] [flags]", Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), @@ -21,20 +18,7 @@ func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { User must pass digest of the image in oder to delete it from index. Sometimes users can just experiment with the feature locally and they want to discard all the local information created by pack. 'rm' command just delete the local manifest list`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - var errMsg = "" - name := args[0] - images := args[1:] - errs := pack.RemoveManifest(cmd.Context(), name, images) - for _, err := range errs { - if err != nil { - errMsg += err.Error() + "\n" - } - } - - if errMsg != "" { - return errors.New(errMsg) - } - return nil + return NewErrors(pack.RemoveManifest(cmd.Context(), args[0], args[1:])).Error() }), } diff --git a/internal/commands/manifest_rm_test.go b/internal/commands/manifest_rm_test.go new file mode 100644 index 0000000000..15cafd8c71 --- /dev/null +++ b/internal/commands/manifest_rm_test.go @@ -0,0 +1,92 @@ +package commands_test + +import ( + "bytes" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/internal/commands" + "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/logging" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestManifestRemoveCommand(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + + spec.Run(t, "Commands", testManifestRemoveCommand, spec.Random(), spec.Report(report.Terminal{})) +} + +func testManifestRemoveCommand(t *testing.T, when spec.G, it spec.S) { + var ( + command *cobra.Command + logger *logging.LogWithWriters + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + ) + + it.Before(func() { + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + mockController = gomock.NewController(t) + mockClient = testmocks.NewMockPackClient(mockController) + + command = commands.ManifestRemove(logger, mockClient) + }) + it("should remove index", func() { + prepareRemoveManifest(t, mockClient) + + command.SetArgs([]string{ + "some-index", + "some-image", + }) + h.AssertNil(t, command.Execute()) + }) + it("should return an error", func() { + prepareRemoveManifest(t, mockClient) + + command.SetArgs([]string{"some-index", "some-image"}) + err := command.Execute() + h.AssertNil(t, err) + + err = command.Execute() + h.AssertNotNil(t, err) + }) + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) +} + +func prepareRemoveManifest(t *testing.T, mockClient *testmocks.MockPackClient) { + mockClient. + EXPECT(). + RemoveManifest( + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + AnyTimes(). + After( + mockClient. + EXPECT(). + RemoveManifest( + gomock.Any(), + gomock.Any(), + gomock.Any(), + ). + Times(1). + Return(nil), + ). + Return([]error{ + errors.New("image doesn't exists"), + }) +} diff --git a/internal/commands/manifest_test.go b/internal/commands/manifest_test.go new file mode 100644 index 0000000000..067271ef5d --- /dev/null +++ b/internal/commands/manifest_test.go @@ -0,0 +1,55 @@ +package commands_test + +import ( + "bytes" + "testing" + + "github.com/golang/mock/gomock" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/internal/commands" + "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/logging" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestNewManifestCommand(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + + spec.Run(t, "Commands", testNewManifestCommand, spec.Random(), spec.Report(report.Terminal{})) +} + +func testNewManifestCommand(t *testing.T, when spec.G, it spec.S) { + var ( + command *cobra.Command + logger *logging.LogWithWriters + outBuf bytes.Buffer + mockController *gomock.Controller + mockClient *testmocks.MockPackClient + ) + + it.Before(func() { + logger = logging.NewLogWithWriters(&outBuf, &outBuf) + mockController = gomock.NewController(t) + mockClient = testmocks.NewMockPackClient(mockController) + + command = commands.NewManifestCommand(logger, mockClient) + command.SetOut(logging.GetWriterForLevel(logger, logging.InfoLevel)) + }) + it("should have help flag", func() { + command.SetArgs([]string{}) + err := command.Execute() + h.AssertNilE(t, err) + + output := outBuf.String() + h.AssertContains(t, output, "Interact with image index") + h.AssertContains(t, output, "Usage:") + for _, command := range []string{"create", "add", "annotate", "inspect", "exists", "remove", "rm"} { + h.AssertContains(t, output, command) + } + }) +} From 71316c3c58f33aae6a40caaee0cf563899923140 Mon Sep 17 00:00:00 2001 From: WYGIN Date: Fri, 1 Mar 2024 11:31:41 +0000 Subject: [PATCH 52/79] chore: updated imgutil to latest version Signed-off-by: WYGIN --- go.mod | 2 +- go.sum | 4 +- pkg/client/add_manifest_test.go | 7 +- pkg/client/annotate_manifest_test.go | 110 ++++++++++++++++++++++----- pkg/client/client.go | 25 ++++-- pkg/client/inspect_manifest_test.go | 22 ++---- pkg/client/push_manifest_test.go | 4 +- pkg/client/rm_manifest_test.go | 4 +- 8 files changed, 129 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index 2ead90c9f3..3458f6a761 100644 --- a/go.mod +++ b/go.mod @@ -135,7 +135,7 @@ require ( go 1.21 replace ( - github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240219150011-2f201867f6c1 + github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240301111028-2b39c6a637c2 // Pin moby/buildkit until docker/docker is upgraded github.com/moby/buildkit => github.com/moby/buildkit v0.11.6 diff --git a/go.sum b/go.sum index 76051e6297..b151bdbb99 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7 github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240219150011-2f201867f6c1 h1:bjszsCM4clSyyZpOSlBwg++kUj+C28bSF+eKVKoCxvA= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240219150011-2f201867f6c1/go.mod h1:7zUmt4wkVJNuXCZhQndEd3kvVGmWLVyzRFIQXTaeXlU= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240301111028-2b39c6a637c2 h1:lwUITvRcyjNCpqeGRKWCnJ2teNr7t7LsqHyswt7MYTk= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240301111028-2b39c6a637c2/go.mod h1:7zUmt4wkVJNuXCZhQndEd3kvVGmWLVyzRFIQXTaeXlU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= diff --git a/pkg/client/add_manifest_test.go b/pkg/client/add_manifest_test.go index cdf316eabf..f2b6e235ed 100644 --- a/pkg/client/add_manifest_test.go +++ b/pkg/client/add_manifest_test.go @@ -78,7 +78,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { err = subject.AddManifest( context.TODO(), "pack/index", - digest.Identifier(), + digest.Name(), ManifestAddOptions{}, ) h.AssertNil(t, err) @@ -207,7 +207,8 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, osFeatures, []string{"some-os-features"}) }) it("should add with annotations", func() { - digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") + digestStr := "pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171" + digest, err := name.NewDigest(digestStr) h.AssertNil(t, err) idx := prepareLoadIndex( @@ -218,7 +219,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { err = subject.AddManifest( context.TODO(), "pack/index", - digest.Name(), + digestStr, ManifestAddOptions{ Annotations: map[string]string{"some-key": "some-value"}, }, diff --git a/pkg/client/annotate_manifest_test.go b/pkg/client/annotate_manifest_test.go index d965a1a774..dcf8adb852 100644 --- a/pkg/client/annotate_manifest_test.go +++ b/pkg/client/annotate_manifest_test.go @@ -20,6 +20,8 @@ import ( h "github.com/buildpacks/pack/testhelpers" ) +const digestStr = "sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56" + func TestAnnotateManifest(t *testing.T) { color.Disable(true) defer color.Disable(false) @@ -216,7 +218,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { osFeatures, err := idx.OSFeatures(digest) h.AssertNil(t, err) - h.AssertEq(t, osFeatures, []string{"some-osFeatures"}) + h.AssertEq(t, osFeatures, []string{"some-osFeatures", "some-osFeatures"}) }) it("should set URLs for given image", func() { idx := prepareLoadIndex(t, *mockIndexFactory) @@ -268,6 +270,79 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) h.AssertEq(t, annos, map[string]string{"some-key": "some-value"}) }) + it("should save annotated index", func() { + var ( + fakeOS = "some-os" + fakeArch = "some-arch" + fakeVariant = "some-variant" + fakeVersion = "some-osVersion" + fakeFeatures = []string{"some-features"} + fakeOSFeatures = []string{"some-OSFeatures"} + fakeURLs = []string{"some-urls"} + fakeAnnotations = map[string]string{"some-key": "some-value"} + ) + idx := prepareLoadIndex(t, *mockIndexFactory) + imgIdx, ok := idx.(*fakes.Index) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + err = subject.AnnotateManifest( + context.TODO(), + "some/repo", + digest.Name(), + ManifestAnnotateOptions{ + OS: fakeOS, + OSArch: fakeArch, + OSVariant: fakeVariant, + OSVersion: fakeVersion, + Features: fakeFeatures, + OSFeatures: fakeOSFeatures, + URLs: fakeURLs, + Annotations: fakeAnnotations, + }, + ) + h.AssertNil(t, err) + + err = idx.Save() + h.AssertNil(t, err) + + os, err := idx.OS(digest) + h.AssertNil(t, err) + h.AssertEq(t, os, fakeOS) + + arch, err := idx.Architecture(digest) + h.AssertNil(t, err) + h.AssertEq(t, arch, fakeArch) + + variant, err := idx.Variant(digest) + h.AssertNil(t, err) + h.AssertEq(t, variant, fakeVariant) + + osVersion, err := idx.OSVersion(digest) + h.AssertNil(t, err) + h.AssertEq(t, osVersion, fakeVersion) + + features, err := idx.Features(digest) + h.AssertNil(t, err) + h.AssertEq(t, features, fakeFeatures) + + osFeatures, err := idx.OSFeatures(digest) + h.AssertNil(t, err) + h.AssertEq(t, osFeatures, []string{"some-OSFeatures", "some-OSFeatures"}) + + urls, err := idx.URLs(digest) + h.AssertNil(t, err) + h.AssertEq(t, urls, fakeURLs) + + annos, err := idx.Annotations(digest) + h.AssertNil(t, err) + h.AssertEq(t, annos, fakeAnnotations) + }) }) when("return an error when", func() { it("has no Index locally by given Name", func() { @@ -282,99 +357,100 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }) it("has no image with given digest for OS", func() { prepareLoadIndex(t, *mockIndexFactory) + err = subject.AnnotateManifest( context.TODO(), "some/repo", - "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + "busybox@"+digestStr, ManifestAnnotateOptions{ OS: "some-os", }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) }) it("has no image with given digest for Arch", func() { prepareLoadIndex(t, *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), "some/repo", - "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + "busybox@"+digestStr, ManifestAnnotateOptions{ OSArch: "some-arch", }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) }) it("has no image with given digest for Variant", func() { prepareLoadIndex(t, *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), "some/repo", - "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + "busybox@"+digestStr, ManifestAnnotateOptions{ OSVariant: "some-variant", }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) }) it("has no image with given digest for osVersion", func() { prepareLoadIndex(t, *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), "some/repo", - "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + "busybox@"+digestStr, ManifestAnnotateOptions{ OSVersion: "some-osVersion", }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) }) it("has no image with given digest for Features", func() { prepareLoadIndex(t, *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), "some/repo", - "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + "busybox@"+digestStr, ManifestAnnotateOptions{ Features: []string{"some-features"}, }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) }) it("has no image with given digest for OSFeatures", func() { prepareLoadIndex(t, *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), "some/repo", - "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + "busybox@"+digestStr, ManifestAnnotateOptions{ OSFeatures: []string{"some-osFeatures"}, }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) }) it("has no image with given digest for URLs", func() { prepareLoadIndex(t, *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), "some/repo", - "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + "busybox@"+digestStr, ManifestAnnotateOptions{ URLs: []string{"some-urls"}, }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) }) it("has no image with given digest for Annotations", func() { prepareLoadIndex(t, *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), "some/repo", - "busybox@sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56", + "busybox@"+digestStr, ManifestAnnotateOptions{ Annotations: map[string]string{"some-key": "some-value"}, }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) }) }) }) diff --git a/pkg/client/client.go b/pkg/client/client.go index 693f66503f..a8cf1d9853 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -329,6 +329,11 @@ func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (i return remote.NewImage(repoName, f.keychain, remote.WithDefaultPlatform(platform)) } +type indexFactory struct { + keychain authn.Keychain + xdgRuntimePath string +} + func (f *indexFactory) LoadIndex(repoName string, opts ...index.Option) (img imgutil.ImageIndex, err error) { opts, err = withOptions(opts, f.keychain) if err != nil { @@ -344,12 +349,8 @@ func (f *indexFactory) LoadIndex(repoName string, opts ...index.Option) (img img if err == nil { return } - return nil, errors.Wrap(err, errors.Errorf("Image: '%s' not found", repoName).Error()) -} -type indexFactory struct { - keychain authn.Keychain - xdgRuntimePath string + return nil, errors.Wrap(err, errors.Errorf("Image: '%s' not found", repoName).Error()) } func (f *indexFactory) FetchIndex(name string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { @@ -402,10 +403,20 @@ func withOptions(ops []index.Option, keychain authn.Keychain) ([]index.Option, e xdgPath, ok := os.LookupEnv(xdgRuntimePath) if ok { - ops = append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(xdgPath), index.WithManifestOnly(!config.ImageIndexFullMode)) + ops = append( + ops, + index.WithKeychain(keychain), + index.WithXDGRuntimePath(xdgPath), + index.WithManifestOnly(!config.ImageIndexFullMode), + ) return ops, nil } - ops = append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(filepath.Join(home, "manifests")), index.WithManifestOnly(!config.ImageIndexFullMode)) + ops = append( + ops, + index.WithKeychain(keychain), + index.WithXDGRuntimePath(filepath.Join(home, "manifests")), + index.WithManifestOnly(!config.ImageIndexFullMode), + ) return ops, nil } diff --git a/pkg/client/inspect_manifest_test.go b/pkg/client/inspect_manifest_test.go index 08b2732a78..1fdf4bc7be 100644 --- a/pkg/client/inspect_manifest_test.go +++ b/pkg/client/inspect_manifest_test.go @@ -3,7 +3,6 @@ package client import ( "bytes" "context" - "encoding/json" "errors" "os" "testing" @@ -33,7 +32,8 @@ func testInspectManifest(t *testing.T, when spec.G, it spec.S) { var ( mockController *gomock.Controller mockIndexFactory *testmocks.MockIndexFactory - out bytes.Buffer + stdout bytes.Buffer + stderr bytes.Buffer logger logging.Logger subject *Client err error @@ -42,7 +42,7 @@ func testInspectManifest(t *testing.T, when spec.G, it spec.S) { when("#Add", func() { it.Before(func() { - logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + logger = logging.NewLogWithWriters(&stdout, &stderr, logging.WithVerbose()) mockController = gomock.NewController(t) mockIndexFactory = testmocks.NewMockIndexFactory(mockController) @@ -53,6 +53,7 @@ func testInspectManifest(t *testing.T, when spec.G, it spec.S) { WithKeychain(authn.DefaultKeychain), ) h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertSameInstance(t, subject.logger, logger) h.AssertNil(t, err) }) it.After(func() { @@ -69,23 +70,14 @@ func testInspectManifest(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, err.Error(), "index not found") }) it("should return formatted IndexManifest", func() { - idx := prepareFindIndex(t, *mockIndexFactory) + prepareFindIndex(t, *mockIndexFactory) + err := subject.InspectManifest( context.TODO(), "some/name", ) h.AssertNil(t, err) - - ii, ok := idx.(*fakes.Index) - h.AssertEq(t, ok, true) - - mfest, err := ii.IndexManifest() - h.AssertNil(t, err) - h.AssertNotNil(t, mfest) - - mfestBytes, err := json.MarshalIndent(mfest, "", " ") - h.AssertNil(t, err) - h.AssertEq(t, mfestBytes, out.Bytes()) + h.AssertEq(t, stderr.String(), "") }) }) } diff --git a/pkg/client/push_manifest_test.go b/pkg/client/push_manifest_test.go index 19837c16da..79ce6cb595 100644 --- a/pkg/client/push_manifest_test.go +++ b/pkg/client/push_manifest_test.go @@ -58,7 +58,7 @@ func testPushManifest(t *testing.T, when spec.G, it spec.S) { prepareLoadIndexWithError(t, *mockIndexFactory) err := subject.PushManifest(context.TODO(), "some-index", PushManifestOptions{}) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest.Error()) + h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest("").Error()) }) it("should push index to registry", func() { prepareLoadIndex(t, *mockIndexFactory) @@ -73,5 +73,5 @@ func prepareLoadIndexWithError(t *testing.T, mockIndexFactory testmocks.MockInde mockIndexFactory. EXPECT(). LoadIndex(gomock.Any(), gomock.Any()). - Return(nil, imgutil.ErrNoImageOrIndexFoundWithGivenDigest) + Return(nil, imgutil.ErrNoImageOrIndexFoundWithGivenDigest("")) } diff --git a/pkg/client/rm_manifest_test.go b/pkg/client/rm_manifest_test.go index c732f0a987..9c092daf94 100644 --- a/pkg/client/rm_manifest_test.go +++ b/pkg/client/rm_manifest_test.go @@ -67,7 +67,7 @@ func testRemoveManifest(t *testing.T, when spec.G, it spec.S) { digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) h.AssertNil(t, err) - errs := subject.RemoveManifest(context.TODO(), "some-index", []string{digest.Identifier()}) + errs := subject.RemoveManifest(context.TODO(), "some-index", []string{digest.Name()}) h.AssertEq(t, len(errs), 0) }) it("should remove image", func() { @@ -81,7 +81,7 @@ func testRemoveManifest(t *testing.T, when spec.G, it spec.S) { digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) h.AssertNil(t, err) - errs := subject.RemoveManifest(context.TODO(), "some-index", []string{digest.Identifier()}) + errs := subject.RemoveManifest(context.TODO(), "some-index", []string{digest.Name()}) h.AssertEq(t, len(errs), 0) }) }) From 76a36781bb124b395b833f875a94f6e743b35339 Mon Sep 17 00:00:00 2001 From: WYGIN Date: Mon, 4 Mar 2024 11:24:38 +0000 Subject: [PATCH 53/79] refactor: change features, os-features, annotations to relevant flags Signed-off-by: WYGIN --- go.mod | 2 +- go.sum | 4 +- internal/commands/manifest_add.go | 53 ++++---------------------- internal/commands/manifest_annotate.go | 34 ++++------------- 4 files changed, 17 insertions(+), 76 deletions(-) diff --git a/go.mod b/go.mod index 3458f6a761..e324a9dddd 100644 --- a/go.mod +++ b/go.mod @@ -135,7 +135,7 @@ require ( go 1.21 replace ( - github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240301111028-2b39c6a637c2 + github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304105122-1c3b05ef4d82 // Pin moby/buildkit until docker/docker is upgraded github.com/moby/buildkit => github.com/moby/buildkit v0.11.6 diff --git a/go.sum b/go.sum index b151bdbb99..772b2b8e9d 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7 github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240301111028-2b39c6a637c2 h1:lwUITvRcyjNCpqeGRKWCnJ2teNr7t7LsqHyswt7MYTk= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240301111028-2b39c6a637c2/go.mod h1:7zUmt4wkVJNuXCZhQndEd3kvVGmWLVyzRFIQXTaeXlU= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304105122-1c3b05ef4d82 h1:45g7TpPQF41nbtdOFULY8J7SE6O9DqD1QA6vq93Mss0= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304105122-1c3b05ef4d82/go.mod h1:7zUmt4wkVJNuXCZhQndEd3kvVGmWLVyzRFIQXTaeXlU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index c7a88d2c29..04ad6aa32a 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -2,8 +2,6 @@ package commands import ( "errors" - "fmt" - "strings" "github.com/spf13/cobra" @@ -13,9 +11,10 @@ import ( // ManifestAddFlags define flags provided to the ManifestAdd type ManifestAddFlags struct { - os, osVersion, osArch, osVariant string - osFeatures, annotations, features string - all bool + os, osVersion, osArch, osVariant string + osFeatures, features []string + annotations map[string]string + all bool } // ManifestAdd modifies a manifest list (Image index) and add a new image to the list of manifests. @@ -41,21 +40,6 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { return err } - if flags.features != "" { - features = strings.Split(flags.features, ";") - } - - if flags.osFeatures != "" { - features = strings.Split(flags.osFeatures, ";") - } - - if flags.annotations != "" { - annotations, err = StringToKeyValueMap(flags.annotations) - if err != nil { - return err - } - } - return pack.AddManifest(cmd.Context(), imageIndex, manifests, client.ManifestAddOptions{ OS: flags.os, OSVersion: flags.osVersion, @@ -74,9 +58,9 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().StringVar(&flags.osArch, "arch", "", "Set the architecture") cmd.Flags().StringVar(&flags.osVariant, "variant", "", "Set the architecture variant") cmd.Flags().StringVar(&flags.osVersion, "os-version", "", "Set the os-version") - cmd.Flags().StringVar(&flags.osFeatures, "os-features", "", "Set the OSFeatures") - cmd.Flags().StringVar(&flags.features, "features", "", "Set the Features") - cmd.Flags().StringVar(&flags.annotations, "annotations", "", "Set the annotations") + cmd.Flags().StringSliceVar(&flags.osFeatures, "os-features", []string{}, "Set the OSFeatures") + cmd.Flags().StringSliceVar(&flags.features, "features", []string{}, "Set the Features") + cmd.Flags().StringToStringVar(&flags.annotations, "annotations", map[string]string{}, "Set the annotations") AddHelpFlag(cmd, "add") return cmd @@ -88,26 +72,3 @@ func validateManifestAddFlags(flags ManifestAddFlags) error { } return nil } - -func StringToKeyValueMap(s string) (map[string]string, error) { - keyValues := strings.Split(s, ";") - - var annosMap = make(map[string]string) - for _, keyValue := range keyValues { - parts := strings.SplitN(keyValue, "=", 2) - if len(parts) != 2 { - return nil, fmt.Errorf("invalid key-value pair: %s", keyValue) - } - - key := parts[0] - value := parts[1] - - if key == "" || value == "" { - return nil, fmt.Errorf("key(%s) or value(%s) is undefined", key, value) - } - - annosMap[key] = value - } - - return annosMap, nil -} diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 3341e4f11a..8b662a2ffe 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -1,8 +1,6 @@ package commands import ( - "strings" - "github.com/pkg/errors" "github.com/spf13/cobra" @@ -12,8 +10,9 @@ import ( // ManifestAnnotateFlags define flags provided to the ManifestAnnotate type ManifestAnnotateFlags struct { - os, arch, variant, osVersion string - features, osFeatures, urls, annotations string + os, arch, variant, osVersion string + features, osFeatures, urls []string + annotations map[string]string } // ManifestAnnotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list. @@ -39,25 +38,6 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { return err } - if flags.features != "" { - features = strings.Split(flags.features, ";") - } - - if flags.osFeatures != "" { - osFeatures = strings.Split(flags.osFeatures, ";") - } - - if flags.urls != "" { - urls = strings.Split(flags.urls, ";") - } - - if flags.annotations != "" { - annotations, err = StringToKeyValueMap(flags.annotations) - if err != nil { - return err - } - } - return pack.AnnotateManifest(cmd.Context(), args[0], args[1], client.ManifestAnnotateOptions{ OS: flags.os, OSVersion: flags.osVersion, @@ -75,10 +55,10 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().StringVar(&flags.arch, "arch", "", "Set the architecture") cmd.Flags().StringVar(&flags.variant, "variant", "", "Set the architecture") cmd.Flags().StringVar(&flags.osVersion, "os-version", "", "override the os `version` of the specified image") - cmd.Flags().StringVar(&flags.features, "features", "", "override the `features` of the specified image") - cmd.Flags().StringVar(&flags.urls, "urls", "", "override the `urls` of the specified image") - cmd.Flags().StringVar(&flags.osFeatures, "os-features", "", "override the os `features` of the specified image") - cmd.Flags().StringVar(&flags.annotations, "annotations", "", "set an `annotation` for the specified image") + cmd.Flags().StringSliceVar(&flags.features, "features", []string{}, "override the `features` of the specified image") + cmd.Flags().StringSliceVar(&flags.urls, "urls", []string{}, "override the `urls` of the specified image") + cmd.Flags().StringSliceVar(&flags.osFeatures, "os-features", []string{}, "override the os `features` of the specified image") + cmd.Flags().StringToStringVar(&flags.annotations, "annotations", map[string]string{}, "set an `annotation` for the specified image") AddHelpFlag(cmd, "annotate") return cmd From 0b477b2e86e9dff5e6fc0c6b60362178e6eb2322 Mon Sep 17 00:00:00 2001 From: WYGIN Date: Mon, 4 Mar 2024 14:04:17 +0000 Subject: [PATCH 54/79] fix: bug causing ignore --features, --os-features, --urls, --annotations Signed-off-by: WYGIN --- go.mod | 2 +- go.sum | 4 +- internal/commands/config.go | 1 - internal/commands/config_index_full_mode.go | 54 --------------------- internal/commands/manifest_add.go | 17 +++---- internal/commands/manifest_add_test.go | 4 +- internal/commands/manifest_annotate.go | 23 +++------ internal/commands/manifest_annotate_test.go | 4 +- internal/config/config.go | 1 - pkg/client/client.go | 29 ++--------- 10 files changed, 26 insertions(+), 113 deletions(-) delete mode 100644 internal/commands/config_index_full_mode.go diff --git a/go.mod b/go.mod index e324a9dddd..b55bbe95c1 100644 --- a/go.mod +++ b/go.mod @@ -135,7 +135,7 @@ require ( go 1.21 replace ( - github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304105122-1c3b05ef4d82 + github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304130223-abfcabf596ce // Pin moby/buildkit until docker/docker is upgraded github.com/moby/buildkit => github.com/moby/buildkit v0.11.6 diff --git a/go.sum b/go.sum index 772b2b8e9d..58f589c3ea 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7 github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304105122-1c3b05ef4d82 h1:45g7TpPQF41nbtdOFULY8J7SE6O9DqD1QA6vq93Mss0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304105122-1c3b05ef4d82/go.mod h1:7zUmt4wkVJNuXCZhQndEd3kvVGmWLVyzRFIQXTaeXlU= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304130223-abfcabf596ce h1:7ntd8izCev/sa05xJPxpWBK0Db0o+LX57yiX6ZCR5D0= +github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304130223-abfcabf596ce/go.mod h1:7zUmt4wkVJNuXCZhQndEd3kvVGmWLVyzRFIQXTaeXlU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= diff --git a/internal/commands/config.go b/internal/commands/config.go index 9ff3f684c6..a2b087dabc 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -24,7 +24,6 @@ func NewConfigCommand(logger logging.Logger, cfg config.Config, cfgPath string, cmd.AddCommand(ConfigTrustedBuilder(logger, cfg, cfgPath)) cmd.AddCommand(ConfigLifecycleImage(logger, cfg, cfgPath)) cmd.AddCommand(ConfigRegistryMirrors(logger, cfg, cfgPath)) - cmd.AddCommand(ConfigImageIndexFullMode(logger, cfg, cfgPath)) AddHelpFlag(cmd, "config") return cmd diff --git a/internal/commands/config_index_full_mode.go b/internal/commands/config_index_full_mode.go deleted file mode 100644 index a27b700544..0000000000 --- a/internal/commands/config_index_full_mode.go +++ /dev/null @@ -1,54 +0,0 @@ -package commands - -import ( - "strconv" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/buildpacks/pack/internal/config" - "github.com/buildpacks/pack/internal/style" - "github.com/buildpacks/pack/pkg/logging" -) - -func ConfigImageIndexFullMode(logger logging.Logger, cfg config.Config, cfgPath string) *cobra.Command { - cmd := &cobra.Command{ - Use: "index-full-mode []", - Args: cobra.MaximumNArgs(1), - Short: "List and set the current 'index-full-mode' value from the config", - Long: "ImageIndex FullMode features in pack are gated, and require you adding setting `index-full-mode=true` to the Pack Config, either manually, or using this command.\n\n" + - "* Running `pack config index-full-mode` prints whether ImageIndexFullMode features are currently enabled.\n" + - "* Running `pack config index-full-mode ` enables or disables ImageIndexFullMode features.", - RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - switch { - case len(args) == 0: - if cfg.ImageIndexFullMode { - logger.Infof("ImageIndexFullMode features are enabled! To turn them off, run `pack config index-full-mode false`") - } else { - logger.Info("ImageIndexFullMode features aren't currently enabled. To enable them, run `pack config index-full-mode true`") - } - default: - val, err := strconv.ParseBool(args[0]) - if err != nil { - return errors.Wrapf(err, "invalid value %s provided", style.Symbol(args[0])) - } - cfg.ImageIndexFullMode = val - - if err = config.Write(cfg, cfgPath); err != nil { - return errors.Wrap(err, "writing to config") - } - - if cfg.ImageIndexFullMode { - logger.Info("ImageIndexFullMode features enabled!") - } else { - logger.Info("ImageIndexFullMode features disabled") - } - } - - return nil - }), - } - - AddHelpFlag(cmd, "index-full-mode") - return cmd -} diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 04ad6aa32a..0c9bc020bf 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -29,11 +29,6 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { cnbs/sample-package:hello-universe-riscv-linux`, Long: `manifest add modifies a manifest list (Image index) and add a new image to the list of manifests.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { - var ( - annotations = make(map[string]string, 0) - features = make([]string, 0) - osFeatures = make([]string, 0) - ) imageIndex := args[0] manifests := args[1] if err := validateManifestAddFlags(flags); err != nil { @@ -45,9 +40,9 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { OSVersion: flags.osVersion, OSArch: flags.osArch, OSVariant: flags.osVariant, - OSFeatures: osFeatures, - Features: features, - Annotations: annotations, + OSFeatures: flags.osFeatures, + Features: flags.features, + Annotations: flags.annotations, All: flags.all, }) }), @@ -58,9 +53,9 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().StringVar(&flags.osArch, "arch", "", "Set the architecture") cmd.Flags().StringVar(&flags.osVariant, "variant", "", "Set the architecture variant") cmd.Flags().StringVar(&flags.osVersion, "os-version", "", "Set the os-version") - cmd.Flags().StringSliceVar(&flags.osFeatures, "os-features", []string{}, "Set the OSFeatures") - cmd.Flags().StringSliceVar(&flags.features, "features", []string{}, "Set the Features") - cmd.Flags().StringToStringVar(&flags.annotations, "annotations", map[string]string{}, "Set the annotations") + cmd.Flags().StringSliceVar(&flags.osFeatures, "os-features", make([]string, 0), "Set the OSFeatures") + cmd.Flags().StringSliceVar(&flags.features, "features", make([]string, 0), "Set the Features") + cmd.Flags().StringToStringVar(&flags.annotations, "annotations", make(map[string]string, 0), "Set the annotations") AddHelpFlag(cmd, "add") return cmd diff --git a/internal/commands/manifest_add_test.go b/internal/commands/manifest_add_test.go index 2232ed1e28..722dce2af4 100644 --- a/internal/commands/manifest_add_test.go +++ b/internal/commands/manifest_add_test.go @@ -106,9 +106,9 @@ func testManifestAddCommand(t *testing.T, when spec.G, it spec.S) { it("should return an error when annotations defined invalidly", func() { prepareAddManifest(t, mockClient) - command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--annotations", "some-key="}) + command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--annotations", "some-key"}) err := command.Execute() - h.AssertEq(t, err.Error(), "key(some-key) or value() is undefined") + h.AssertEq(t, err.Error(), `invalid argument "some-key" for "--annotations" flag: some-key must be formatted as key=value`) }) it("should have help flag", func() { prepareAddManifest(t, mockClient) diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 8b662a2ffe..4dbf65eccf 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -27,13 +27,6 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { cnbs/sample-package:hello-universe --arch amd64`, Long: `manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { - var ( - annotations = make(map[string]string, 0) - features = make([]string, 0) - osFeatures = make([]string, 0) - urls = make([]string, 0) - ) - if err := validateManifestAnnotateFlags(flags); err != nil { return err } @@ -43,10 +36,10 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { OSVersion: flags.osVersion, OSArch: flags.arch, OSVariant: flags.variant, - OSFeatures: osFeatures, - Features: features, - URLs: urls, - Annotations: annotations, + OSFeatures: flags.osFeatures, + Features: flags.features, + URLs: flags.urls, + Annotations: flags.annotations, }) }), } @@ -55,10 +48,10 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().StringVar(&flags.arch, "arch", "", "Set the architecture") cmd.Flags().StringVar(&flags.variant, "variant", "", "Set the architecture") cmd.Flags().StringVar(&flags.osVersion, "os-version", "", "override the os `version` of the specified image") - cmd.Flags().StringSliceVar(&flags.features, "features", []string{}, "override the `features` of the specified image") - cmd.Flags().StringSliceVar(&flags.urls, "urls", []string{}, "override the `urls` of the specified image") - cmd.Flags().StringSliceVar(&flags.osFeatures, "os-features", []string{}, "override the os `features` of the specified image") - cmd.Flags().StringToStringVar(&flags.annotations, "annotations", map[string]string{}, "set an `annotation` for the specified image") + cmd.Flags().StringSliceVar(&flags.features, "features", make([]string, 0), "override the `features` of the specified image") + cmd.Flags().StringSliceVar(&flags.urls, "urls", make([]string, 0), "override the `urls` of the specified image") + cmd.Flags().StringSliceVar(&flags.osFeatures, "os-features", make([]string, 0), "override the os `features` of the specified image") + cmd.Flags().StringToStringVar(&flags.annotations, "annotations", make(map[string]string, 0), "set an `annotation` for the specified image") AddHelpFlag(cmd, "annotate") return cmd diff --git a/internal/commands/manifest_annotate_test.go b/internal/commands/manifest_annotate_test.go index 3d2de44728..adc0db1675 100644 --- a/internal/commands/manifest_annotate_test.go +++ b/internal/commands/manifest_annotate_test.go @@ -89,9 +89,9 @@ func testManifestAnnotateCommand(t *testing.T, when spec.G, it spec.S) { it("should return an error when annotations defined invalidly", func() { prepareAnnotateManifest(t, mockClient) - command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--annotations", "some-key="}) + command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--annotations", "some-key"}) err := command.Execute() - h.AssertEq(t, err.Error(), "key(some-key) or value() is undefined") + h.AssertEq(t, err.Error(), `invalid argument "some-key" for "--annotations" flag: some-key must be formatted as key=value`) }) it("should have help flag", func() { prepareAnnotateManifest(t, mockClient) diff --git a/internal/config/config.go b/internal/config/config.go index 36d4af66b0..de370ef727 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,7 +23,6 @@ type Config struct { LifecycleImage string `toml:"lifecycle-image,omitempty"` RegistryMirrors map[string]string `toml:"registry-mirrors,omitempty"` LayoutRepositoryDir string `toml:"layout-repo-dir,omitempty"` - ImageIndexFullMode bool `toml:"index-full-mode,omitempty"` } type Registry struct { diff --git a/pkg/client/client.go b/pkg/client/client.go index a8cf1d9853..4fe74f7fb3 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -391,32 +391,13 @@ func (f *indexFactory) CreateIndex(repoName string, opts ...index.Option) (imgut } func withOptions(ops []index.Option, keychain authn.Keychain) ([]index.Option, error) { - home, err := iconfig.PackHome() - if err != nil { - return ops, err + if xdgPath, ok := os.LookupEnv(xdgRuntimePath); ok { + return append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(xdgPath)), nil } - config, err := getConfig() + home, err := iconfig.PackHome() if err != nil { - return nil, err - } - - xdgPath, ok := os.LookupEnv(xdgRuntimePath) - if ok { - ops = append( - ops, - index.WithKeychain(keychain), - index.WithXDGRuntimePath(xdgPath), - index.WithManifestOnly(!config.ImageIndexFullMode), - ) - return ops, nil + return ops, err } - - ops = append( - ops, - index.WithKeychain(keychain), - index.WithXDGRuntimePath(filepath.Join(home, "manifests")), - index.WithManifestOnly(!config.ImageIndexFullMode), - ) - return ops, nil + return append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(filepath.Join(home, "manifests"))), nil } From 83f75397b2ac6ff50acf869f6da4fd4332124528 Mon Sep 17 00:00:00 2001 From: WYGIN Date: Sat, 30 Mar 2024 09:31:25 +0000 Subject: [PATCH 55/79] refactor: improve code readability Signed-off-by: WYGIN --- internal/commands/buildpack_inspect.go | 2 +- internal/commands/extension_inspect.go | 2 +- internal/commands/manifest_add.go | 4 +-- internal/commands/manifest_create.go | 18 ++----------- internal/commands/manifest_exists.go | 2 -- internal/commands/manifest_remove.go | 12 ++++----- pkg/client/add_manifest.go | 6 ++--- pkg/client/add_manifest_test.go | 4 +-- pkg/client/annotate_manifest.go | 3 +-- pkg/client/annotate_manifest_test.go | 4 +-- pkg/client/client.go | 35 ++++++++++--------------- pkg/client/create_manifest.go | 36 ++++++++++---------------- pkg/client/inspect_image.go | 2 +- pkg/client/inspect_manifest.go | 10 +++---- pkg/client/inspect_manifest_test.go | 4 +-- pkg/client/push_manifest.go | 30 +++++++-------------- pkg/client/push_manifest_test.go | 4 +-- pkg/client/remove_manifest.go | 13 +++++----- 18 files changed, 70 insertions(+), 121 deletions(-) diff --git a/internal/commands/buildpack_inspect.go b/internal/commands/buildpack_inspect.go index e75c479f89..2829967368 100644 --- a/internal/commands/buildpack_inspect.go +++ b/internal/commands/buildpack_inspect.go @@ -42,7 +42,7 @@ func BuildpackInspect(logger logging.Logger, cfg config.Config, client PackClien return cmd } -func buildpackInspect(logger logging.Logger, buildpackName, registryName string, flags BuildpackInspectFlags, cfg config.Config, pack PackClient) error { +func buildpackInspect(logger logging.Logger, buildpackName, registryName string, flags BuildpackInspectFlags, _ config.Config, pack PackClient) error { logger.Infof("Inspecting buildpack: %s\n", style.Symbol(buildpackName)) inspectedBuildpacksOutput, err := inspectAllBuildpacks( diff --git a/internal/commands/extension_inspect.go b/internal/commands/extension_inspect.go index b7a09bee4e..42863f78bf 100644 --- a/internal/commands/extension_inspect.go +++ b/internal/commands/extension_inspect.go @@ -26,7 +26,7 @@ func ExtensionInspect(logger logging.Logger, cfg config.Config, client PackClien return cmd } -func extensionInspect(logger logging.Logger, extensionName string, cfg config.Config, pack PackClient) error { +func extensionInspect(logger logging.Logger, extensionName string, _ config.Config, pack PackClient) error { logger.Infof("Inspecting extension: %s\n", style.Symbol(extensionName)) inspectedExtensionsOutput, err := inspectAllExtensions( diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 0c9bc020bf..d09b60e3bc 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -29,13 +29,11 @@ func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { cnbs/sample-package:hello-universe-riscv-linux`, Long: `manifest add modifies a manifest list (Image index) and add a new image to the list of manifests.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { - imageIndex := args[0] - manifests := args[1] if err := validateManifestAddFlags(flags); err != nil { return err } - return pack.AddManifest(cmd.Context(), imageIndex, manifests, client.ManifestAddOptions{ + return pack.AddManifest(cmd.Context(), args[0], args[1], client.ManifestAddOptions{ OS: flags.os, OSVersion: flags.osVersion, OSArch: flags.osArch, diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index e04e34b976..982d81459b 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -1,8 +1,6 @@ package commands import ( - "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" @@ -29,17 +27,14 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cnbs/sample-package:hello-universe-windows`, Long: `Generate manifest list for a multi-arch image which will be stored locally for manipulating images within index`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - imageIndex := args[0] - manifests := args[1:] - if err := validateManifestCreateFlags(flags); err != nil { return err } return pack.CreateManifest( cmd.Context(), - imageIndex, - manifests, + args[0], + args[1:], client.CreateManifestOptions{ Format: flags.format, Insecure: flags.insecure, @@ -54,17 +49,8 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmdFlags.StringVarP(&flags.format, "format", "f", "v2s2", "Format to save image index as ('OCI' or 'V2S2')") cmdFlags.StringVar(&flags.os, "os", "", "If any of the specified images is a list/index, choose the one for `os`") - if err := cmdFlags.MarkHidden("os"); err != nil { - panic(fmt.Sprintf("error marking --os as hidden: %v", err)) - } cmdFlags.StringVar(&flags.arch, "arch", "", "If any of the specified images is a list/index, choose the one for `arch`") - if err := cmdFlags.MarkHidden("arch"); err != nil { - panic(fmt.Sprintf("error marking --arch as hidden: %v", err)) - } cmdFlags.BoolVar(&flags.insecure, "insecure", false, "Allow publishing to insecure registry") - if err := cmdFlags.MarkHidden("insecure"); err != nil { - panic(fmt.Sprintf("error marking insecure as hidden: %v", err)) - } cmdFlags.BoolVar(&flags.publish, "publish", false, "Publish to registry") cmdFlags.BoolVar(&flags.all, "all", false, "Add all of the list's images if the images to add are lists/index") diff --git a/internal/commands/manifest_exists.go b/internal/commands/manifest_exists.go index 7b022f274b..79a0e770c8 100644 --- a/internal/commands/manifest_exists.go +++ b/internal/commands/manifest_exists.go @@ -8,8 +8,6 @@ import ( // ManifestExists checks if a manifest list exists in local storage func ManifestExists(logger logging.Logger, pack PackClient) *cobra.Command { - // var flags ManifestDeleteFlags - cmd := &cobra.Command{ Use: "exists [manifest-list]", Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index 49e42a2c98..7a4f99f85c 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -37,13 +37,13 @@ func NewErrors(errs []error) Errors { func (e Errors) Error() error { var errMsg string - if len(e.errs) > 0 { - for _, err := range e.errs { - errMsg += err.Error() - } + if len(e.errs) == 0 { + return nil + } - return errors.New(errMsg) + for _, err := range e.errs { + errMsg += err.Error() } - return nil + return errors.New(errMsg) } diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 1f8dc5256c..3e3b27514a 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -60,13 +60,11 @@ func (c *Client) AddManifest(ctx context.Context, ii string, image string, opts return err } - err = idx.Add(ref, ops...) - if err != nil { + if err = idx.Add(ref, ops...); err != nil { return err } - err = idx.Save() - if err != nil { + if err = idx.Save(); err != nil { return err } diff --git a/pkg/client/add_manifest_test.go b/pkg/client/add_manifest_test.go index f2b6e235ed..1dba278536 100644 --- a/pkg/client/add_manifest_test.go +++ b/pkg/client/add_manifest_test.go @@ -60,7 +60,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, os.RemoveAll(tmpDir)) }) it("should return an error if index doesn't exists locally", func() { - prepareIndexWithoutLocallyExists(t, *mockIndexFactory) + prepareIndexWithoutLocallyExists(*mockIndexFactory) err = subject.AddManifest( context.TODO(), "pack/index", @@ -233,7 +233,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { }) } -func prepareIndexWithoutLocallyExists(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) { +func prepareIndexWithoutLocallyExists(mockIndexFactory testmocks.MockIndexFactory) { mockIndexFactory. EXPECT(). LoadIndex(gomock.Any(), gomock.Any()). diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index fb2ecdd172..0fa77181a3 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -71,8 +71,7 @@ func (c *Client) AnnotateManifest(ctx context.Context, name string, image string } } - err = idx.Save() - if err != nil { + if err = idx.Save(); err != nil { return err } diff --git a/pkg/client/annotate_manifest_test.go b/pkg/client/annotate_manifest_test.go index dcf8adb852..1b0bb07a1b 100644 --- a/pkg/client/annotate_manifest_test.go +++ b/pkg/client/annotate_manifest_test.go @@ -60,7 +60,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }) when("successful when", func() { it("should return an error if index doesn't exists locally", func() { - prepareIndexWithoutLocallyExists(t, *mockIndexFactory) + prepareIndexWithoutLocallyExists(*mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), "pack/index", @@ -346,7 +346,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }) when("return an error when", func() { it("has no Index locally by given Name", func() { - prepareIndexWithoutLocallyExists(t, *mockIndexFactory) + prepareIndexWithoutLocallyExists(*mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), "some/repo", diff --git a/pkg/client/client.go b/pkg/client/client.go index 4fe74f7fb3..21c3a7076b 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -335,32 +335,27 @@ type indexFactory struct { } func (f *indexFactory) LoadIndex(repoName string, opts ...index.Option) (img imgutil.ImageIndex, err error) { - opts, err = withOptions(opts, f.keychain) - if err != nil { + if opts, err = withOptions(opts, f.keychain); err != nil { return nil, err } - img, err = local.NewIndex(repoName, opts...) - if err == nil { - return + if img, err = local.NewIndex(repoName, opts...); err == nil { + return img, err } - img, err = layout.NewIndex(repoName, opts...) - if err == nil { - return + if img, err = layout.NewIndex(repoName, opts...); err == nil { + return img, err } return nil, errors.Wrap(err, errors.Errorf("Image: '%s' not found", repoName).Error()) } func (f *indexFactory) FetchIndex(name string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { - opts, err = withOptions(opts, f.keychain) - if err != nil { + if opts, err = withOptions(opts, f.keychain); err != nil { return nil, err } - idx, err = remote.NewIndex(name, opts...) - if err != nil { + if idx, err = remote.NewIndex(name, opts...); err != nil { return idx, fmt.Errorf("ImageIndex in not available at registry") } @@ -368,22 +363,19 @@ func (f *indexFactory) FetchIndex(name string, opts ...index.Option) (idx imguti } func (f *indexFactory) FindIndex(repoName string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { - opts, err = withOptions(opts, f.keychain) - if err != nil { + if opts, err = withOptions(opts, f.keychain); err != nil { return nil, err } - idx, err = f.LoadIndex(repoName, opts...) - if err == nil { + if idx, err = f.LoadIndex(repoName, opts...); err == nil { return idx, err } return f.FetchIndex(repoName, opts...) } -func (f *indexFactory) CreateIndex(repoName string, opts ...index.Option) (imgutil.ImageIndex, error) { - opts, err := withOptions(opts, f.keychain) - if err != nil { +func (f *indexFactory) CreateIndex(repoName string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { + if opts, err = withOptions(opts, f.keychain); err != nil { return nil, err } @@ -391,13 +383,14 @@ func (f *indexFactory) CreateIndex(repoName string, opts ...index.Option) (imgut } func withOptions(ops []index.Option, keychain authn.Keychain) ([]index.Option, error) { + ops = append(ops, index.WithKeychain(keychain)) if xdgPath, ok := os.LookupEnv(xdgRuntimePath); ok { - return append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(xdgPath)), nil + return append(ops, index.WithXDGRuntimePath(xdgPath)), nil } home, err := iconfig.PackHome() if err != nil { return ops, err } - return append(ops, index.WithKeychain(keychain), index.WithXDGRuntimePath(filepath.Join(home, "manifests"))), nil + return append(ops, index.WithXDGRuntimePath(filepath.Join(home, "manifests"))), nil } diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 9f43514c0a..4fa01fc362 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -3,7 +3,6 @@ package client import ( "context" "fmt" - "sync" "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/index" @@ -25,9 +24,8 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin return fmt.Errorf("exits in your local storage, use 'pack manifest remove' if you want to delete it") } - _, err = c.indexFactory.CreateIndex(name, ops...) - if err != nil { - return + if _, err = c.indexFactory.CreateIndex(name, ops...); err != nil { + return err } index, err := c.indexFactory.LoadIndex(name, ops...) @@ -36,36 +34,32 @@ func (c *Client) CreateManifest(ctx context.Context, name string, images []strin } var errGroup, _ = errgroup.WithContext(ctx) - var wg sync.WaitGroup for _, img := range images { img := img - wg.Add(1) errGroup.Go(func() error { - return addImage(index, img, &wg, opts) + return addImage(index, img, opts) }) } - wg.Wait() if err = errGroup.Wait(); err != nil { return err } - err = index.Save() - if err != nil { + if err = index.Save(); err != nil { return err } fmt.Printf("successfully created index: '%s'\n", name) - if opts.Publish { - err = index.Push(imgutil.WithInsecure(opts.Insecure)) - if err != nil { - return err - } + if !opts.Publish { + return nil + } - fmt.Printf("successfully pushed '%s' to registry \n", name) + if err = index.Push(imgutil.WithInsecure(opts.Insecure)); err != nil { + return err } - return err + fmt.Printf("successfully pushed '%s' to registry \n", name) + return nil } func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []index.Option) { @@ -82,15 +76,11 @@ func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []index.Option } } -func addImage(index imgutil.ImageIndex, img string, wg *sync.WaitGroup, opts CreateManifestOptions) error { +func addImage(index imgutil.ImageIndex, img string, opts CreateManifestOptions) error { ref, err := ggcrName.ParseReference(img) if err != nil { return err } - if err = index.Add(ref, imgutil.WithAll(opts.All)); err != nil { - return err - } - wg.Done() - return nil + return index.Add(ref, imgutil.WithAll(opts.All)) } diff --git a/pkg/client/inspect_image.go b/pkg/client/inspect_image.go index e60f444a8f..84fa044301 100644 --- a/pkg/client/inspect_image.go +++ b/pkg/client/inspect_image.go @@ -223,7 +223,7 @@ func getRebasableLabel(labeled dist.Labeled) (bool, error) { return false, err } - if !isPresent && err == nil { + if !isPresent { rebasableOutput = true } diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index a906acd344..73f401385d 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -6,17 +6,15 @@ import ( ) // InspectManifest implements commands.PackClient. -func (c *Client) InspectManifest(ctx context.Context, name string) error { +func (c *Client) InspectManifest(ctx context.Context, name string) (err error) { idx, err := c.indexFactory.FindIndex(name) if err != nil { return err } - mfest, err := idx.Inspect() - if err != nil { - return err + if mfest, err := idx.Inspect(); err == nil { + fmt.Println(mfest) } - fmt.Println(mfest) - return nil + return err } diff --git a/pkg/client/inspect_manifest_test.go b/pkg/client/inspect_manifest_test.go index 1fdf4bc7be..55a2417918 100644 --- a/pkg/client/inspect_manifest_test.go +++ b/pkg/client/inspect_manifest_test.go @@ -61,7 +61,7 @@ func testInspectManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, os.RemoveAll(tmpDir)) }) it("should return an error when index not found", func() { - prepareFindIndexWithError(t, *mockIndexFactory) + prepareFindIndexWithError(*mockIndexFactory) err := subject.InspectManifest( context.TODO(), @@ -82,7 +82,7 @@ func testInspectManifest(t *testing.T, when spec.G, it spec.S) { }) } -func prepareFindIndexWithError(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) { +func prepareFindIndexWithError(mockIndexFactory testmocks.MockIndexFactory) { mockIndexFactory. EXPECT(). FindIndex(gomock.Any(), gomock.Any()). diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index d7050bfce2..a6bcff8ab7 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -20,36 +20,26 @@ func (c *Client) PushManifest(ctx context.Context, index string, opts PushManife return } - err = idx.Push(parseFalgsForImgUtil(opts)...) - if err != nil { - return + if err = idx.Push(parseFalgsForImgUtil(opts)...); err != nil { + return err } - if opts.Purge { - if err = idx.Delete(); err != nil { - return - } + if !opts.Purge { + fmt.Printf("successfully pushed index: '%s'\n", index) + return nil } - fmt.Printf("successfully pushed index: '%s'\n", index) - return + return idx.Delete() } func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions []imgutil.IndexPushOption) { + idxOptions = append(idxOptions, imgutil.WithInsecure(opts.Insecure)) switch opts.Format { case "oci": - return []imgutil.IndexPushOption{ - imgutil.WithFormat(types.OCIImageIndex), - imgutil.WithInsecure(opts.Insecure), - } + return append(idxOptions, imgutil.WithFormat(types.OCIImageIndex)) case "v2s2": - return []imgutil.IndexPushOption{ - imgutil.WithFormat(types.DockerManifestList), - imgutil.WithInsecure(opts.Insecure), - } + return append(idxOptions, imgutil.WithFormat(types.DockerManifestList)) default: - return []imgutil.IndexPushOption{ - imgutil.WithInsecure(opts.Insecure), - } + return idxOptions } } diff --git a/pkg/client/push_manifest_test.go b/pkg/client/push_manifest_test.go index 79ce6cb595..09e5414daf 100644 --- a/pkg/client/push_manifest_test.go +++ b/pkg/client/push_manifest_test.go @@ -55,7 +55,7 @@ func testPushManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, os.RemoveAll(tmpDir)) }) it("should not have local image index", func() { - prepareLoadIndexWithError(t, *mockIndexFactory) + prepareLoadIndexWithError(*mockIndexFactory) err := subject.PushManifest(context.TODO(), "some-index", PushManifestOptions{}) h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest("").Error()) @@ -69,7 +69,7 @@ func testPushManifest(t *testing.T, when spec.G, it spec.S) { }) } -func prepareLoadIndexWithError(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) { +func prepareLoadIndexWithError(mockIndexFactory testmocks.MockIndexFactory) { mockIndexFactory. EXPECT(). LoadIndex(gomock.Any(), gomock.Any()). diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index 941ea18f82..5df0de8908 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -6,17 +6,16 @@ import ( ) // DeleteManifest implements commands.PackClient. -func (c *Client) DeleteManifest(ctx context.Context, names []string) []error { - var errs []error +func (c *Client) DeleteManifest(ctx context.Context, names []string) (errs []error) { for _, name := range names { imgIndex, err := c.indexFactory.LoadIndex(name) - if err == nil { - if err := imgIndex.Delete(); err == nil { - continue - } + if err != nil { + errs = append(errs, err) + } + + if err := imgIndex.Delete(); err != nil { errs = append(errs, err) } - errs = append(errs, err) } if len(errs) == 0 { From 64edeba50ad481fc27a718d8dd0625290f6ca686 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Fri, 26 Apr 2024 13:35:00 -0500 Subject: [PATCH 56/79] WIP - doing some refactoring and testing Signed-off-by: Juan Bustamante --- go.mod | 2 +- go.sum | 6 +- internal/commands/commands.go | 11 +- internal/commands/manifest.go | 1 - internal/commands/manifest_add.go | 45 +- internal/commands/manifest_add_test.go | 99 +-- internal/commands/manifest_annotate.go | 36 +- internal/commands/manifest_annotate_test.go | 89 +-- internal/commands/manifest_create.go | 30 +- internal/commands/manifest_create_test.go | 14 - internal/commands/manifest_exists.go | 24 - internal/commands/manifest_exists_test.go | 88 --- internal/commands/manifest_inspect.go | 7 +- internal/commands/manifest_inspect_test.go | 11 +- internal/commands/manifest_push.go | 9 +- internal/commands/manifest_push_test.go | 8 +- .../commands/testmocks/mock_pack_client.go | 54 +- pkg/client/add_manifest.go | 63 +- pkg/client/add_manifest_test.go | 363 ++++++----- pkg/client/annotate_manifest.go | 90 +-- pkg/client/annotate_manifest_test.go | 562 ++++++------------ pkg/client/client.go | 98 +-- pkg/client/create_manifest.go | 86 +-- pkg/client/create_manifest_test.go | 194 +++--- pkg/client/exists_manifest.go | 17 - pkg/client/foo/index.json | 25 + pkg/client/foo/oci-layout | 3 + pkg/client/inspect_manifest.go | 21 +- pkg/client/inspect_manifest_test.go | 100 ++-- pkg/client/pack_index/index.json | 25 + pkg/client/pack_index/oci-layout | 3 + pkg/client/push_manifest.go | 47 +- pkg/client/push_manifest_test.go | 99 ++- pkg/client/remove_manifest.go | 2 +- pkg/client/remove_manifest_test.go | 4 +- pkg/client/rm_manifest.go | 7 +- pkg/client/rm_manifest_test.go | 11 +- pkg/client/some_repo/index.json | 11 + pkg/client/some_repo/oci-layout | 3 + pkg/index/index_factory.go | 84 +++ pkg/testmocks/mock_access_checker.go | 48 ++ pkg/testmocks/mock_docker_client.go | 12 +- pkg/testmocks/mock_index_factory.go | 23 +- 43 files changed, 1093 insertions(+), 1442 deletions(-) delete mode 100644 internal/commands/manifest_exists.go delete mode 100644 internal/commands/manifest_exists_test.go delete mode 100644 pkg/client/exists_manifest.go create mode 100755 pkg/client/foo/index.json create mode 100755 pkg/client/foo/oci-layout create mode 100644 pkg/client/pack_index/index.json create mode 100644 pkg/client/pack_index/oci-layout create mode 100755 pkg/client/some_repo/index.json create mode 100755 pkg/client/some_repo/oci-layout create mode 100644 pkg/index/index_factory.go create mode 100644 pkg/testmocks/mock_access_checker.go diff --git a/go.mod b/go.mod index ae1a898953..96a1468bfe 100644 --- a/go.mod +++ b/go.mod @@ -147,4 +147,4 @@ require ( go 1.22 -replace github.com/buildpacks/imgutil => github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304130223-abfcabf596ce \ No newline at end of file +replace github.com/buildpacks/imgutil => github.com/husni-faiz/imgutil v0.0.0-20240426182921-f79d933eb4e1 diff --git a/go.sum b/go.sum index 3c8d5b77b7..ff348acdc9 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,6 @@ github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7 github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304130223-abfcabf596ce h1:7ntd8izCev/sa05xJPxpWBK0Db0o+LX57yiX6ZCR5D0= -github.com/WYGIN/buildpacks-imgutil v0.0.0-20240304130223-abfcabf596ce/go.mod h1:7zUmt4wkVJNuXCZhQndEd3kvVGmWLVyzRFIQXTaeXlU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -93,8 +91,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/buildpacks/imgutil v0.0.0-20240422175901-30b002586ecc h1:Pxgpm4bIjgWY9a8r8xHlMYp6wzm5hIZHtw4Iux3XA0c= -github.com/buildpacks/imgutil v0.0.0-20240422175901-30b002586ecc/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk= github.com/buildpacks/lifecycle v0.19.4-0.20240416165809-82fdf23a6dbf h1:BN82j9sdRloVW0xQfbsF6+Bfz3F0NUzZ2LuM5UwX0s4= github.com/buildpacks/lifecycle v0.19.4-0.20240416165809-82fdf23a6dbf/go.mod h1:UbSf3hNT/QvQRBBtifDh5oMCMhIl0C8MwvoFPgjXhQE= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= @@ -226,6 +222,8 @@ github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLa github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0= github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/husni-faiz/imgutil v0.0.0-20240424232532-cbecaa88347b h1:qNx9ZGQ3N6pascREpfIDxkV95IOY2nCVmFOIYKmAV5I= +github.com/husni-faiz/imgutil v0.0.0-20240424232532-cbecaa88347b/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= diff --git a/internal/commands/commands.go b/internal/commands/commands.go index 2a237b51a7..edadfaf50f 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -32,14 +32,13 @@ type PackClient interface { InspectExtension(client.InspectExtensionOptions) (*client.ExtensionInfo, error) PullBuildpack(context.Context, client.PullBuildpackOptions) error DownloadSBOM(name string, options client.DownloadSBOMOptions) error - CreateManifest(ctx context.Context, name string, images []string, opts client.CreateManifestOptions) error - AnnotateManifest(ctx context.Context, name string, image string, opts client.ManifestAnnotateOptions) error - ExistsManifest(ctx context.Context, image string) error - AddManifest(ctx context.Context, index string, images string, opts client.ManifestAddOptions) error + CreateManifest(ctx context.Context, opts client.CreateManifestOptions) error + AnnotateManifest(ctx context.Context, opts client.ManifestAnnotateOptions) error + AddManifest(ctx context.Context, opts client.ManifestAddOptions) error DeleteManifest(ctx context.Context, name []string) []error RemoveManifest(ctx context.Context, name string, images []string) []error - PushManifest(ctx context.Context, index string, opts client.PushManifestOptions) error - InspectManifest(ctx context.Context, name string) error + PushManifest(client.PushManifestOptions) error + InspectManifest(string) error } func AddHelpFlag(cmd *cobra.Command, commandName string) { diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index 6ec1367d81..f6984a89fe 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -20,7 +20,6 @@ func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command cmd.AddCommand(ManifestInspect(logger, client)) cmd.AddCommand(ManifestPush(logger, client)) cmd.AddCommand(ManifestRemove(logger, client)) - cmd.AddCommand(ManifestExists(logger, client)) AddHelpFlag(cmd, "manifest") return cmd diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index d09b60e3bc..500e3202d4 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -1,67 +1,30 @@ package commands import ( - "errors" - "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" ) -// ManifestAddFlags define flags provided to the ManifestAdd -type ManifestAddFlags struct { - os, osVersion, osArch, osVariant string - osFeatures, features []string - annotations map[string]string - all bool -} - // ManifestAdd modifies a manifest list (Image index) and add a new image to the list of manifests. func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { - var flags ManifestAddFlags cmd := &cobra.Command{ Use: "add [OPTIONS] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), Short: "Add an image to a manifest list or image index.", Example: `pack manifest add cnbs/sample-package:hello-multiarch-universe \ - cnbs/sample-package:hello-universe-riscv-linux`, + cnbs/sample-package:hello-universe-riscv-linux`, Long: `manifest add modifies a manifest list (Image index) and add a new image to the list of manifests.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { - if err := validateManifestAddFlags(flags); err != nil { - return err - } - - return pack.AddManifest(cmd.Context(), args[0], args[1], client.ManifestAddOptions{ - OS: flags.os, - OSVersion: flags.osVersion, - OSArch: flags.osArch, - OSVariant: flags.osVariant, - OSFeatures: flags.osFeatures, - Features: flags.features, - Annotations: flags.annotations, - All: flags.all, + return pack.AddManifest(cmd.Context(), client.ManifestAddOptions{ + IndexRepoName: args[0], + RepoName: args[1], }) }), } - cmd.Flags().BoolVar(&flags.all, "all", false, "add all of the contents to the local list (applies only if is an index)") - cmd.Flags().StringVar(&flags.os, "os", "", "Set the operating system") - cmd.Flags().StringVar(&flags.osArch, "arch", "", "Set the architecture") - cmd.Flags().StringVar(&flags.osVariant, "variant", "", "Set the architecture variant") - cmd.Flags().StringVar(&flags.osVersion, "os-version", "", "Set the os-version") - cmd.Flags().StringSliceVar(&flags.osFeatures, "os-features", make([]string, 0), "Set the OSFeatures") - cmd.Flags().StringSliceVar(&flags.features, "features", make([]string, 0), "Set the Features") - cmd.Flags().StringToStringVar(&flags.annotations, "annotations", make(map[string]string, 0), "Set the annotations") - AddHelpFlag(cmd, "add") return cmd } - -func validateManifestAddFlags(flags ManifestAddFlags) error { - if (flags.os != "" && flags.osArch == "") || (flags.os == "" && flags.osArch != "") { - return errors.New("'os' or 'arch' is undefined") - } - return nil -} diff --git a/internal/commands/manifest_add_test.go b/internal/commands/manifest_add_test.go index 722dce2af4..0b148a1015 100644 --- a/internal/commands/manifest_add_test.go +++ b/internal/commands/manifest_add_test.go @@ -20,7 +20,7 @@ func TestManifestAddCommand(t *testing.T) { color.Disable(true) defer color.Disable(false) - spec.Run(t, "Commands", testManifestAddCommand, spec.Random(), spec.Report(report.Terminal{})) + spec.Run(t, "Commands", testManifestAddCommand, spec.Parallel(), spec.Report(report.Terminal{})) } func testManifestAddCommand(t *testing.T, when spec.G, it spec.S) { @@ -36,97 +36,46 @@ func testManifestAddCommand(t *testing.T, when spec.G, it spec.S) { logger = logging.NewLogWithWriters(&outBuf, &outBuf) mockController = gomock.NewController(t) mockClient = testmocks.NewMockPackClient(mockController) - command = commands.ManifestAdd(logger, mockClient) }) - it("should add image with current platform specs", func() { - prepareAddManifest(t, mockClient) - - command.SetArgs([]string{"some-index", "busybox:1.36-musl"}) - err := command.Execute() - h.AssertNil(t, err) - h.AssertEq(t, outBuf.String(), "") - }) - it("should add images with given platform", func() { - prepareAddManifest(t, mockClient) - command.SetArgs([]string{ - "some-index", - "busybox:1.36-musl", - "--os", - "linux", - "--arch", - "arm", - "--variant", - "v6", - "--os-version", - "22.04", + when("args are valid", func() { + it.Before(func() { + prepareAddManifest(t, mockClient) }) - err := command.Execute() - h.AssertNil(t, err) - h.AssertEq(t, outBuf.String(), "") - }) - it("should add return an error when platform's os and arch not defined", func() { - prepareAddManifest(t, mockClient) - command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--os", "linux"}) - err := command.Execute() - h.AssertEq(t, err.Error(), "'os' or 'arch' is undefined") - h.AssertEq(t, outBuf.String(), "ERROR: 'os' or 'arch' is undefined\n") - }) - it("should add all images", func() { - prepareAddManifest(t, mockClient) - - command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--all"}) - err := command.Execute() - h.AssertNil(t, err) - h.AssertEq(t, outBuf.String(), "") - }) - it("should return an error when features defined invalidly", func() { - prepareAddManifest(t, mockClient) - - command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--features"}) - err := command.Execute() - h.AssertEq(t, err.Error(), "flag needs an argument: --features") - }) - it("should return an error when osFeatures defined invalidly", func() { - prepareAddManifest(t, mockClient) - - command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--os-features"}) - err := command.Execute() - h.AssertEq(t, err.Error(), "flag needs an argument: --os-features") - }) - it("should return an error when invalid arg passed", func() { - prepareAddManifest(t, mockClient) + it("should add image with current platform specs", func() { + command.SetArgs([]string{"some-index", "busybox:1.36-musl"}) + err := command.Execute() + h.AssertNil(t, err) + h.AssertEq(t, outBuf.String(), "") + }) - command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--urls"}) - err := command.Execute() - h.AssertEq(t, err.Error(), "unknown flag: --urls") - }) - it("should return an error when annotations defined invalidly", func() { - prepareAddManifest(t, mockClient) + it("should have help flag", func() { + prepareAnnotateManifest(t, mockClient) - command.SetArgs([]string{"some-index", "busybox:1.36-musl", "--annotations", "some-key"}) - err := command.Execute() - h.AssertEq(t, err.Error(), `invalid argument "some-key" for "--annotations" flag: some-key must be formatted as key=value`) + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) }) - it("should have help flag", func() { - prepareAddManifest(t, mockClient) - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") + when("args are invalid", func() { + it("error when missing mandatory arguments", func() { + command.SetArgs([]string{"some-index"}) + err := command.Execute() + h.AssertNotNil(t, err) + h.AssertError(t, err, "accepts 2 arg(s), received 1") + }) }) } -func prepareAddManifest(t *testing.T, mockClient *testmocks.MockPackClient) { +func prepareAddManifest(_ *testing.T, mockClient *testmocks.MockPackClient) { mockClient. EXPECT(). AddManifest( gomock.Any(), gomock.Any(), - gomock.Any(), - gomock.Any(), ). AnyTimes(). Return(nil) diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 4dbf65eccf..1923479397 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -1,7 +1,6 @@ package commands import ( - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -10,9 +9,8 @@ import ( // ManifestAnnotateFlags define flags provided to the ManifestAnnotate type ManifestAnnotateFlags struct { - os, arch, variant, osVersion string - features, osFeatures, urls []string - annotations map[string]string + os, arch, variant string + annotations map[string]string } // ManifestAnnotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list. @@ -27,19 +25,14 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { cnbs/sample-package:hello-universe --arch amd64`, Long: `manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { - if err := validateManifestAnnotateFlags(flags); err != nil { - return err - } - return pack.AnnotateManifest(cmd.Context(), args[0], args[1], client.ManifestAnnotateOptions{ - OS: flags.os, - OSVersion: flags.osVersion, - OSArch: flags.arch, - OSVariant: flags.variant, - OSFeatures: flags.osFeatures, - Features: flags.features, - URLs: flags.urls, - Annotations: flags.annotations, + return pack.AnnotateManifest(cmd.Context(), client.ManifestAnnotateOptions{ + IndexRepoName: args[0], + RepoName: args[1], + OS: flags.os, + OSArch: flags.arch, + OSVariant: flags.variant, + Annotations: flags.annotations, }) }), } @@ -47,19 +40,8 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { cmd.Flags().StringVar(&flags.os, "os", "", "Set the architecture") cmd.Flags().StringVar(&flags.arch, "arch", "", "Set the architecture") cmd.Flags().StringVar(&flags.variant, "variant", "", "Set the architecture") - cmd.Flags().StringVar(&flags.osVersion, "os-version", "", "override the os `version` of the specified image") - cmd.Flags().StringSliceVar(&flags.features, "features", make([]string, 0), "override the `features` of the specified image") - cmd.Flags().StringSliceVar(&flags.urls, "urls", make([]string, 0), "override the `urls` of the specified image") - cmd.Flags().StringSliceVar(&flags.osFeatures, "os-features", make([]string, 0), "override the os `features` of the specified image") cmd.Flags().StringToStringVar(&flags.annotations, "annotations", make(map[string]string, 0), "set an `annotation` for the specified image") AddHelpFlag(cmd, "annotate") return cmd } - -func validateManifestAnnotateFlags(flags ManifestAnnotateFlags) error { - if (flags.os != "" && flags.arch == "") || (flags.os == "" && flags.arch != "") { - return errors.New("'os' or 'arch' is undefined") - } - return nil -} diff --git a/internal/commands/manifest_annotate_test.go b/internal/commands/manifest_annotate_test.go index adc0db1675..3da693ce48 100644 --- a/internal/commands/manifest_annotate_test.go +++ b/internal/commands/manifest_annotate_test.go @@ -39,77 +39,54 @@ func testManifestAnnotateCommand(t *testing.T, when spec.G, it spec.S) { command = commands.ManifestAnnotate(logger, mockClient) }) - it("should annotate images with given flags", func() { - prepareAnnotateManifest(t, mockClient) - command.SetArgs([]string{ - "some-index", - "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", - "--os", - "linux", - "--arch", - "arm", - "--variant", - "v6", - "--os-version", - "22.04", + when("args are valid", func() { + it.Before(func() { + prepareAnnotateManifest(t, mockClient) }) - h.AssertNil(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") - }) - it("should return an error when platform's os and arch not defined", func() { - prepareAnnotateManifest(t, mockClient) - - command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--os", "linux"}) - err := command.Execute() - h.AssertEq(t, err.Error(), "'os' or 'arch' is undefined") - h.AssertEq(t, outBuf.String(), "ERROR: 'os' or 'arch' is undefined\n") - }) - it("should return an error when features defined invalidly", func() { - prepareAnnotateManifest(t, mockClient) - command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--features"}) - err := command.Execute() - h.AssertEq(t, err.Error(), "flag needs an argument: --features") - }) - it("should return an error when osFeatures defined invalidly", func() { - prepareAnnotateManifest(t, mockClient) - - command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--os-features"}) - err := command.Execute() - h.AssertEq(t, err.Error(), "flag needs an argument: --os-features") - }) - it("should return an error when urls defined invalidly", func() { - prepareAnnotateManifest(t, mockClient) + it("should annotate images with given flags", func() { + command.SetArgs([]string{ + "some-index", + "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", + "--os", + "linux", + "--arch", + "arm", + "--variant", + "v6", + }) + h.AssertNilE(t, command.Execute()) + }) - command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--urls"}) - err := command.Execute() - h.AssertEq(t, err.Error(), "flag needs an argument: --urls") + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + }) }) - it("should return an error when annotations defined invalidly", func() { - prepareAnnotateManifest(t, mockClient) - command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--annotations", "some-key"}) - err := command.Execute() - h.AssertEq(t, err.Error(), `invalid argument "some-key" for "--annotations" flag: some-key must be formatted as key=value`) - }) - it("should have help flag", func() { - prepareAnnotateManifest(t, mockClient) + when("args are invalid", func() { + it("error when missing mandatory arguments", func() { + command.SetArgs([]string{"some-index"}) + err := command.Execute() + h.AssertNotNil(t, err) + h.AssertError(t, err, "accepts 2 arg(s), received 1") + }) - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") + it("should return an error when annotations defined invalidly", func() { + command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--annotations", "some-key"}) + err := command.Execute() + h.AssertEq(t, err.Error(), `invalid argument "some-key" for "--annotations" flag: some-key must be formatted as key=value`) + }) }) } -func prepareAnnotateManifest(t *testing.T, mockClient *testmocks.MockPackClient) { +func prepareAnnotateManifest(_ *testing.T, mockClient *testmocks.MockPackClient) { mockClient. EXPECT(). AnnotateManifest( gomock.Any(), gomock.Any(), - gomock.Any(), - gomock.Any(), ). AnyTimes(). Return(nil) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 982d81459b..2b053c18f8 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -1,7 +1,6 @@ package commands import ( - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -10,8 +9,8 @@ import ( // ManifestCreateFlags define flags provided to the ManifestCreate type ManifestCreateFlags struct { - format, os, arch string - insecure, publish, all bool + format string + insecure, publish bool } // ManifestCreate creates an image-index/image-list for a multi-arch image @@ -27,19 +26,14 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cnbs/sample-package:hello-universe-windows`, Long: `Generate manifest list for a multi-arch image which will be stored locally for manipulating images within index`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - if err := validateManifestCreateFlags(flags); err != nil { - return err - } - return pack.CreateManifest( cmd.Context(), - args[0], - args[1:], client.CreateManifestOptions{ - Format: flags.format, - Insecure: flags.insecure, - Publish: flags.publish, - All: flags.all, + IndexRepoName: args[0], + RepoNames: args[1:], + Format: flags.format, + Insecure: flags.insecure, + Publish: flags.publish, }, ) }), @@ -48,19 +42,9 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmdFlags := cmd.Flags() cmdFlags.StringVarP(&flags.format, "format", "f", "v2s2", "Format to save image index as ('OCI' or 'V2S2')") - cmdFlags.StringVar(&flags.os, "os", "", "If any of the specified images is a list/index, choose the one for `os`") - cmdFlags.StringVar(&flags.arch, "arch", "", "If any of the specified images is a list/index, choose the one for `arch`") cmdFlags.BoolVar(&flags.insecure, "insecure", false, "Allow publishing to insecure registry") cmdFlags.BoolVar(&flags.publish, "publish", false, "Publish to registry") - cmdFlags.BoolVar(&flags.all, "all", false, "Add all of the list's images if the images to add are lists/index") AddHelpFlag(cmd, "create") return cmd } - -func validateManifestCreateFlags(flags ManifestCreateFlags) error { - if (flags.os != "" && flags.arch == "") || (flags.os == "" && flags.arch != "") { - return errors.New("'os' or 'arch' is undefined") - } - return nil -} diff --git a/internal/commands/manifest_create_test.go b/internal/commands/manifest_create_test.go index 890bfe302f..b7927685a9 100644 --- a/internal/commands/manifest_create_test.go +++ b/internal/commands/manifest_create_test.go @@ -45,10 +45,6 @@ func testManifestCreateCommand(t *testing.T, when spec.G, it spec.S) { command.SetArgs([]string{ "some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", - "--os", - "linux", - "--arch", - "arm", "--format", "v2s2", "--insecure", @@ -57,14 +53,6 @@ func testManifestCreateCommand(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, command.Execute()) h.AssertEq(t, outBuf.String(), "") }) - it("should return an error when platform's os and arch not defined", func() { - prepareCreateManifest(t, mockClient) - - command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--os", "linux"}) - err := command.Execute() - h.AssertEq(t, err.Error(), "'os' or 'arch' is undefined") - h.AssertEq(t, outBuf.String(), "ERROR: 'os' or 'arch' is undefined\n") - }) it("should have help flag", func() { prepareCreateManifest(t, mockClient) @@ -80,8 +68,6 @@ func prepareCreateManifest(t *testing.T, mockClient *testmocks.MockPackClient) { CreateManifest( gomock.Any(), gomock.Any(), - gomock.Any(), - gomock.Any(), ). AnyTimes(). Return(nil) diff --git a/internal/commands/manifest_exists.go b/internal/commands/manifest_exists.go deleted file mode 100644 index 79a0e770c8..0000000000 --- a/internal/commands/manifest_exists.go +++ /dev/null @@ -1,24 +0,0 @@ -package commands - -import ( - "github.com/spf13/cobra" - - "github.com/buildpacks/pack/pkg/logging" -) - -// ManifestExists checks if a manifest list exists in local storage -func ManifestExists(logger logging.Logger, pack PackClient) *cobra.Command { - cmd := &cobra.Command{ - Use: "exists [manifest-list]", - Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), - Short: "Check if the given manifest list exists in local storage", - Example: `pack manifest exists cnbs/sample-package:hello-multiarch-universe`, - Long: `Checks if a manifest list exists in local storage`, - RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - return pack.ExistsManifest(cmd.Context(), args[0]) - }), - } - - AddHelpFlag(cmd, "exists") - return cmd -} diff --git a/internal/commands/manifest_exists_test.go b/internal/commands/manifest_exists_test.go deleted file mode 100644 index 7ee7081d97..0000000000 --- a/internal/commands/manifest_exists_test.go +++ /dev/null @@ -1,88 +0,0 @@ -package commands_test - -import ( - "bytes" - "testing" - - "github.com/golang/mock/gomock" - "github.com/heroku/color" - "github.com/pkg/errors" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" - "github.com/spf13/cobra" - - "github.com/buildpacks/pack/internal/commands" - "github.com/buildpacks/pack/internal/commands/testmocks" - "github.com/buildpacks/pack/pkg/logging" - h "github.com/buildpacks/pack/testhelpers" -) - -func TestManifestExistsCommand(t *testing.T) { - color.Disable(true) - defer color.Disable(false) - - spec.Run(t, "Commands", testManifestExistsCommand, spec.Random(), spec.Report(report.Terminal{})) -} - -func testManifestExistsCommand(t *testing.T, when spec.G, it spec.S) { - var ( - command *cobra.Command - logger *logging.LogWithWriters - outBuf bytes.Buffer - mockController *gomock.Controller - mockClient *testmocks.MockPackClient - ) - - it.Before(func() { - logger = logging.NewLogWithWriters(&outBuf, &outBuf) - mockController = gomock.NewController(t) - mockClient = testmocks.NewMockPackClient(mockController) - - command = commands.ManifestExists(logger, mockClient) - }) - it("should annotate images with given flags", func() { - prepareExistsManifest(t, mockClient) - - command.SetArgs([]string{ - "some-index", - }) - h.AssertNil(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") - }) - it("should return an error when index doesn't exists", func() { - prepareNotExistsManifest(t, mockClient) - - command.SetArgs([]string{"some-other-index"}) - err := command.Execute() - h.AssertEq(t, err.Error(), "no index found with given name") - }) - it("should have help flag", func() { - prepareExistsManifest(t, mockClient) - - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") - }) -} - -func prepareExistsManifest(t *testing.T, mockClient *testmocks.MockPackClient) { - mockClient. - EXPECT(). - ExistsManifest( - gomock.Any(), - gomock.Any(), - ). - AnyTimes(). - Return(nil) -} - -func prepareNotExistsManifest(t *testing.T, mockClient *testmocks.MockPackClient) { - mockClient. - EXPECT(). - ExistsManifest( - gomock.Any(), - gomock.Any(), - ). - AnyTimes(). - Return(errors.New("no index found with given name")) -} diff --git a/internal/commands/manifest_inspect.go b/internal/commands/manifest_inspect.go index 6a61934787..49727ddebe 100644 --- a/internal/commands/manifest_inspect.go +++ b/internal/commands/manifest_inspect.go @@ -1,6 +1,8 @@ package commands import ( + "errors" + "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/logging" @@ -16,7 +18,10 @@ func ManifestInspect(logger logging.Logger, pack PackClient) *cobra.Command { Long: `manifest inspect shows the manifest information stored in local storage. The inspect command will help users to view how their local manifest list looks like`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - return pack.InspectManifest(cmd.Context(), args[0]) + if args[0] == "" { + errors.New("'' is required") + } + return pack.InspectManifest(args[0]) }), } diff --git a/internal/commands/manifest_inspect_test.go b/internal/commands/manifest_inspect_test.go index 87c0b4f95f..d99f8df094 100644 --- a/internal/commands/manifest_inspect_test.go +++ b/internal/commands/manifest_inspect_test.go @@ -47,13 +47,7 @@ func testManifestInspectCommand(t *testing.T, when spec.G, it spec.S) { }) h.AssertNil(t, command.Execute()) }) - it("should return an error when index not passed", func() { - prepareInspectManifest(t, mockClient) - command.SetArgs([]string(nil)) - err := command.Execute() - h.AssertNotNil(t, err) - }) it("should have help flag", func() { prepareInspectManifest(t, mockClient) @@ -66,10 +60,7 @@ func testManifestInspectCommand(t *testing.T, when spec.G, it spec.S) { func prepareInspectManifest(t *testing.T, mockClient *testmocks.MockPackClient) { mockClient. EXPECT(). - InspectManifest( - gomock.Any(), - gomock.Any(), - ). + InspectManifest(gomock.Any()). AnyTimes(). Return(nil) } diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index a62abec273..109b5fce72 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -25,10 +25,11 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { Long: `manifest push pushes a manifest list (Image index) to a registry. Once a manifest list is ready to be published into the registry, the push command can be used`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - return pack.PushManifest(cmd.Context(), args[0], client.PushManifestOptions{ - Format: flags.format, - Insecure: flags.insecure, - Purge: flags.purge, + return pack.PushManifest(client.PushManifestOptions{ + IndexRepoName: args[0], + Format: flags.format, + Insecure: flags.insecure, + Purge: flags.purge, }) }), } diff --git a/internal/commands/manifest_push_test.go b/internal/commands/manifest_push_test.go index 8a431a64c2..42360e6654 100644 --- a/internal/commands/manifest_push_test.go +++ b/internal/commands/manifest_push_test.go @@ -68,25 +68,21 @@ func testManifestPushCommand(t *testing.T, when spec.G, it spec.S) { }) } -func preparePushManifest(t *testing.T, mockClient *testmocks.MockPackClient) { +func preparePushManifest(_ *testing.T, mockClient *testmocks.MockPackClient) { mockClient. EXPECT(). PushManifest( gomock.Any(), - gomock.Any(), - gomock.Any(), ). AnyTimes(). Return(nil) } -func preparePushManifestWithError(t *testing.T, mockClient *testmocks.MockPackClient) { +func preparePushManifestWithError(_ *testing.T, mockClient *testmocks.MockPackClient) { mockClient. EXPECT(). PushManifest( gomock.Any(), - gomock.Any(), - gomock.Any(), ). AnyTimes(). Return(errors.New("unable to push Image")) diff --git a/internal/commands/testmocks/mock_pack_client.go b/internal/commands/testmocks/mock_pack_client.go index 4bdf29af24..58d7699353 100644 --- a/internal/commands/testmocks/mock_pack_client.go +++ b/internal/commands/testmocks/mock_pack_client.go @@ -37,31 +37,31 @@ func (m *MockPackClient) EXPECT() *MockPackClientMockRecorder { } // AddManifest mocks base method. -func (m *MockPackClient) AddManifest(arg0 context.Context, arg1, arg2 string, arg3 client.ManifestAddOptions) error { +func (m *MockPackClient) AddManifest(arg0 context.Context, arg1 client.ManifestAddOptions) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddManifest", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "AddManifest", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // AddManifest indicates an expected call of AddManifest. -func (mr *MockPackClientMockRecorder) AddManifest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) AddManifest(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddManifest", reflect.TypeOf((*MockPackClient)(nil).AddManifest), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddManifest", reflect.TypeOf((*MockPackClient)(nil).AddManifest), arg0, arg1) } // AnnotateManifest mocks base method. -func (m *MockPackClient) AnnotateManifest(arg0 context.Context, arg1, arg2 string, arg3 client.ManifestAnnotateOptions) error { +func (m *MockPackClient) AnnotateManifest(arg0 context.Context, arg1 client.ManifestAnnotateOptions) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AnnotateManifest", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "AnnotateManifest", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // AnnotateManifest indicates an expected call of AnnotateManifest. -func (mr *MockPackClientMockRecorder) AnnotateManifest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) AnnotateManifest(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnnotateManifest", reflect.TypeOf((*MockPackClient)(nil).AnnotateManifest), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AnnotateManifest", reflect.TypeOf((*MockPackClient)(nil).AnnotateManifest), arg0, arg1) } // Build mocks base method. @@ -93,17 +93,17 @@ func (mr *MockPackClientMockRecorder) CreateBuilder(arg0, arg1 interface{}) *gom } // CreateManifest mocks base method. -func (m *MockPackClient) CreateManifest(arg0 context.Context, arg1 string, arg2 []string, arg3 client.CreateManifestOptions) error { +func (m *MockPackClient) CreateManifest(arg0 context.Context, arg1 client.CreateManifestOptions) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateManifest", arg0, arg1, arg2, arg3) + ret := m.ctrl.Call(m, "CreateManifest", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // CreateManifest indicates an expected call of CreateManifest. -func (mr *MockPackClientMockRecorder) CreateManifest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) CreateManifest(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateManifest", reflect.TypeOf((*MockPackClient)(nil).CreateManifest), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateManifest", reflect.TypeOf((*MockPackClient)(nil).CreateManifest), arg0, arg1) } // DeleteManifest mocks base method. @@ -134,20 +134,6 @@ func (mr *MockPackClientMockRecorder) DownloadSBOM(arg0, arg1 interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadSBOM", reflect.TypeOf((*MockPackClient)(nil).DownloadSBOM), arg0, arg1) } -// ExistsManifest mocks base method. -func (m *MockPackClient) ExistsManifest(arg0 context.Context, arg1 string) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ExistsManifest", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// ExistsManifest indicates an expected call of ExistsManifest. -func (mr *MockPackClientMockRecorder) ExistsManifest(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExistsManifest", reflect.TypeOf((*MockPackClient)(nil).ExistsManifest), arg0, arg1) -} - // InspectBuilder mocks base method. func (m *MockPackClient) InspectBuilder(arg0 string, arg1 bool, arg2 ...client.BuilderInspectionModifier) (*client.BuilderInfo, error) { m.ctrl.T.Helper() @@ -214,17 +200,17 @@ func (mr *MockPackClientMockRecorder) InspectImage(arg0, arg1 interface{}) *gomo } // InspectManifest mocks base method. -func (m *MockPackClient) InspectManifest(arg0 context.Context, arg1 string) error { +func (m *MockPackClient) InspectManifest(arg0 string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InspectManifest", arg0, arg1) + ret := m.ctrl.Call(m, "InspectManifest", arg0) ret0, _ := ret[0].(error) return ret0 } // InspectManifest indicates an expected call of InspectManifest. -func (mr *MockPackClientMockRecorder) InspectManifest(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) InspectManifest(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectManifest", reflect.TypeOf((*MockPackClient)(nil).InspectManifest), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InspectManifest", reflect.TypeOf((*MockPackClient)(nil).InspectManifest), arg0) } // NewBuildpack mocks base method. @@ -284,17 +270,17 @@ func (mr *MockPackClientMockRecorder) PullBuildpack(arg0, arg1 interface{}) *gom } // PushManifest mocks base method. -func (m *MockPackClient) PushManifest(arg0 context.Context, arg1 string, arg2 client.PushManifestOptions) error { +func (m *MockPackClient) PushManifest(arg0 client.PushManifestOptions) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "PushManifest", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "PushManifest", arg0) ret0, _ := ret[0].(error) return ret0 } // PushManifest indicates an expected call of PushManifest. -func (mr *MockPackClientMockRecorder) PushManifest(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) PushManifest(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushManifest", reflect.TypeOf((*MockPackClient)(nil).PushManifest), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PushManifest", reflect.TypeOf((*MockPackClient)(nil).PushManifest), arg0) } // Rebase mocks base method. diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 3e3b27514a..367f3ecfc6 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -4,70 +4,41 @@ import ( "context" "fmt" - "github.com/buildpacks/imgutil" "github.com/google/go-containerregistry/pkg/name" + + "github.com/buildpacks/pack/pkg/image" ) type ManifestAddOptions struct { - OS, OSVersion, OSArch, OSVariant string - OSFeatures, Features []string - Annotations map[string]string - All bool + // Image index we want to update + IndexRepoName string + + // Name of image we wish to add into the image index + RepoName string } // AddManifest implements commands.PackClient. -func (c *Client) AddManifest(ctx context.Context, ii string, image string, opts ManifestAddOptions) (err error) { - idx, err := c.indexFactory.LoadIndex(ii) +func (c *Client) AddManifest(ctx context.Context, opts ManifestAddOptions) (err error) { + idx, err := c.indexFactory.LoadIndex(opts.IndexRepoName) if err != nil { return err } - var ops = make([]imgutil.IndexAddOption, 0) - if opts.All { - ops = append(ops, imgutil.WithAll(opts.All)) - } - - if opts.OS != "" { - ops = append(ops, imgutil.WithOS(opts.OS)) - } - - if opts.OSArch != "" { - ops = append(ops, imgutil.WithArchitecture(opts.OSArch)) - } - - if opts.OSVariant != "" { - ops = append(ops, imgutil.WithVariant(opts.OSVariant)) - } - - if opts.OSVersion != "" { - ops = append(ops, imgutil.WithOSVersion(opts.OSVersion)) - } - - if len(opts.Features) != 0 { - ops = append(ops, imgutil.WithFeatures(opts.Features)) - } - - if len(opts.OSFeatures) != 0 { - ops = append(ops, imgutil.WithOSFeatures(opts.OSFeatures)) - } - - if len(opts.Annotations) != 0 { - ops = append(ops, imgutil.WithAnnotations(opts.Annotations)) - } - - ref, err := name.ParseReference(image, name.Insecure, name.WeakValidation) + imageRef, err := name.ParseReference(opts.RepoName, name.WeakValidation) if err != nil { - return err + return fmt.Errorf("'%s' is not a valid manifest reference: %s", opts.RepoName, err) } - if err = idx.Add(ref, ops...); err != nil { + imageToAdd, err := c.imageFetcher.Fetch(ctx, imageRef.Name(), image.FetchOptions{Daemon: false}) + if err != nil { return err } - if err = idx.Save(); err != nil { - return err + idx.AddManifest(imageToAdd.UnderlyingImage()) + if err = idx.SaveDir(); err != nil { + return fmt.Errorf("'%s' could not be saved in the local storage: %s", opts.RepoName, err) } - fmt.Printf("successfully added to index: '%s'\n", image) + c.logger.Infof("successfully added to index: '%s'\n", opts.RepoName) return nil } diff --git a/pkg/client/add_manifest_test.go b/pkg/client/add_manifest_test.go index 1dba278536..48f5abdd7b 100644 --- a/pkg/client/add_manifest_test.go +++ b/pkg/client/add_manifest_test.go @@ -11,13 +11,13 @@ import ( "github.com/buildpacks/imgutil/fakes" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/google/go-containerregistry/pkg/v1/random" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" + ifakes "github.com/buildpacks/pack/internal/fakes" "github.com/buildpacks/pack/pkg/logging" "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" @@ -26,6 +26,8 @@ import ( func TestAddManifest(t *testing.T) { color.Disable(true) defer color.Disable(false) + + // TODO I think we can make this test to run in parallel spec.Run(t, "build", testAddManifest, spec.Report(report.Terminal{})) } @@ -33,6 +35,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { var ( mockController *gomock.Controller mockIndexFactory *testmocks.MockIndexFactory + fakeImageFetcher *ifakes.FakeImageFetcher out bytes.Buffer logger logging.Logger subject *Client @@ -40,199 +43,128 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { tmpDir string ) - when("#Add", func() { - it.Before(func() { - logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) - mockController = gomock.NewController(t) - mockIndexFactory = testmocks.NewMockIndexFactory(mockController) - - subject, err = NewClient( - WithLogger(logger), - WithIndexFactory(mockIndexFactory), - WithExperimental(true), - WithKeychain(authn.DefaultKeychain), - ) - h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) - h.AssertNil(t, err) - }) - it.After(func() { - mockController.Finish() - h.AssertNil(t, os.RemoveAll(tmpDir)) - }) - it("should return an error if index doesn't exists locally", func() { - prepareIndexWithoutLocallyExists(*mockIndexFactory) - err = subject.AddManifest( - context.TODO(), - "pack/index", - "pack/image", - ManifestAddOptions{}, - ) - - h.AssertEq(t, err.Error(), "index not found locally") - }) - it("should add the given image", func() { - digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") - h.AssertNil(t, err) - - idx := prepareLoadIndex(t, *mockIndexFactory) - err = subject.AddManifest( - context.TODO(), - "pack/index", - digest.Name(), - ManifestAddOptions{}, - ) - h.AssertNil(t, err) - - _, err = idx.OS(digest) - h.AssertNil(t, err) - }) - it("should add index with OS and Arch specific", func() { - digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") - h.AssertNil(t, err) - - idx := prepareLoadIndex( - t, - *mockIndexFactory, - ) - - err = subject.AddManifest( - context.TODO(), - "pack/index", - digest.Name(), - ManifestAddOptions{ - OS: "some-os", - OSArch: "some-arch", - }, - ) - h.AssertNil(t, err) - - os, err := idx.OS(digest) - h.AssertNil(t, err) - h.AssertEq(t, os, "some-os") - - arch, err := idx.Architecture(digest) - h.AssertNil(t, err) - h.AssertEq(t, arch, "some-arch") - }) - it("should add with variant", func() { - digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") - h.AssertNil(t, err) - - idx := prepareLoadIndex( - t, - *mockIndexFactory, - ) - - err = subject.AddManifest( - context.TODO(), - "pack/index", - digest.Name(), - ManifestAddOptions{ - OSVariant: "some-variant", - }, - ) - h.AssertNil(t, err) - - variant, err := idx.Variant(digest) - h.AssertNil(t, err) - h.AssertEq(t, variant, "some-variant") - }) - it("should add with osVersion", func() { - digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") - h.AssertNil(t, err) - - idx := prepareLoadIndex( - t, - *mockIndexFactory, - ) - - err = subject.AddManifest( - context.TODO(), - "pack/index", - digest.Name(), - ManifestAddOptions{ - OSVersion: "some-os-version", - }, - ) - h.AssertNil(t, err) - - osVersion, err := idx.OSVersion(digest) - h.AssertNil(t, err) - h.AssertEq(t, osVersion, "some-os-version") - }) - it("should add with features", func() { - digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") - h.AssertNil(t, err) - - idx := prepareLoadIndex( - t, - *mockIndexFactory, - ) - - err = subject.AddManifest( - context.TODO(), - "pack/index", - digest.Name(), - ManifestAddOptions{ - Features: []string{"some-features"}, - }, - ) - h.AssertNil(t, err) - - features, err := idx.Features(digest) - h.AssertNil(t, err) - h.AssertEq(t, features, []string{"some-features"}) - }) - it("should add with osFeatures", func() { - digest, err := name.NewDigest("pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171") - h.AssertNil(t, err) - - idx := prepareLoadIndex( - t, - *mockIndexFactory, - ) - - err = subject.AddManifest( - context.TODO(), - "pack/index", - digest.Name(), - ManifestAddOptions{ - Features: []string{"some-os-features"}, - }, - ) - h.AssertNil(t, err) - - osFeatures, err := idx.Features(digest) - h.AssertNil(t, err) - h.AssertEq(t, osFeatures, []string{"some-os-features"}) + it.Before(func() { + fakeImageFetcher = ifakes.NewFakeImageFetcher() + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + tmpDir, err = os.MkdirTemp("", "add-manifest-test") + h.AssertNil(t, err) + os.Setenv("XDG_RUNTIME_DIR", tmpDir) + + subject, err = NewClient( + WithLogger(logger), + WithFetcher(fakeImageFetcher), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + + // Create a remote image to be fetched when adding to the image index + fakeImage := setUpRemoteImageForIndex(t, nil) + fakeImageFetcher.RemoteImages["index.docker.io/pack/image:latest"] = fakeImage + + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) + + when("#AddManifest", func() { + when("index doesn't exists", func() { + it.Before(func() { + prepareIndexWithoutLocallyExists(*mockIndexFactory) + }) + + it("should return an error", func() { + err = subject.AddManifest( + context.TODO(), + ManifestAddOptions{ + IndexRepoName: "pack/none-existent-index", + RepoName: "pack/image", + }, + ) + h.AssertError(t, err, "index not found locally") + }) }) - it("should add with annotations", func() { - digestStr := "pack/image@sha256:15c46ced65c6abed6a27472a7904b04273e9a8091a5627badd6ff016ab073171" - digest, err := name.NewDigest(digestStr) - h.AssertNil(t, err) - - idx := prepareLoadIndex( - t, - *mockIndexFactory, - ) - - err = subject.AddManifest( - context.TODO(), - "pack/index", - digestStr, - ManifestAddOptions{ - Annotations: map[string]string{"some-key": "some-value"}, - }, - ) - h.AssertNil(t, err) - - annos, err := idx.Annotations(digest) - h.AssertNil(t, err) - h.AssertEq(t, annos, map[string]string{"some-key": "some-value"}) + + when("index exists", func() { + when("no errors on save", func() { + it.Before(func() { + prepareLoadIndex(t, "pack/index", *mockIndexFactory) + }) + + it("adds the given image", func() { + err = subject.AddManifest( + context.TODO(), + ManifestAddOptions{ + IndexRepoName: "pack/index", + RepoName: "pack/image", + }, + ) + h.AssertNil(t, err) + h.AssertContains(t, out.String(), "successfully added to index: 'pack/image'") + }) + + it("error when invalid manifest reference name is used", func() { + err = subject.AddManifest( + context.TODO(), + ManifestAddOptions{ + IndexRepoName: "pack/index", + RepoName: "pack@@image", + }, + ) + h.AssertNotNil(t, err) + h.AssertError(t, err, "is not a valid manifest reference") + }) + + it("error when manifest reference doesn't exist in a registry", func() { + err = subject.AddManifest( + context.TODO(), + ManifestAddOptions{ + IndexRepoName: "pack/index", + RepoName: "pack/image-not-found", + }, + ) + h.AssertNotNil(t, err) + h.AssertError(t, err, "does not exist in registry") + }) + }) + + when("errors on save", func() { + it.Before(func() { + prepareLoadIndexWithErrorOnSave(t, "pack/index-error-on-saved", *mockIndexFactory) + }) + + it("error when manifest couldn't be saved locally", func() { + err = subject.AddManifest( + context.TODO(), + ManifestAddOptions{ + IndexRepoName: "pack/index-error-on-saved", + RepoName: "pack/image", + }, + ) + h.AssertNotNil(t, err) + h.AssertError(t, err, "could not be saved in the local storage") + }) + }) }) }) } +func setUpRemoteImageForIndex(t *testing.T, identifier imgutil.Identifier) *testImage { + fakeCNBImage := fakes.NewImage("pack/image", "", identifier) + underlyingImage, err := random.Image(1024, 1) + h.AssertNil(t, err) + return &testImage{ + Image: fakeCNBImage, + underlyingImage: underlyingImage, + } +} + func prepareIndexWithoutLocallyExists(mockIndexFactory testmocks.MockIndexFactory) { mockIndexFactory. EXPECT(). @@ -240,15 +172,60 @@ func prepareIndexWithoutLocallyExists(mockIndexFactory testmocks.MockIndexFactor Return(nil, errors.New("index not found locally")) } -func prepareLoadIndex(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { - idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) +func randomCNBIndex(t *testing.T, repoName string) *imgutil.CNBIndex { + ridx, err := random.Index(1024, 1, 2) h.AssertNil(t, err) + options := &imgutil.IndexOptions{ + BaseIndex: ridx, + } + idx, err := imgutil.NewCNBIndex(repoName, *options) + h.AssertNil(t, err) + return idx +} +func prepareLoadIndex(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { + idx := randomCNBIndex(t, repoName) mockIndexFactory. EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). + LoadIndex(gomock.Eq(repoName), gomock.Any()). + Return(idx, nil). + AnyTimes() + + return idx +} + +func prepareLoadIndexWithErrorOnSave(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { + cnbIdx := randomCNBIndex(t, repoName) + idx := &testIndex{ + CNBIndex: *cnbIdx, + errorOnSave: true, + } + mockIndexFactory. + EXPECT(). + LoadIndex(gomock.Eq(repoName), gomock.Any()). Return(idx, nil). AnyTimes() return idx } + +type testImage struct { + *fakes.Image + underlyingImage v1.Image +} + +func (t *testImage) UnderlyingImage() v1.Image { + return t.underlyingImage +} + +type testIndex struct { + imgutil.CNBIndex + errorOnSave bool +} + +func (i *testIndex) SaveDir() error { + if i.errorOnSave { + return errors.New("something failed writing the index on disk") + } + return i.CNBIndex.SaveDir() +} diff --git a/pkg/client/annotate_manifest.go b/pkg/client/annotate_manifest.go index 0fa77181a3..655ed287bf 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/annotate_manifest.go @@ -4,77 +4,83 @@ import ( "context" "fmt" - ggcrName "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/name" + + "github.com/buildpacks/pack/pkg/image" ) type ManifestAnnotateOptions struct { - OS, OSVersion, OSArch, OSVariant string - OSFeatures, Features, URLs []string - Annotations map[string]string + // Image index we want to update + IndexRepoName string + + // Name of image we wish to update into the image index + RepoName string + + // 'os' of the image we wish to update in the image index + OS string + + // 'architecture' of the image we wish to update in the image index + OSArch string + + // 'os variant' of the image we wish to update in the image index + OSVariant string + + // 'annotations' of the image we wish to update in the image index + Annotations map[string]string } // AnnotateManifest implements commands.PackClient. -func (c *Client) AnnotateManifest(ctx context.Context, name string, image string, opts ManifestAnnotateOptions) error { - idx, err := c.indexFactory.LoadIndex(name) +func (c *Client) AnnotateManifest(ctx context.Context, opts ManifestAnnotateOptions) error { + idx, err := c.indexFactory.LoadIndex(opts.IndexRepoName) if err != nil { return err } - digest, err := ggcrName.NewDigest(image, ggcrName.Insecure, ggcrName.WeakValidation) + imageRef, err := name.ParseReference(opts.RepoName, name.WeakValidation) + if err != nil { + return fmt.Errorf("'%s' is not a valid manifest reference: %s", opts.RepoName, err) + } + + imageToAnnotate, err := c.imageFetcher.Fetch(ctx, imageRef.Name(), image.FetchOptions{Daemon: false}) if err != nil { return err } - if opts.OS != "" { - if err := idx.SetOS(digest, opts.OS); err != nil { - return err - } + hash, err := imageToAnnotate.Identifier() + if err != nil { + return err } - if opts.OSVersion != "" { - if err := idx.SetOSVersion(digest, opts.OSVersion); err != nil { - return err - } + + digest, err := name.NewDigest(hash.String()) + if err != nil { + return err } - if len(opts.OSFeatures) != 0 { - if err := idx.SetOSFeatures(digest, opts.OSFeatures); err != nil { - return err + + if opts.OS != "" { + if err = idx.SetOS(digest, opts.OS); err != nil { + return fmt.Errorf("'%s' setting the 'os': %s", opts.RepoName, err) } } if opts.OSArch != "" { - if err := idx.SetArchitecture(digest, opts.OSArch); err != nil { - return err + if err = idx.SetArchitecture(digest, opts.OSArch); err != nil { + return fmt.Errorf("'%s' setting the 'arch': %s", opts.RepoName, err) } } if opts.OSVariant != "" { - if err := idx.SetVariant(digest, opts.OSVariant); err != nil { - return err - } - } - if len(opts.Features) != 0 { - if err := idx.SetFeatures(digest, opts.Features); err != nil { - return err - } - } - if len(opts.OSFeatures) != 0 { - if err := idx.SetOSFeatures(digest, opts.OSFeatures); err != nil { - return err - } - } - if len(opts.URLs) != 0 { - if err := idx.SetURLs(digest, opts.URLs); err != nil { - return err + if err = idx.SetVariant(digest, opts.OSVariant); err != nil { + return fmt.Errorf("'%s' setting the 'os variant': %s", opts.RepoName, err) } } if len(opts.Annotations) != 0 { - if err := idx.SetAnnotations(digest, opts.Annotations); err != nil { - return err + if err = idx.SetAnnotations(digest, opts.Annotations); err != nil { + return fmt.Errorf("'%s' updating the 'annotations': %s", opts.RepoName, err) } } - if err = idx.Save(); err != nil { - return err + if err = idx.SaveDir(); err != nil { + return fmt.Errorf("'%s' could not be saved in the local storage: %s", opts.RepoName, err) } - fmt.Printf("successfully annotated image '%s' in index '%s'\n", image, name) + c.logger.Infof("successfully annotated image '%s' in index '%s'\n", opts.RepoName, opts.IndexRepoName) return nil } diff --git a/pkg/client/annotate_manifest_test.go b/pkg/client/annotate_manifest_test.go index 1b0bb07a1b..1353dce16c 100644 --- a/pkg/client/annotate_manifest_test.go +++ b/pkg/client/annotate_manifest_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/buildpacks/imgutil" - "github.com/buildpacks/imgutil/fakes" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" @@ -15,6 +14,7 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" + ifakes "github.com/buildpacks/pack/internal/fakes" "github.com/buildpacks/pack/pkg/logging" "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" @@ -25,6 +25,7 @@ const digestStr = "sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf751 func TestAnnotateManifest(t *testing.T) { color.Disable(true) defer color.Disable(false) + // TODO I think we can make this test to run in parallel spec.Run(t, "build", testAnnotateManifest, spec.Report(report.Terminal{})) } @@ -32,6 +33,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { var ( mockController *gomock.Controller mockIndexFactory *testmocks.MockIndexFactory + fakeImageFetcher *ifakes.FakeImageFetcher out bytes.Buffer logger logging.Logger subject *Client @@ -39,418 +41,246 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { tmpDir string ) - when("#Add", func() { - it.Before(func() { - logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) - mockController = gomock.NewController(t) - mockIndexFactory = testmocks.NewMockIndexFactory(mockController) - - subject, err = NewClient( - WithLogger(logger), - WithIndexFactory(mockIndexFactory), - WithExperimental(true), - WithKeychain(authn.DefaultKeychain), - ) - h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) - h.AssertNil(t, err) - }) - it.After(func() { - mockController.Finish() - h.AssertNil(t, os.RemoveAll(tmpDir)) - }) - when("successful when", func() { - it("should return an error if index doesn't exists locally", func() { - prepareIndexWithoutLocallyExists(*mockIndexFactory) - err = subject.AnnotateManifest( - context.TODO(), - "pack/index", - "pack/image", - ManifestAnnotateOptions{}, - ) - - h.AssertEq(t, err.Error(), "index not found locally") - }) - it("should set OS for given image", func() { - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) - - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - digest.Name(), - ManifestAnnotateOptions{ - OS: "some-os", - }, - ) - h.AssertNil(t, err) - - os, err := idx.OS(digest) - h.AssertNil(t, err) - h.AssertEq(t, os, "some-os") - }) - it("should set Arch for given image", func() { - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) - - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - digest.Name(), - ManifestAnnotateOptions{ - OSArch: "some-arch", - }, - ) - h.AssertNil(t, err) - - arch, err := idx.Architecture(digest) - h.AssertNil(t, err) - h.AssertEq(t, arch, "some-arch") - }) - it("should set Variant for given image", func() { - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) - - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - digest.Name(), - ManifestAnnotateOptions{ - OSVariant: "some-variant", - }, - ) - h.AssertNil(t, err) - - variant, err := idx.Variant(digest) - h.AssertNil(t, err) - h.AssertEq(t, variant, "some-variant") - }) - it("should set OSVersion for given image", func() { - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) - - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - digest.Name(), - ManifestAnnotateOptions{ - OSVersion: "some-osVersion", - }, - ) - h.AssertNil(t, err) - - osVersion, err := idx.OSVersion(digest) - h.AssertNil(t, err) - h.AssertEq(t, osVersion, "some-osVersion") - }) - it("should set Features for given image", func() { - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) - - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - digest.Name(), - ManifestAnnotateOptions{ - Features: []string{"some-features"}, - }, - ) - h.AssertNil(t, err) - - features, err := idx.Features(digest) - h.AssertNil(t, err) - h.AssertEq(t, features, []string{"some-features"}) - }) - it("should set OSFeatures for given image", func() { - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) - - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - digest.Name(), - ManifestAnnotateOptions{ - OSFeatures: []string{"some-osFeatures"}, - }, - ) - h.AssertNil(t, err) - - osFeatures, err := idx.OSFeatures(digest) - h.AssertNil(t, err) - h.AssertEq(t, osFeatures, []string{"some-osFeatures", "some-osFeatures"}) - }) - it("should set URLs for given image", func() { - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) - - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - digest.Name(), - ManifestAnnotateOptions{ - URLs: []string{"some-urls"}, - }, - ) - h.AssertNil(t, err) + it.Before(func() { + fakeImageFetcher = ifakes.NewFakeImageFetcher() + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + tmpDir, err = os.MkdirTemp("", "annotate-manifest-test") + h.AssertNil(t, err) + os.Setenv("XDG_RUNTIME_DIR", tmpDir) + + subject, err = NewClient( + WithLogger(logger), + WithFetcher(fakeImageFetcher), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) - urls, err := idx.URLs(digest) - h.AssertNil(t, err) - h.AssertEq(t, urls, []string{"some-urls"}) + when("#AnnotateManifest", func() { + when("index doesn't exists", func() { + it.Before(func() { + prepareIndexWithoutLocallyExists(*mockIndexFactory) }) - it("should set Annotations for given image", func() { - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) + it("should return an error", func() { err = subject.AnnotateManifest( context.TODO(), - "some/repo", - digest.Name(), ManifestAnnotateOptions{ - Annotations: map[string]string{"some-key": "some-value"}, + IndexRepoName: "pack/index", + RepoName: "pack/image", }, ) - h.AssertNil(t, err) - - annos, err := idx.Annotations(digest) - h.AssertNil(t, err) - h.AssertEq(t, annos, map[string]string{"some-key": "some-value"}) + h.AssertEq(t, err.Error(), "index not found locally") }) - it("should save annotated index", func() { - var ( - fakeOS = "some-os" - fakeArch = "some-arch" - fakeVariant = "some-variant" - fakeVersion = "some-osVersion" - fakeFeatures = []string{"some-features"} - fakeOSFeatures = []string{"some-OSFeatures"} - fakeURLs = []string{"some-urls"} - fakeAnnotations = map[string]string{"some-key": "some-value"} - ) - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) - - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - digest.Name(), - ManifestAnnotateOptions{ - OS: fakeOS, - OSArch: fakeArch, - OSVariant: fakeVariant, - OSVersion: fakeVersion, - Features: fakeFeatures, - OSFeatures: fakeOSFeatures, - URLs: fakeURLs, - Annotations: fakeAnnotations, - }, - ) - h.AssertNil(t, err) - - err = idx.Save() - h.AssertNil(t, err) - - os, err := idx.OS(digest) - h.AssertNil(t, err) - h.AssertEq(t, os, fakeOS) - - arch, err := idx.Architecture(digest) - h.AssertNil(t, err) - h.AssertEq(t, arch, fakeArch) - - variant, err := idx.Variant(digest) - h.AssertNil(t, err) - h.AssertEq(t, variant, fakeVariant) - - osVersion, err := idx.OSVersion(digest) - h.AssertNil(t, err) - h.AssertEq(t, osVersion, fakeVersion) - - features, err := idx.Features(digest) - h.AssertNil(t, err) - h.AssertEq(t, features, fakeFeatures) - - osFeatures, err := idx.OSFeatures(digest) - h.AssertNil(t, err) - h.AssertEq(t, osFeatures, []string{"some-OSFeatures", "some-OSFeatures"}) - - urls, err := idx.URLs(digest) - h.AssertNil(t, err) - h.AssertEq(t, urls, fakeURLs) + }) - annos, err := idx.Annotations(digest) - h.AssertNil(t, err) - h.AssertEq(t, annos, fakeAnnotations) + when("index exists", func() { + when("no errors on save", func() { + var digest name.Digest + var idx imgutil.ImageIndex + + it.Before(func() { + idx = prepareLoadIndex(t, "some/repo", *mockIndexFactory) + imgIdx, ok := idx.(*imgutil.CNBIndex) + h.AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) + + digest, err = name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + + fakeImage := setUpRemoteImageForIndex(t, digest) + fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage + }) + + it("should set OS for given image", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: "some/repo", + RepoName: digest.Name(), + OS: "some-os", + }, + ) + h.AssertNil(t, err) + + os, err := idx.OS(digest) + h.AssertNil(t, err) + h.AssertEq(t, os, "some-os") + }) + + it("should set Arch for given image", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: "some/repo", + RepoName: digest.Name(), + OSArch: "some-arch", + }, + ) + h.AssertNil(t, err) + + arch, err := idx.Architecture(digest) + h.AssertNil(t, err) + h.AssertEq(t, arch, "some-arch") + }) + + it("should set Variant for given image", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: "some/repo", + RepoName: digest.Name(), + OSVariant: "some-variant", + }, + ) + h.AssertNil(t, err) + + variant, err := idx.Variant(digest) + h.AssertNil(t, err) + h.AssertEq(t, variant, "some-variant") + }) + + it("should set Annotations for given image", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: "some/repo", + RepoName: digest.Name(), + Annotations: map[string]string{"some-key": "some-value"}, + }, + ) + h.AssertNil(t, err) + + annos, err := idx.Annotations(digest) + h.AssertNil(t, err) + h.AssertEq(t, annos, map[string]string{"some-key": "some-value"}) + }) + + it("should save annotated index", func() { + var ( + fakeOS = "some-os" + fakeArch = "some-arch" + fakeVariant = "some-variant" + fakeAnnotations = map[string]string{"some-key": "some-value"} + ) + + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: "some/repo", + RepoName: digest.Name(), + OS: fakeOS, + OSArch: fakeArch, + OSVariant: fakeVariant, + Annotations: fakeAnnotations, + }, + ) + h.AssertNil(t, err) + + err = idx.SaveDir() + h.AssertNil(t, err) + + os, err := idx.OS(digest) + h.AssertNil(t, err) + h.AssertEq(t, os, fakeOS) + + arch, err := idx.Architecture(digest) + h.AssertNil(t, err) + h.AssertEq(t, arch, fakeArch) + + variant, err := idx.Variant(digest) + h.AssertNil(t, err) + h.AssertEq(t, variant, fakeVariant) + + /* TODO Getters are still available in the imgutil.ImageIndex interface but we removed the Setters + osVersion, err := idx.OSVersion(digest) + h.AssertNil(t, err) + h.AssertEq(t, osVersion, fakeVersion) + + osFeatures, err := idx.OSFeatures(digest) + h.AssertNil(t, err) + h.AssertEq(t, osFeatures, []string{"some-OSFeatures", "some-OSFeatures"}) + */ + + annos, err := idx.Annotations(digest) + h.AssertNil(t, err) + h.AssertEq(t, annos, fakeAnnotations) + }) }) }) - when("return an error when", func() { + + when("return an error", func() { it("has no Index locally by given Name", func() { prepareIndexWithoutLocallyExists(*mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), - "some/repo", - "", - ManifestAnnotateOptions{}, + ManifestAnnotateOptions{ + IndexRepoName: "some/repo", + RepoName: "", + }, ) h.AssertEq(t, err.Error(), "index not found locally") }) it("has no image with given digest for OS", func() { - prepareLoadIndex(t, *mockIndexFactory) + prepareLoadIndex(t, "some/repo", *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), - "some/repo", - "busybox@"+digestStr, ManifestAnnotateOptions{ - OS: "some-os", + IndexRepoName: "some/repo", + RepoName: "busybox@" + digestStr, + OS: "some-os", }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) + h.AssertNotNil(t, err) }) it("has no image with given digest for Arch", func() { - prepareLoadIndex(t, *mockIndexFactory) + prepareLoadIndex(t, "some/repo", *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), - "some/repo", - "busybox@"+digestStr, ManifestAnnotateOptions{ - OSArch: "some-arch", + IndexRepoName: "some/repo", + RepoName: "busybox@" + digestStr, + OSArch: "some-arch", }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) + h.AssertNotNil(t, err) }) it("has no image with given digest for Variant", func() { - prepareLoadIndex(t, *mockIndexFactory) - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - "busybox@"+digestStr, - ManifestAnnotateOptions{ - OSVariant: "some-variant", - }, - ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) - }) - it("has no image with given digest for osVersion", func() { - prepareLoadIndex(t, *mockIndexFactory) - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - "busybox@"+digestStr, - ManifestAnnotateOptions{ - OSVersion: "some-osVersion", - }, - ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) - }) - it("has no image with given digest for Features", func() { - prepareLoadIndex(t, *mockIndexFactory) - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - "busybox@"+digestStr, - ManifestAnnotateOptions{ - Features: []string{"some-features"}, - }, - ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) - }) - it("has no image with given digest for OSFeatures", func() { - prepareLoadIndex(t, *mockIndexFactory) - err = subject.AnnotateManifest( - context.TODO(), - "some/repo", - "busybox@"+digestStr, - ManifestAnnotateOptions{ - OSFeatures: []string{"some-osFeatures"}, - }, - ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) - }) - it("has no image with given digest for URLs", func() { - prepareLoadIndex(t, *mockIndexFactory) + prepareLoadIndex(t, "some/repo", *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), - "some/repo", - "busybox@"+digestStr, ManifestAnnotateOptions{ - URLs: []string{"some-urls"}, + IndexRepoName: "some/repo", + RepoName: "busybox@" + digestStr, + OSVariant: "some-variant", }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) + h.AssertNotNil(t, err) }) it("has no image with given digest for Annotations", func() { - prepareLoadIndex(t, *mockIndexFactory) + prepareLoadIndex(t, "some/repo", *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), - "some/repo", - "busybox@"+digestStr, ManifestAnnotateOptions{ - Annotations: map[string]string{"some-key": "some-value"}, + IndexRepoName: "some/repo", + RepoName: "busybox@" + digestStr, + Annotations: map[string]string{"some-key": "some-value"}, }, ) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest(digestStr).Error()) + h.AssertNotNil(t, err) }) }) }) diff --git a/pkg/client/client.go b/pkg/client/client.go index 21c3a7076b..4555589f2b 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -16,13 +16,10 @@ package client import ( "context" - "fmt" "os" "path/filepath" "github.com/buildpacks/imgutil" - "github.com/buildpacks/imgutil/index" - "github.com/buildpacks/imgutil/layout" "github.com/buildpacks/imgutil/local" "github.com/buildpacks/imgutil/remote" dockerClient "github.com/docker/docker/client" @@ -36,6 +33,7 @@ import ( "github.com/buildpacks/pack/pkg/blob" "github.com/buildpacks/pack/pkg/buildpack" "github.com/buildpacks/pack/pkg/image" + "github.com/buildpacks/pack/pkg/index" "github.com/buildpacks/pack/pkg/logging" ) @@ -83,14 +81,16 @@ type ImageFactory interface { // IndexFactory is an interface representing the ability to create a ImageIndex/ManifestList. type IndexFactory interface { - // create ManifestList locally - CreateIndex(repoName string, opts ...index.Option) (imgutil.ImageIndex, error) - // load ManifestList from local storage with the given name - LoadIndex(reponame string, opts ...index.Option) (imgutil.ImageIndex, error) - // Fetch ManifestList from Registry with the given name - FetchIndex(name string, opts ...index.Option) (imgutil.ImageIndex, error) + // Exists return true if the given index exits in the local storage + Exists(repoName string) bool + // CreateIndex creates ManifestList locally + CreateIndex(repoName string, opts ...imgutil.IndexOption) (imgutil.ImageIndex, error) + // LoadIndex loads ManifestList from local storage with the given name + LoadIndex(reponame string, opts ...imgutil.IndexOption) (imgutil.ImageIndex, error) + // FetchIndex fetches ManifestList from Registry with the given name + FetchIndex(name string, opts ...imgutil.IndexOption) (imgutil.ImageIndex, error) // FindIndex will find Index locally then on remote - FindIndex(name string, opts ...index.Option) (imgutil.ImageIndex, error) + FindIndex(name string, opts ...imgutil.IndexOption) (imgutil.ImageIndex, error) } //go:generate mockgen -package testmocks -destination ../testmocks/mock_buildpack_downloader.go github.com/buildpacks/pack/pkg/client BuildpackDownloader @@ -270,10 +270,16 @@ func NewClient(opts ...Option) (*Client, error) { } if client.indexFactory == nil { - client.indexFactory = &indexFactory{ - keychain: client.keychain, - xdgRuntimePath: os.Getenv("XDG_RUNTIME_DIR"), + packHome, err := iconfig.PackHome() + if err != nil { + return nil, errors.Wrap(err, "getting pack home") + } + indexRootStoragePath := filepath.Join(packHome, "manifests") + + if xdgPath, ok := os.LookupEnv(xdgRuntimePath); ok { + indexRootStoragePath = xdgPath } + client.indexFactory = index.NewIndexFactory(client.keychain, indexRootStoragePath) } if client.accessChecker == nil { @@ -328,69 +334,3 @@ func (f *imageFactory) NewImage(repoName string, daemon bool, imageOS string) (i return remote.NewImage(repoName, f.keychain, remote.WithDefaultPlatform(platform)) } - -type indexFactory struct { - keychain authn.Keychain - xdgRuntimePath string -} - -func (f *indexFactory) LoadIndex(repoName string, opts ...index.Option) (img imgutil.ImageIndex, err error) { - if opts, err = withOptions(opts, f.keychain); err != nil { - return nil, err - } - - if img, err = local.NewIndex(repoName, opts...); err == nil { - return img, err - } - - if img, err = layout.NewIndex(repoName, opts...); err == nil { - return img, err - } - - return nil, errors.Wrap(err, errors.Errorf("Image: '%s' not found", repoName).Error()) -} - -func (f *indexFactory) FetchIndex(name string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { - if opts, err = withOptions(opts, f.keychain); err != nil { - return nil, err - } - - if idx, err = remote.NewIndex(name, opts...); err != nil { - return idx, fmt.Errorf("ImageIndex in not available at registry") - } - - return idx, err -} - -func (f *indexFactory) FindIndex(repoName string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { - if opts, err = withOptions(opts, f.keychain); err != nil { - return nil, err - } - - if idx, err = f.LoadIndex(repoName, opts...); err == nil { - return idx, err - } - - return f.FetchIndex(repoName, opts...) -} - -func (f *indexFactory) CreateIndex(repoName string, opts ...index.Option) (idx imgutil.ImageIndex, err error) { - if opts, err = withOptions(opts, f.keychain); err != nil { - return nil, err - } - - return index.NewIndex(repoName, opts...) -} - -func withOptions(ops []index.Option, keychain authn.Keychain) ([]index.Option, error) { - ops = append(ops, index.WithKeychain(keychain)) - if xdgPath, ok := os.LookupEnv(xdgRuntimePath); ok { - return append(ops, index.WithXDGRuntimePath(xdgPath)), nil - } - - home, err := iconfig.PackHome() - if err != nil { - return ops, err - } - return append(ops, index.WithXDGRuntimePath(filepath.Join(home, "manifests"))), nil -} diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 4fa01fc362..7e36abec3c 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -5,64 +5,76 @@ import ( "fmt" "github.com/buildpacks/imgutil" - "github.com/buildpacks/imgutil/index" - ggcrName "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/types" - "golang.org/x/sync/errgroup" + "github.com/pkg/errors" + + "github.com/buildpacks/pack/pkg/image" ) type CreateManifestOptions struct { - Format, Registry string - Insecure, Publish, All bool + // Image index we want to create + IndexRepoName string + + // Name of images we wish to add into the image index + RepoNames []string + + // Media type of the index + Format string + + // true if we want to publish to an insecure registry + Insecure bool + + // true if we want to push the index to a registry after creating + Publish bool } // CreateManifest implements commands.PackClient. -func (c *Client) CreateManifest(ctx context.Context, name string, images []string, opts CreateManifestOptions) (err error) { +func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) (err error) { ops := parseOptsToIndexOptions(opts) - _, err = c.indexFactory.LoadIndex(name, ops...) - if err == nil { - return fmt.Errorf("exits in your local storage, use 'pack manifest remove' if you want to delete it") - } - if _, err = c.indexFactory.CreateIndex(name, ops...); err != nil { - return err + if c.indexFactory.Exists(opts.IndexRepoName) { + return errors.New("exits in your local storage, use 'pack manifest remove' if you want to delete it") } - index, err := c.indexFactory.LoadIndex(name, ops...) + index, err := c.indexFactory.CreateIndex(opts.IndexRepoName, ops...) if err != nil { return err } - var errGroup, _ = errgroup.WithContext(ctx) - for _, img := range images { - img := img - errGroup.Go(func() error { - return addImage(index, img, opts) - }) - } + for _, repoName := range opts.RepoNames { + // TODO same code to add_manifest.go externalize it! + imageRef, err := name.ParseReference(repoName, name.WeakValidation) + if err != nil { + return fmt.Errorf("'%s' is not a valid manifest reference: %s", repoName, err) + } - if err = errGroup.Wait(); err != nil { - return err + imageToAdd, err := c.imageFetcher.Fetch(ctx, imageRef.Name(), image.FetchOptions{Daemon: false}) + if err != nil { + return err + } + + index.AddManifest(imageToAdd.UnderlyingImage()) } - if err = index.Save(); err != nil { - return err + if err = index.SaveDir(); err != nil { + return fmt.Errorf("'%s' could not be saved in the local storage: %s", opts.IndexRepoName, err) } - fmt.Printf("successfully created index: '%s'\n", name) + c.logger.Infof("successfully created index: '%s'\n", opts.IndexRepoName) if !opts.Publish { return nil } - if err = index.Push(imgutil.WithInsecure(opts.Insecure)); err != nil { + if err = index.Push(ops...); err != nil { return err } - fmt.Printf("successfully pushed '%s' to registry \n", name) + c.logger.Infof("successfully pushed '%s' to registry \n", opts.IndexRepoName) return nil } -func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []index.Option) { +func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []imgutil.IndexOption) { var format types.MediaType switch opts.Format { case "oci": @@ -70,17 +82,13 @@ func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []index.Option default: format = types.DockerManifestList } - return []index.Option{ - index.WithFormat(format), - index.WithInsecure(opts.Insecure), + if opts.Insecure { + return []imgutil.IndexOption{ + imgutil.WithMediaType(format), + imgutil.WithInsecure(), + } } -} - -func addImage(index imgutil.ImageIndex, img string, opts CreateManifestOptions) error { - ref, err := ggcrName.ParseReference(img) - if err != nil { - return err + return []imgutil.IndexOption{ + imgutil.WithMediaType(format), } - - return index.Add(ref, imgutil.WithAll(opts.All)) } diff --git a/pkg/client/create_manifest_test.go b/pkg/client/create_manifest_test.go index 51c5cc7d55..fb7707d83d 100644 --- a/pkg/client/create_manifest_test.go +++ b/pkg/client/create_manifest_test.go @@ -3,21 +3,19 @@ package client import ( "bytes" "context" - "errors" "os" "testing" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/google/go-containerregistry/pkg/v1/random" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" "github.com/buildpacks/imgutil" - "github.com/buildpacks/imgutil/fakes" + ifakes "github.com/buildpacks/pack/internal/fakes" "github.com/buildpacks/pack/pkg/logging" "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" @@ -33,141 +31,101 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { var ( mockController *gomock.Controller mockIndexFactory *testmocks.MockIndexFactory + fakeImageFetcher *ifakes.FakeImageFetcher out bytes.Buffer logger logging.Logger subject *Client err error tmpDir string ) + + it.Before(func() { + fakeImageFetcher = ifakes.NewFakeImageFetcher() + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + tmpDir, err = os.MkdirTemp("", "add-manifest-test") + h.AssertNil(t, err) + os.Setenv("XDG_RUNTIME_DIR", tmpDir) + + subject, err = NewClient( + WithLogger(logger), + WithFetcher(fakeImageFetcher), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) + when("#CreateManifest", func() { - it.Before(func() { - logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) - mockController = gomock.NewController(t) - mockIndexFactory = testmocks.NewMockIndexFactory(mockController) - - subject, err = NewClient( - WithLogger(logger), - WithIndexFactory(mockIndexFactory), - WithExperimental(true), - WithKeychain(authn.DefaultKeychain), - ) - h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) - h.AssertNil(t, err) - }) - it.After(func() { - mockController.Finish() - h.AssertNil(t, os.RemoveAll(tmpDir)) - }) - it("create manifest", func() { - prepareMockImageFactoryForValidCreateIndex(t, mockIndexFactory) - err := subject.CreateManifest( - context.TODO(), - "pack/imgutil", - []string{"busybox:1.36-musl"}, - CreateManifestOptions{ - Insecure: true, - }, - ) - h.AssertNil(t, err) - }) - it("create manifests ignoring all option", func() { - prepareMockImageFactoryForValidCreateIndex(t, mockIndexFactory) - err := subject.CreateManifest( - context.TODO(), - "pack/imgutil", - []string{"busybox:1.36-musl"}, - CreateManifestOptions{ - Insecure: true, - All: true, - }, - ) - h.AssertNil(t, err) - }) - it("create manifests with all nested images", func() { - prepareMockImageFactoryForValidCreateIndexWithAll(t, mockIndexFactory) - err := subject.CreateManifest( - context.TODO(), - "pack/imgutil", - []string{"busybox:1.36-musl"}, - CreateManifestOptions{ - Insecure: true, - All: true, - }, - ) - h.AssertNil(t, err) + when("index doesn't exists", func() { + when("remote manifest exists", func() { + it.Before(func() { + fakeImage := setUpRemoteImageForIndex(t, nil) + fakeImageFetcher.RemoteImages["index.docker.io/library/busybox:1.36-musl"] = fakeImage + + prepareMockImageFactoryForValidCreateIndex(t, mockIndexFactory) + }) + + when("no errors on save", func() { + it("creates the index with the given manifest", func() { + err = subject.CreateManifest( + context.TODO(), + CreateManifestOptions{ + IndexRepoName: "pack/imgutil", + RepoNames: []string{"busybox:1.36-musl"}, + Insecure: true, + }, + ) + h.AssertNil(t, err) + }) + }) + }) }) - it("return an error when index exists already", func() { - prepareMockImageFactoryForInvalidCreateIndexExistsLoadIndex(t, mockIndexFactory) - err := subject.CreateManifest( - context.TODO(), - "pack/imgutil", - []string{"busybox:1.36-musl"}, - CreateManifestOptions{ - Insecure: true, - }, - ) - h.AssertEq(t, err.Error(), "exits in your local storage, use 'pack manifest remove' if you want to delete it") + + when("index exists", func() { + it.Before(func() { + mockIndexFactory.EXPECT(). + Exists(gomock.Any()).AnyTimes().Return(true) + }) + + it("return an error when index exists already", func() { + err = subject.CreateManifest( + context.TODO(), + CreateManifestOptions{ + IndexRepoName: "pack/imgutil", + RepoNames: []string{"busybox:1.36-musl"}, + Insecure: true, + }, + ) + h.AssertEq(t, err.Error(), "exits in your local storage, use 'pack manifest remove' if you want to delete it") + }) }) }) } func prepareMockImageFactoryForValidCreateIndex(t *testing.T, mockIndexFactory *testmocks.MockIndexFactory) { - idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) + ridx, err := random.Index(1024, 1, 2) h.AssertNil(t, err) - mockIndexFactory.EXPECT(). - CreateIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(idx, err) - mockIndexFactory.EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - After( - mockIndexFactory.EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - Times(1). - Return( - imgutil.ImageIndex(nil), - errors.New("no image exists"), - ), - ). - Return(idx, err) -} - -func prepareMockImageFactoryForValidCreateIndexWithAll(t *testing.T, mockIndexFactory *testmocks.MockIndexFactory) { - idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) + options := &imgutil.IndexOptions{ + BaseIndex: ridx, + } + idx, err := imgutil.NewCNBIndex("foo", *options) h.AssertNil(t, err) mockIndexFactory.EXPECT(). - CreateIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(idx, err) - mockIndexFactory.EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - After( - mockIndexFactory.EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - Times(1). - Return( - imgutil.ImageIndex(nil), - errors.New("no image exists"), - ), - ). - Return(idx, err) -} - -func prepareMockImageFactoryForInvalidCreateIndexExistsLoadIndex(t *testing.T, mockIndexFactory *testmocks.MockIndexFactory) { - idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) - h.AssertNil(t, err) + Exists(gomock.Any()).AnyTimes().Return(false) mockIndexFactory.EXPECT(). CreateIndex(gomock.Any(), gomock.Any()). AnyTimes(). Return(idx, err) - - mockIndexFactory.EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(idx, err) } diff --git a/pkg/client/exists_manifest.go b/pkg/client/exists_manifest.go deleted file mode 100644 index 1fc09f5fb0..0000000000 --- a/pkg/client/exists_manifest.go +++ /dev/null @@ -1,17 +0,0 @@ -package client - -import ( - "context" - "fmt" - - "github.com/pkg/errors" -) - -func (c *Client) ExistsManifest(ctx context.Context, image string) error { - if _, err := c.indexFactory.LoadIndex(image); err != nil { - return errors.Errorf("image '%s' is not found", image) - } - - fmt.Printf("index '%s' exists \n", image) - return nil -} diff --git a/pkg/client/foo/index.json b/pkg/client/foo/index.json new file mode 100755 index 0000000000..901ca69362 --- /dev/null +++ b/pkg/client/foo/index.json @@ -0,0 +1,25 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 424, + "digest": "sha256:f61aed5d0a49f08c7da6df2fb8b16a821ea4dc2567e8979411dc3374e1f3498d" + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 424, + "digest": "sha256:94eb8afab7782a30e53e88e4a2c27997379ee616b3412f857f04b87f7866f650" + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 424, + "digest": "sha256:01eddcaf4ac7c9a9b6414298813953e1ea8e5540dce7e0ded604530266b8dfd8", + "platform": { + "architecture": "", + "os": "" + } + } + ] +} \ No newline at end of file diff --git a/pkg/client/foo/oci-layout b/pkg/client/foo/oci-layout new file mode 100755 index 0000000000..224a869812 --- /dev/null +++ b/pkg/client/foo/oci-layout @@ -0,0 +1,3 @@ +{ + "imageLayoutVersion": "1.0.0" +} \ No newline at end of file diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index 73f401385d..c3aa189807 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -1,20 +1,27 @@ package client import ( - "context" - "fmt" + "github.com/buildpacks/imgutil" + "github.com/pkg/errors" ) // InspectManifest implements commands.PackClient. -func (c *Client) InspectManifest(ctx context.Context, name string) (err error) { - idx, err := c.indexFactory.FindIndex(name) +func (c *Client) InspectManifest(indexRepoName string) error { + var ( + index imgutil.ImageIndex + indexStr string + err error + ) + + index, err = c.indexFactory.FindIndex(indexRepoName) if err != nil { return err } - if mfest, err := idx.Inspect(); err == nil { - fmt.Println(mfest) + if indexStr, err = index.Inspect(); err != nil { + return errors.Wrapf(err, "'%s' printing the index", indexRepoName) } - return err + c.logger.Info(indexStr) + return nil } diff --git a/pkg/client/inspect_manifest_test.go b/pkg/client/inspect_manifest_test.go index 55a2417918..49eaf6f5e1 100644 --- a/pkg/client/inspect_manifest_test.go +++ b/pkg/client/inspect_manifest_test.go @@ -2,18 +2,14 @@ package client import ( "bytes" - "context" - "errors" - "os" "testing" "github.com/buildpacks/imgutil" - "github.com/buildpacks/imgutil/fakes" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/google/go-containerregistry/pkg/v1/random" "github.com/heroku/color" + "github.com/pkg/errors" "github.com/sclevine/spec" "github.com/sclevine/spec/report" @@ -37,61 +33,65 @@ func testInspectManifest(t *testing.T, when spec.G, it spec.S) { logger logging.Logger subject *Client err error - tmpDir string ) - when("#Add", func() { - it.Before(func() { - logger = logging.NewLogWithWriters(&stdout, &stderr, logging.WithVerbose()) - mockController = gomock.NewController(t) - mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + it.Before(func() { + logger = logging.NewLogWithWriters(&stdout, &stderr, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) - subject, err = NewClient( - WithLogger(logger), - WithIndexFactory(mockIndexFactory), - WithExperimental(true), - WithKeychain(authn.DefaultKeychain), - ) - h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) - h.AssertSameInstance(t, subject.logger, logger) - h.AssertNil(t, err) - }) - it.After(func() { - mockController.Finish() - h.AssertNil(t, os.RemoveAll(tmpDir)) - }) - it("should return an error when index not found", func() { - prepareFindIndexWithError(*mockIndexFactory) + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertSameInstance(t, subject.logger, logger) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + }) + + when("#InspectManifest", func() { + when("index doesn't exits", func() { + it.Before(func() { + mockIndexFactory. + EXPECT(). + FindIndex(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil, errors.New("index not found")) + }) - err := subject.InspectManifest( - context.TODO(), - "some/name", - ) - h.AssertEq(t, err.Error(), "index not found") + it("should return an error when index not found", func() { + err = subject.InspectManifest("some/name") + h.AssertEq(t, err.Error(), "index not found") + }) }) - it("should return formatted IndexManifest", func() { - prepareFindIndex(t, *mockIndexFactory) - err := subject.InspectManifest( - context.TODO(), - "some/name", - ) - h.AssertNil(t, err) - h.AssertEq(t, stderr.String(), "") + when("index exists", func() { + it.Before(func() { + setUpIndex(t, "some/name", *mockIndexFactory) + }) + + it("should return formatted IndexManifest", func() { + err = subject.InspectManifest("some/name") + h.AssertNil(t, err) + h.AssertEq(t, stderr.String(), "") + }) }) }) } -func prepareFindIndexWithError(mockIndexFactory testmocks.MockIndexFactory) { - mockIndexFactory. - EXPECT(). - FindIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(nil, errors.New("index not found")) -} +func setUpIndex(t *testing.T, indexRepoName string, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { + ridx, err := random.Index(1024, 1, 2) + h.AssertNil(t, err) -func prepareFindIndex(t *testing.T, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { - idx, err := fakes.NewIndex(types.OCIImageIndex, 1024, 1, 1, v1.Descriptor{}) + options := &imgutil.IndexOptions{ + BaseIndex: ridx, + } + idx, err := imgutil.NewCNBIndex(indexRepoName, *options) h.AssertNil(t, err) mockIndexFactory. diff --git a/pkg/client/pack_index/index.json b/pkg/client/pack_index/index.json new file mode 100644 index 0000000000..2fe3edd0a0 --- /dev/null +++ b/pkg/client/pack_index/index.json @@ -0,0 +1,25 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": [ + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 424, + "digest": "sha256:a191d255a868227d5ccea94a81f9220bdea92533c272ca8cb40d0c937f190885" + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 424, + "digest": "sha256:be5c9c9d9c68a5aa8d94ade9c2b83c98a5d001fab5c5c6ed88df68544c62de04" + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 424, + "digest": "sha256:3c5cff78831e9a3ff05a030eea7805eafb4462866c8651f65943afa2ee44f2c4", + "platform": { + "architecture": "", + "os": "" + } + } + ] +} \ No newline at end of file diff --git a/pkg/client/pack_index/oci-layout b/pkg/client/pack_index/oci-layout new file mode 100644 index 0000000000..224a869812 --- /dev/null +++ b/pkg/client/pack_index/oci-layout @@ -0,0 +1,3 @@ +{ + "imageLayoutVersion": "1.0.0" +} \ No newline at end of file diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index a6bcff8ab7..0867467805 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -1,45 +1,58 @@ package client import ( - "context" - "fmt" - - "github.com/buildpacks/imgutil" - "github.com/google/go-containerregistry/pkg/v1/types" + "github.com/pkg/errors" ) type PushManifestOptions struct { - Format string - Insecure, Purge bool + // Image index we want to update + IndexRepoName string + + // Index media-type + Format string + + // true if we want to publish to an insecure registry + Insecure bool + + // true if we want the index to be deleted from local storage after pushing it + Purge bool } // PushManifest implements commands.PackClient. -func (c *Client) PushManifest(ctx context.Context, index string, opts PushManifestOptions) (err error) { - idx, err := c.indexFactory.LoadIndex(index) +func (c *Client) PushManifest(opts PushManifestOptions) (err error) { + idx, err := c.indexFactory.LoadIndex(opts.IndexRepoName) if err != nil { return } - if err = idx.Push(parseFalgsForImgUtil(opts)...); err != nil { - return err + // TODO pass through the options + if err = idx.Push(); err != nil { + return errors.Wrapf(err, "pushing index '%s'", opts.IndexRepoName) } if !opts.Purge { - fmt.Printf("successfully pushed index: '%s'\n", index) + c.logger.Infof("successfully pushed index: '%s'\n", opts.IndexRepoName) return nil } - return idx.Delete() + return idx.DeleteDir() } -func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions []imgutil.IndexPushOption) { - idxOptions = append(idxOptions, imgutil.WithInsecure(opts.Insecure)) +/* +func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions []imgutil.IndexOption) { + idxOptions = append(idxOptions, imgutil.WithInsecure()) + + if opts.Purge { + idxOptions = append(idxOptions, imgutil.WithPurge(true)) + } + switch opts.Format { case "oci": - return append(idxOptions, imgutil.WithFormat(types.OCIImageIndex)) + return append(idxOptions, imgutil.WithMediaType(types.OCIImageIndex)) case "v2s2": - return append(idxOptions, imgutil.WithFormat(types.DockerManifestList)) + return append(idxOptions, imgutil.WithMediaType(types.DockerManifestList)) default: return idxOptions } } +*/ diff --git a/pkg/client/push_manifest_test.go b/pkg/client/push_manifest_test.go index 09e5414daf..f0fbe6721a 100644 --- a/pkg/client/push_manifest_test.go +++ b/pkg/client/push_manifest_test.go @@ -2,18 +2,17 @@ package client import ( "bytes" - "context" "os" "testing" + "github.com/buildpacks/imgutil" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" "github.com/heroku/color" + "github.com/pkg/errors" "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/imgutil" - "github.com/buildpacks/pack/pkg/logging" "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" @@ -35,36 +34,52 @@ func testPushManifest(t *testing.T, when spec.G, it spec.S) { err error tmpDir string ) - when("#Push", func() { - it.Before(func() { - logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) - mockController = gomock.NewController(t) - mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + it.Before(func() { + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) - subject, err = NewClient( - WithLogger(logger), - WithIndexFactory(mockIndexFactory), - WithExperimental(true), - WithKeychain(authn.DefaultKeychain), - ) - h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) - h.AssertNil(t, err) - }) - it.After(func() { - mockController.Finish() - h.AssertNil(t, os.RemoveAll(tmpDir)) - }) - it("should not have local image index", func() { - prepareLoadIndexWithError(*mockIndexFactory) + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) - err := subject.PushManifest(context.TODO(), "some-index", PushManifestOptions{}) - h.AssertEq(t, err.Error(), imgutil.ErrNoImageOrIndexFoundWithGivenDigest("").Error()) + when("#PushManifest", func() { + when("index exists locally", func() { + var index *testPushIndex + + it.Before(func() { + index = prepareLoadIndexWithMockPush(t, "some-index", *mockIndexFactory) + }) + it("should push index to registry", func() { + err = subject.PushManifest(PushManifestOptions{ + IndexRepoName: "some-index", + }) + h.AssertNil(t, err) + h.AssertTrue(t, index.PushCalled) + }) }) - it("should push index to registry", func() { - prepareLoadIndex(t, *mockIndexFactory) - err := subject.PushManifest(context.TODO(), "some-index", PushManifestOptions{}) - h.AssertNil(t, err) + when("index doesn't exist locally", func() { + it.Before(func() { + prepareLoadIndexWithError(*mockIndexFactory) + }) + + it("should not have local image index", func() { + err = subject.PushManifest(PushManifestOptions{ + IndexRepoName: "some-index", + }) + h.AssertNotNil(t, err) + }) }) }) } @@ -73,5 +88,29 @@ func prepareLoadIndexWithError(mockIndexFactory testmocks.MockIndexFactory) { mockIndexFactory. EXPECT(). LoadIndex(gomock.Any(), gomock.Any()). - Return(nil, imgutil.ErrNoImageOrIndexFoundWithGivenDigest("")) + Return(nil, errors.New("ErrNoImageOrIndexFoundWithGivenDigest")) +} + +func prepareLoadIndexWithMockPush(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) *testPushIndex { + cnbIdx := randomCNBIndex(t, repoName) + idx := &testPushIndex{ + CNBIndex: *cnbIdx, + } + mockIndexFactory. + EXPECT(). + LoadIndex(gomock.Eq(repoName), gomock.Any()). + Return(idx, nil). + AnyTimes() + + return idx +} + +type testPushIndex struct { + imgutil.CNBIndex + PushCalled bool +} + +func (i *testPushIndex) Push(_ ...imgutil.IndexOption) error { + i.PushCalled = true + return nil } diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index 5df0de8908..665b73ee5e 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -13,7 +13,7 @@ func (c *Client) DeleteManifest(ctx context.Context, names []string) (errs []err errs = append(errs, err) } - if err := imgIndex.Delete(); err != nil { + if err := imgIndex.DeleteDir(); err != nil { errs = append(errs, err) } } diff --git a/pkg/client/remove_manifest_test.go b/pkg/client/remove_manifest_test.go index b919032fef..443b66e948 100644 --- a/pkg/client/remove_manifest_test.go +++ b/pkg/client/remove_manifest_test.go @@ -54,13 +54,13 @@ func testDeleteManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, os.RemoveAll(tmpDir)) }) it("should delete local index", func() { - prepareLoadIndex(t, *mockIndexFactory) + prepareLoadIndex(t, "pack/index", *mockIndexFactory) errs := subject.DeleteManifest(context.TODO(), []string{"some-index"}) h.AssertEq(t, len(errs), 0) }) it("should return an error when index is already deleted", func() { - prepareLoadIndex(t, *mockIndexFactory) + prepareLoadIndex(t, "pack/index", *mockIndexFactory) errs := subject.DeleteManifest(context.TODO(), []string{"some-index"}) h.AssertEq(t, len(errs), 0) diff --git a/pkg/client/rm_manifest.go b/pkg/client/rm_manifest.go index 9aa95e4e5c..7f8563c85f 100644 --- a/pkg/client/rm_manifest.go +++ b/pkg/client/rm_manifest.go @@ -15,15 +15,16 @@ func (c *Client) RemoveManifest(ctx context.Context, name string, images []strin } for _, image := range images { - ref, err := gccrName.ParseReference(image, gccrName.WeakValidation, gccrName.Insecure) + ref, err := gccrName.NewDigest(image, gccrName.WeakValidation, gccrName.Insecure) if err != nil { errs = append(errs, fmt.Errorf(`invalid instance "%s": %v`, image, err)) } - if err = imgIndex.Remove(ref); err != nil { + + if err = imgIndex.RemoveManifest(ref); err != nil { errs = append(errs, err) } - if err = imgIndex.Save(); err != nil { + if err = imgIndex.SaveDir(); err != nil { errs = append(errs, err) } } diff --git a/pkg/client/rm_manifest_test.go b/pkg/client/rm_manifest_test.go index 9c092daf94..97812abb21 100644 --- a/pkg/client/rm_manifest_test.go +++ b/pkg/client/rm_manifest_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + "github.com/buildpacks/imgutil" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" @@ -13,8 +14,6 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/imgutil/fakes" - "github.com/buildpacks/pack/pkg/logging" "github.com/buildpacks/pack/pkg/testmocks" h "github.com/buildpacks/pack/testhelpers" @@ -57,8 +56,8 @@ func testRemoveManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, os.RemoveAll(tmpDir)) }) it("should remove local index", func() { - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) + idx := prepareLoadIndex(t, "pack/index", *mockIndexFactory) + imgIdx, ok := idx.(*imgutil.CNBIndex) h.AssertEq(t, ok, true) mfest, err := imgIdx.IndexManifest() @@ -71,8 +70,8 @@ func testRemoveManifest(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, len(errs), 0) }) it("should remove image", func() { - idx := prepareLoadIndex(t, *mockIndexFactory) - imgIdx, ok := idx.(*fakes.Index) + idx := prepareLoadIndex(t, "pack/index", *mockIndexFactory) + imgIdx, ok := idx.(*imgutil.CNBIndex) h.AssertEq(t, ok, true) mfest, err := imgIdx.IndexManifest() diff --git a/pkg/client/some_repo/index.json b/pkg/client/some_repo/index.json new file mode 100755 index 0000000000..b5b910e354 --- /dev/null +++ b/pkg/client/some_repo/index.json @@ -0,0 +1,11 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.index.v1+json", + "manifests": null +} { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 424, + "digest": "sha256:ff42da2f8eaa027b1c7469b452e220b686e9839288f48b0dffe702dfc72c3dab" + } + ] +} \ No newline at end of file diff --git a/pkg/client/some_repo/oci-layout b/pkg/client/some_repo/oci-layout new file mode 100755 index 0000000000..224a869812 --- /dev/null +++ b/pkg/client/some_repo/oci-layout @@ -0,0 +1,3 @@ +{ + "imageLayoutVersion": "1.0.0" +} \ No newline at end of file diff --git a/pkg/index/index_factory.go b/pkg/index/index_factory.go new file mode 100644 index 0000000000..aaa5d70e1e --- /dev/null +++ b/pkg/index/index_factory.go @@ -0,0 +1,84 @@ +package index + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/layout" + "github.com/buildpacks/imgutil/remote" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/pkg/errors" +) + +type IndexFactory struct { + keychain authn.Keychain + path string +} + +func NewIndexFactory(keychain authn.Keychain, path string) *IndexFactory { + return &IndexFactory{ + keychain: keychain, + path: path, + } +} + +func (f *IndexFactory) Exists(repoName string) bool { + return layoutImageExists(f.localPath(repoName)) +} + +func (f *IndexFactory) LoadIndex(repoName string, opts ...imgutil.IndexOption) (index imgutil.ImageIndex, err error) { + if !f.Exists(repoName) { + return nil, errors.New(fmt.Sprintf("Image: '%s' not found", repoName)) + } + opts = appendOption(opts, imgutil.FromBaseIndex(f.localPath(repoName))) + return layout.NewIndex(repoName, appendDefaultOptions(opts, f.keychain, f.path)...) +} + +func (f *IndexFactory) FetchIndex(name string, opts ...imgutil.IndexOption) (idx imgutil.ImageIndex, err error) { + return remote.NewIndex(name, appendDefaultOptions(opts, f.keychain, f.path)...) +} + +func (f *IndexFactory) FindIndex(repoName string, opts ...imgutil.IndexOption) (idx imgutil.ImageIndex, err error) { + if f.Exists(repoName) { + return f.LoadIndex(repoName, opts...) + } + return f.FetchIndex(repoName, opts...) +} + +func (f *IndexFactory) CreateIndex(repoName string, opts ...imgutil.IndexOption) (idx imgutil.ImageIndex, err error) { + return layout.NewIndex(repoName, appendDefaultOptions(opts, f.keychain, f.path)...) +} + +func (f *IndexFactory) localPath(repoName string) string { + return filepath.Join(f.path, imgutil.MakeFileSafeName(repoName)) +} + +func layoutImageExists(path string) bool { + if !pathExists(path) { + return false + } + index := filepath.Join(path, "index.json") + if _, err := os.Stat(index); os.IsNotExist(err) { + return false + } + return true +} + +func pathExists(path string) bool { + if path != "" { + if _, err := os.Stat(path); !os.IsNotExist(err) { + return true + } + } + return false +} + +func appendOption(ops []imgutil.IndexOption, op imgutil.IndexOption) []imgutil.IndexOption { + return append(ops, op) +} + +func appendDefaultOptions(ops []imgutil.IndexOption, keychain authn.Keychain, path string) []imgutil.IndexOption { + return append(ops, imgutil.WithKeychain(keychain), imgutil.WithXDGRuntimePath(path)) +} diff --git a/pkg/testmocks/mock_access_checker.go b/pkg/testmocks/mock_access_checker.go new file mode 100644 index 0000000000..558b85a580 --- /dev/null +++ b/pkg/testmocks/mock_access_checker.go @@ -0,0 +1,48 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/buildpacks/pack/pkg/client (interfaces: AccessChecker) + +// Package testmocks is a generated GoMock package. +package testmocks + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockAccessChecker is a mock of AccessChecker interface. +type MockAccessChecker struct { + ctrl *gomock.Controller + recorder *MockAccessCheckerMockRecorder +} + +// MockAccessCheckerMockRecorder is the mock recorder for MockAccessChecker. +type MockAccessCheckerMockRecorder struct { + mock *MockAccessChecker +} + +// NewMockAccessChecker creates a new mock instance. +func NewMockAccessChecker(ctrl *gomock.Controller) *MockAccessChecker { + mock := &MockAccessChecker{ctrl: ctrl} + mock.recorder = &MockAccessCheckerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccessChecker) EXPECT() *MockAccessCheckerMockRecorder { + return m.recorder +} + +// Check mocks base method. +func (m *MockAccessChecker) Check(arg0 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Check", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Check indicates an expected call of Check. +func (mr *MockAccessCheckerMockRecorder) Check(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Check", reflect.TypeOf((*MockAccessChecker)(nil).Check), arg0) +} diff --git a/pkg/testmocks/mock_docker_client.go b/pkg/testmocks/mock_docker_client.go index c094670cd5..7613e48cff 100644 --- a/pkg/testmocks/mock_docker_client.go +++ b/pkg/testmocks/mock_docker_client.go @@ -767,7 +767,7 @@ func (mr *MockCommonAPIClientMockRecorder) ImageBuild(arg0, arg1, arg2 interface } // ImageCreate mocks base method. -func (m *MockCommonAPIClient) ImageCreate(arg0 context.Context, arg1 string, arg2 types.ImageCreateOptions) (io.ReadCloser, error) { +func (m *MockCommonAPIClient) ImageCreate(arg0 context.Context, arg1 string, arg2 image.CreateOptions) (io.ReadCloser, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ImageCreate", arg0, arg1, arg2) ret0, _ := ret[0].(io.ReadCloser) @@ -797,7 +797,7 @@ func (mr *MockCommonAPIClientMockRecorder) ImageHistory(arg0, arg1 interface{}) } // ImageImport mocks base method. -func (m *MockCommonAPIClient) ImageImport(arg0 context.Context, arg1 types.ImageImportSource, arg2 string, arg3 types.ImageImportOptions) (io.ReadCloser, error) { +func (m *MockCommonAPIClient) ImageImport(arg0 context.Context, arg1 types.ImageImportSource, arg2 string, arg3 image.ImportOptions) (io.ReadCloser, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ImageImport", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(io.ReadCloser) @@ -828,7 +828,7 @@ func (mr *MockCommonAPIClientMockRecorder) ImageInspectWithRaw(arg0, arg1 interf } // ImageList mocks base method. -func (m *MockCommonAPIClient) ImageList(arg0 context.Context, arg1 types.ImageListOptions) ([]image.Summary, error) { +func (m *MockCommonAPIClient) ImageList(arg0 context.Context, arg1 image.ListOptions) ([]image.Summary, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ImageList", arg0, arg1) ret0, _ := ret[0].([]image.Summary) @@ -858,7 +858,7 @@ func (mr *MockCommonAPIClientMockRecorder) ImageLoad(arg0, arg1, arg2 interface{ } // ImagePull mocks base method. -func (m *MockCommonAPIClient) ImagePull(arg0 context.Context, arg1 string, arg2 types.ImagePullOptions) (io.ReadCloser, error) { +func (m *MockCommonAPIClient) ImagePull(arg0 context.Context, arg1 string, arg2 image.PullOptions) (io.ReadCloser, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ImagePull", arg0, arg1, arg2) ret0, _ := ret[0].(io.ReadCloser) @@ -873,7 +873,7 @@ func (mr *MockCommonAPIClientMockRecorder) ImagePull(arg0, arg1, arg2 interface{ } // ImagePush mocks base method. -func (m *MockCommonAPIClient) ImagePush(arg0 context.Context, arg1 string, arg2 types.ImagePushOptions) (io.ReadCloser, error) { +func (m *MockCommonAPIClient) ImagePush(arg0 context.Context, arg1 string, arg2 image.PushOptions) (io.ReadCloser, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ImagePush", arg0, arg1, arg2) ret0, _ := ret[0].(io.ReadCloser) @@ -888,7 +888,7 @@ func (mr *MockCommonAPIClientMockRecorder) ImagePush(arg0, arg1, arg2 interface{ } // ImageRemove mocks base method. -func (m *MockCommonAPIClient) ImageRemove(arg0 context.Context, arg1 string, arg2 types.ImageRemoveOptions) ([]image.DeleteResponse, error) { +func (m *MockCommonAPIClient) ImageRemove(arg0 context.Context, arg1 string, arg2 image.RemoveOptions) ([]image.DeleteResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ImageRemove", arg0, arg1, arg2) ret0, _ := ret[0].([]image.DeleteResponse) diff --git a/pkg/testmocks/mock_index_factory.go b/pkg/testmocks/mock_index_factory.go index dc38d11f6a..8607c12df7 100644 --- a/pkg/testmocks/mock_index_factory.go +++ b/pkg/testmocks/mock_index_factory.go @@ -8,7 +8,6 @@ import ( reflect "reflect" imgutil "github.com/buildpacks/imgutil" - index "github.com/buildpacks/imgutil/index" gomock "github.com/golang/mock/gomock" ) @@ -36,7 +35,7 @@ func (m *MockIndexFactory) EXPECT() *MockIndexFactoryMockRecorder { } // CreateIndex mocks base method. -func (m *MockIndexFactory) CreateIndex(arg0 string, arg1 ...index.Option) (imgutil.ImageIndex, error) { +func (m *MockIndexFactory) CreateIndex(arg0 string, arg1 ...imgutil.IndexOption) (imgutil.ImageIndex, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { @@ -55,8 +54,22 @@ func (mr *MockIndexFactoryMockRecorder) CreateIndex(arg0 interface{}, arg1 ...in return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateIndex", reflect.TypeOf((*MockIndexFactory)(nil).CreateIndex), varargs...) } +// Exists mocks base method. +func (m *MockIndexFactory) Exists(arg0 string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Exists", arg0) + ret0, _ := ret[0].(bool) + return ret0 +} + +// Exists indicates an expected call of Exists. +func (mr *MockIndexFactoryMockRecorder) Exists(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exists", reflect.TypeOf((*MockIndexFactory)(nil).Exists), arg0) +} + // FetchIndex mocks base method. -func (m *MockIndexFactory) FetchIndex(arg0 string, arg1 ...index.Option) (imgutil.ImageIndex, error) { +func (m *MockIndexFactory) FetchIndex(arg0 string, arg1 ...imgutil.IndexOption) (imgutil.ImageIndex, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { @@ -76,7 +89,7 @@ func (mr *MockIndexFactoryMockRecorder) FetchIndex(arg0 interface{}, arg1 ...int } // FindIndex mocks base method. -func (m *MockIndexFactory) FindIndex(arg0 string, arg1 ...index.Option) (imgutil.ImageIndex, error) { +func (m *MockIndexFactory) FindIndex(arg0 string, arg1 ...imgutil.IndexOption) (imgutil.ImageIndex, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { @@ -96,7 +109,7 @@ func (mr *MockIndexFactoryMockRecorder) FindIndex(arg0 interface{}, arg1 ...inte } // LoadIndex mocks base method. -func (m *MockIndexFactory) LoadIndex(arg0 string, arg1 ...index.Option) (imgutil.ImageIndex, error) { +func (m *MockIndexFactory) LoadIndex(arg0 string, arg1 ...imgutil.IndexOption) (imgutil.ImageIndex, error) { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { From 33396dac7f696d6da3875de8c2e17a0cd8983ce2 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Fri, 26 Apr 2024 14:38:08 -0500 Subject: [PATCH 57/79] WIP - running go mod tidy Signed-off-by: Juan Bustamante --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index ff348acdc9..62de1c9d94 100644 --- a/go.sum +++ b/go.sum @@ -222,8 +222,8 @@ github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLa github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0= github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/husni-faiz/imgutil v0.0.0-20240424232532-cbecaa88347b h1:qNx9ZGQ3N6pascREpfIDxkV95IOY2nCVmFOIYKmAV5I= -github.com/husni-faiz/imgutil v0.0.0-20240424232532-cbecaa88347b/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk= +github.com/husni-faiz/imgutil v0.0.0-20240426182921-f79d933eb4e1 h1:B+i0nziQ6k42hkAr0bgua/zdWqm4R6dZuw3V2Wvrex4= +github.com/husni-faiz/imgutil v0.0.0-20240426182921-f79d933eb4e1/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= From 71dfb600d900a532deceff38f8000e5bc3a04b62 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Fri, 26 Apr 2024 15:16:58 -0500 Subject: [PATCH 58/79] WIP - fixing lint errors Signed-off-by: Juan Bustamante --- internal/commands/manifest_add.go | 1 - internal/commands/manifest_annotate.go | 1 - internal/commands/manifest_inspect.go | 2 +- pkg/client/add_manifest_test.go | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 500e3202d4..9343f80dcf 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -9,7 +9,6 @@ import ( // ManifestAdd modifies a manifest list (Image index) and add a new image to the list of manifests. func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { - cmd := &cobra.Command{ Use: "add [OPTIONS] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 1923479397..7ea8407fda 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -25,7 +25,6 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { cnbs/sample-package:hello-universe --arch amd64`, Long: `manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { - return pack.AnnotateManifest(cmd.Context(), client.ManifestAnnotateOptions{ IndexRepoName: args[0], RepoName: args[1], diff --git a/internal/commands/manifest_inspect.go b/internal/commands/manifest_inspect.go index 49727ddebe..3779a0af80 100644 --- a/internal/commands/manifest_inspect.go +++ b/internal/commands/manifest_inspect.go @@ -19,7 +19,7 @@ func ManifestInspect(logger logging.Logger, pack PackClient) *cobra.Command { The inspect command will help users to view how their local manifest list looks like`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { if args[0] == "" { - errors.New("'' is required") + return errors.New("'' is required") } return pack.InspectManifest(args[0]) }), diff --git a/pkg/client/add_manifest_test.go b/pkg/client/add_manifest_test.go index 48f5abdd7b..b40e5f6ca2 100644 --- a/pkg/client/add_manifest_test.go +++ b/pkg/client/add_manifest_test.go @@ -66,7 +66,6 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { // Create a remote image to be fetched when adding to the image index fakeImage := setUpRemoteImageForIndex(t, nil) fakeImageFetcher.RemoteImages["index.docker.io/pack/image:latest"] = fakeImage - }) it.After(func() { mockController.Finish() From 7c1fbc376b7678effd6b1cba04a8e61863758df8 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Fri, 26 Apr 2024 15:26:08 -0500 Subject: [PATCH 59/79] WIP - fixing TestNewManifestCommand after removing 'exists' Signed-off-by: Juan Bustamante --- internal/commands/manifest_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/manifest_test.go b/internal/commands/manifest_test.go index 067271ef5d..983598e9f2 100644 --- a/internal/commands/manifest_test.go +++ b/internal/commands/manifest_test.go @@ -48,7 +48,7 @@ func testNewManifestCommand(t *testing.T, when spec.G, it spec.S) { output := outBuf.String() h.AssertContains(t, output, "Interact with image index") h.AssertContains(t, output, "Usage:") - for _, command := range []string{"create", "add", "annotate", "inspect", "exists", "remove", "rm"} { + for _, command := range []string{"create", "add", "annotate", "inspect", "remove", "rm"} { h.AssertContains(t, output, command) } }) From 29776ac054025dbacc1d33861409fde80bd6a839 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Fri, 26 Apr 2024 16:18:09 -0500 Subject: [PATCH 60/79] WIP - fixing unit tests Signed-off-by: Juan Bustamante --- pkg/client/add_manifest_test.go | 27 ++++++++--- pkg/client/foo/index.json | 25 ---------- pkg/client/foo/oci-layout | 3 -- pkg/client/pack_index/index.json | 25 ---------- pkg/client/pack_index/oci-layout | 3 -- pkg/client/push_manifest_test.go | 19 ++------ pkg/client/remove_manifest.go | 5 +- pkg/client/remove_manifest_test.go | 68 +++++++++++++++----------- pkg/client/rm_manifest_test.go | 77 ++++++++++++++---------------- pkg/client/some_repo/index.json | 11 ----- pkg/client/some_repo/oci-layout | 3 -- 11 files changed, 104 insertions(+), 162 deletions(-) delete mode 100755 pkg/client/foo/index.json delete mode 100755 pkg/client/foo/oci-layout delete mode 100644 pkg/client/pack_index/index.json delete mode 100644 pkg/client/pack_index/oci-layout delete mode 100755 pkg/client/some_repo/index.json delete mode 100755 pkg/client/some_repo/oci-layout diff --git a/pkg/client/add_manifest_test.go b/pkg/client/add_manifest_test.go index b40e5f6ca2..8dce955eb5 100644 --- a/pkg/client/add_manifest_test.go +++ b/pkg/client/add_manifest_test.go @@ -176,6 +176,9 @@ func randomCNBIndex(t *testing.T, repoName string) *imgutil.CNBIndex { h.AssertNil(t, err) options := &imgutil.IndexOptions{ BaseIndex: ridx, + LayoutIndexOptions: imgutil.LayoutIndexOptions{ + XdgPath: os.Getenv("XDG_RUNTIME_DIR"), + }, } idx, err := imgutil.NewCNBIndex(repoName, *options) h.AssertNil(t, err) @@ -195,9 +198,9 @@ func prepareLoadIndex(t *testing.T, repoName string, mockIndexFactory testmocks. func prepareLoadIndexWithErrorOnSave(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { cnbIdx := randomCNBIndex(t, repoName) - idx := &testIndex{ + idx := &mockImageIndex{ CNBIndex: *cnbIdx, - errorOnSave: true, + ErrorOnSave: true, } mockIndexFactory. EXPECT(). @@ -217,14 +220,26 @@ func (t *testImage) UnderlyingImage() v1.Image { return t.underlyingImage } -type testIndex struct { +type mockImageIndex struct { imgutil.CNBIndex - errorOnSave bool + ErrorOnSave bool + PushCalled bool + DeleteDirCalled bool } -func (i *testIndex) SaveDir() error { - if i.errorOnSave { +func (i *mockImageIndex) SaveDir() error { + if i.ErrorOnSave { return errors.New("something failed writing the index on disk") } return i.CNBIndex.SaveDir() } + +func (i *mockImageIndex) Push(_ ...imgutil.IndexOption) error { + i.PushCalled = true + return nil +} + +func (i *mockImageIndex) DeleteDir() error { + i.DeleteDirCalled = true + return nil +} diff --git a/pkg/client/foo/index.json b/pkg/client/foo/index.json deleted file mode 100755 index 901ca69362..0000000000 --- a/pkg/client/foo/index.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.index.v1+json", - "manifests": [ - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 424, - "digest": "sha256:f61aed5d0a49f08c7da6df2fb8b16a821ea4dc2567e8979411dc3374e1f3498d" - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 424, - "digest": "sha256:94eb8afab7782a30e53e88e4a2c27997379ee616b3412f857f04b87f7866f650" - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 424, - "digest": "sha256:01eddcaf4ac7c9a9b6414298813953e1ea8e5540dce7e0ded604530266b8dfd8", - "platform": { - "architecture": "", - "os": "" - } - } - ] -} \ No newline at end of file diff --git a/pkg/client/foo/oci-layout b/pkg/client/foo/oci-layout deleted file mode 100755 index 224a869812..0000000000 --- a/pkg/client/foo/oci-layout +++ /dev/null @@ -1,3 +0,0 @@ -{ - "imageLayoutVersion": "1.0.0" -} \ No newline at end of file diff --git a/pkg/client/pack_index/index.json b/pkg/client/pack_index/index.json deleted file mode 100644 index 2fe3edd0a0..0000000000 --- a/pkg/client/pack_index/index.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.index.v1+json", - "manifests": [ - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 424, - "digest": "sha256:a191d255a868227d5ccea94a81f9220bdea92533c272ca8cb40d0c937f190885" - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 424, - "digest": "sha256:be5c9c9d9c68a5aa8d94ade9c2b83c98a5d001fab5c5c6ed88df68544c62de04" - }, - { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 424, - "digest": "sha256:3c5cff78831e9a3ff05a030eea7805eafb4462866c8651f65943afa2ee44f2c4", - "platform": { - "architecture": "", - "os": "" - } - } - ] -} \ No newline at end of file diff --git a/pkg/client/pack_index/oci-layout b/pkg/client/pack_index/oci-layout deleted file mode 100644 index 224a869812..0000000000 --- a/pkg/client/pack_index/oci-layout +++ /dev/null @@ -1,3 +0,0 @@ -{ - "imageLayoutVersion": "1.0.0" -} \ No newline at end of file diff --git a/pkg/client/push_manifest_test.go b/pkg/client/push_manifest_test.go index f0fbe6721a..88090f324c 100644 --- a/pkg/client/push_manifest_test.go +++ b/pkg/client/push_manifest_test.go @@ -5,7 +5,6 @@ import ( "os" "testing" - "github.com/buildpacks/imgutil" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" "github.com/heroku/color" @@ -55,10 +54,10 @@ func testPushManifest(t *testing.T, when spec.G, it spec.S) { when("#PushManifest", func() { when("index exists locally", func() { - var index *testPushIndex + var index *mockImageIndex it.Before(func() { - index = prepareLoadIndexWithMockPush(t, "some-index", *mockIndexFactory) + index = newMockImageIndex(t, "some-index", *mockIndexFactory) }) it("should push index to registry", func() { err = subject.PushManifest(PushManifestOptions{ @@ -91,9 +90,9 @@ func prepareLoadIndexWithError(mockIndexFactory testmocks.MockIndexFactory) { Return(nil, errors.New("ErrNoImageOrIndexFoundWithGivenDigest")) } -func prepareLoadIndexWithMockPush(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) *testPushIndex { +func newMockImageIndex(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) *mockImageIndex { cnbIdx := randomCNBIndex(t, repoName) - idx := &testPushIndex{ + idx := &mockImageIndex{ CNBIndex: *cnbIdx, } mockIndexFactory. @@ -104,13 +103,3 @@ func prepareLoadIndexWithMockPush(t *testing.T, repoName string, mockIndexFactor return idx } - -type testPushIndex struct { - imgutil.CNBIndex - PushCalled bool -} - -func (i *testPushIndex) Push(_ ...imgutil.IndexOption) error { - i.PushCalled = true - return nil -} diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index 665b73ee5e..a363fbced4 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -2,7 +2,6 @@ package client import ( "context" - "fmt" ) // DeleteManifest implements commands.PackClient. @@ -11,6 +10,7 @@ func (c *Client) DeleteManifest(ctx context.Context, names []string) (errs []err imgIndex, err := c.indexFactory.LoadIndex(name) if err != nil { errs = append(errs, err) + continue } if err := imgIndex.DeleteDir(); err != nil { @@ -19,8 +19,7 @@ func (c *Client) DeleteManifest(ctx context.Context, names []string) (errs []err } if len(errs) == 0 { - fmt.Printf("successfully deleted indexes \n") + c.logger.Info("successfully deleted indexes \n") } - return errs } diff --git a/pkg/client/remove_manifest_test.go b/pkg/client/remove_manifest_test.go index 443b66e948..812d8bbac5 100644 --- a/pkg/client/remove_manifest_test.go +++ b/pkg/client/remove_manifest_test.go @@ -34,39 +34,51 @@ func testDeleteManifest(t *testing.T, when spec.G, it spec.S) { tmpDir string ) - when("#Add", func() { - it.Before(func() { - logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) - mockController = gomock.NewController(t) - mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + it.Before(func() { + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) - subject, err = NewClient( - WithLogger(logger), - WithIndexFactory(mockIndexFactory), - WithExperimental(true), - WithKeychain(authn.DefaultKeychain), - ) - h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) - h.AssertNil(t, err) - }) - it.After(func() { - mockController.Finish() - h.AssertNil(t, os.RemoveAll(tmpDir)) - }) - it("should delete local index", func() { - prepareLoadIndex(t, "pack/index", *mockIndexFactory) + tmpDir, err = os.MkdirTemp("", "remove-manifest-test") + h.AssertNil(t, err) + os.Setenv("XDG_RUNTIME_DIR", tmpDir) + + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) - errs := subject.DeleteManifest(context.TODO(), []string{"some-index"}) - h.AssertEq(t, len(errs), 0) + when("#DeleteManifest", func() { + when("index doesn't exists", func() { + it.Before(func() { + prepareIndexWithoutLocallyExists(*mockIndexFactory) + }) + it("should return an error when index is already deleted", func() { + errs := subject.DeleteManifest(context.TODO(), []string{"pack/none-existent-index"}) + h.AssertNotEq(t, len(errs), 0) + }) }) - it("should return an error when index is already deleted", func() { - prepareLoadIndex(t, "pack/index", *mockIndexFactory) - errs := subject.DeleteManifest(context.TODO(), []string{"some-index"}) - h.AssertEq(t, len(errs), 0) + when("index exists", func() { + var index *mockImageIndex + it.Before(func() { + index = newMockImageIndex(t, "some-index", *mockIndexFactory) + }) - errs = subject.DeleteManifest(context.TODO(), []string{"some-index"}) - h.AssertNotEq(t, len(errs), 0) + it("should delete local index", func() { + errs := subject.DeleteManifest(context.TODO(), []string{"some-index"}) + h.AssertEq(t, len(errs), 0) + h.AssertTrue(t, index.DeleteDirCalled) + }) }) }) } diff --git a/pkg/client/rm_manifest_test.go b/pkg/client/rm_manifest_test.go index 97812abb21..eed97c2479 100644 --- a/pkg/client/rm_manifest_test.go +++ b/pkg/client/rm_manifest_test.go @@ -36,52 +36,49 @@ func testRemoveManifest(t *testing.T, when spec.G, it spec.S) { tmpDir string ) - when("#Add", func() { - it.Before(func() { - logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) - mockController = gomock.NewController(t) - mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + it.Before(func() { + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) - subject, err = NewClient( - WithLogger(logger), - WithIndexFactory(mockIndexFactory), - WithExperimental(true), - WithKeychain(authn.DefaultKeychain), - ) - h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) - h.AssertNil(t, err) - }) - it.After(func() { - mockController.Finish() - h.AssertNil(t, os.RemoveAll(tmpDir)) - }) - it("should remove local index", func() { - idx := prepareLoadIndex(t, "pack/index", *mockIndexFactory) - imgIdx, ok := idx.(*imgutil.CNBIndex) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) + tmpDir, err = os.MkdirTemp("", "rm-manifest-test") + h.AssertNil(t, err) + os.Setenv("XDG_RUNTIME_DIR", tmpDir) - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) + subject, err = NewClient( + WithLogger(logger), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + h.AssertNil(t, os.RemoveAll(tmpDir)) + }) - errs := subject.RemoveManifest(context.TODO(), "some-index", []string{digest.Name()}) - h.AssertEq(t, len(errs), 0) - }) - it("should remove image", func() { - idx := prepareLoadIndex(t, "pack/index", *mockIndexFactory) - imgIdx, ok := idx.(*imgutil.CNBIndex) - h.AssertEq(t, ok, true) + when("#RemoveManifest", func() { + when("index exists", func() { + var digest name.Digest + var idx imgutil.ImageIndex - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) + it.Before(func() { + idx = prepareLoadIndex(t, "some/repo", *mockIndexFactory) + imgIdx, ok := idx.(*imgutil.CNBIndex) + h.AssertEq(t, ok, true) - digest, err := name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) + mfest, err := imgIdx.IndexManifest() + h.AssertNil(t, err) - errs := subject.RemoveManifest(context.TODO(), "some-index", []string{digest.Name()}) - h.AssertEq(t, len(errs), 0) + digest, err = name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) + h.AssertNil(t, err) + }) + it("should remove local index", func() { + errs := subject.RemoveManifest(context.TODO(), "some/repo", []string{digest.Name()}) + h.AssertEq(t, len(errs), 0) + }) }) }) } diff --git a/pkg/client/some_repo/index.json b/pkg/client/some_repo/index.json deleted file mode 100755 index b5b910e354..0000000000 --- a/pkg/client/some_repo/index.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "schemaVersion": 2, - "mediaType": "application/vnd.oci.image.index.v1+json", - "manifests": null -} { - "mediaType": "application/vnd.docker.distribution.manifest.v2+json", - "size": 424, - "digest": "sha256:ff42da2f8eaa027b1c7469b452e220b686e9839288f48b0dffe702dfc72c3dab" - } - ] -} \ No newline at end of file diff --git a/pkg/client/some_repo/oci-layout b/pkg/client/some_repo/oci-layout deleted file mode 100755 index 224a869812..0000000000 --- a/pkg/client/some_repo/oci-layout +++ /dev/null @@ -1,3 +0,0 @@ -{ - "imageLayoutVersion": "1.0.0" -} \ No newline at end of file From 5f84b74f4d9da48010b20a9c9d5f5ba1748891aa Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Fri, 26 Apr 2024 16:26:17 -0500 Subject: [PATCH 61/79] WIP - restoring .gitpod.yml file and fixing error with client initialization Signed-off-by: Juan Bustamante --- .gitpod.yml | 18 ++++++++++++++++++ pkg/client/client.go | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 .gitpod.yml diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000000..b716e56cd5 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,18 @@ + +tasks: + - name: Setup + before: chmod ugo+w /var/run/docker.sock + init: make build + command: chmod ugo+w /var/run/docker.sock + +github: + prebuilds: + master: true + branches: true + pullRequests: true + pullRequestsFromForks: true + addCheck: true + +vscode: + extensions: + - golang.go diff --git a/pkg/client/client.go b/pkg/client/client.go index 4555589f2b..89f2b9a5de 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -265,7 +265,8 @@ func NewClient(opts ...Option) (*Client, error) { if client.imageFactory == nil { client.imageFactory = &imageFactory{ - keychain: client.keychain, + dockerClient: client.docker, + keychain: client.keychain, } } From e6fb2b740ad619c58b6c4a513ca418597866dcfc Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Fri, 26 Apr 2024 16:57:14 -0500 Subject: [PATCH 62/79] WIP - removing some unused files Signed-off-by: Juan Bustamante --- pkg/errors/errors.go | 9 ------ pkg/testmocks/mock_access_checker.go | 48 ---------------------------- 2 files changed, 57 deletions(-) delete mode 100644 pkg/errors/errors.go delete mode 100644 pkg/testmocks/mock_access_checker.go diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go deleted file mode 100644 index 669d3e8ab7..0000000000 --- a/pkg/errors/errors.go +++ /dev/null @@ -1,9 +0,0 @@ -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_access_checker.go b/pkg/testmocks/mock_access_checker.go deleted file mode 100644 index 558b85a580..0000000000 --- a/pkg/testmocks/mock_access_checker.go +++ /dev/null @@ -1,48 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/buildpacks/pack/pkg/client (interfaces: AccessChecker) - -// Package testmocks is a generated GoMock package. -package testmocks - -import ( - reflect "reflect" - - gomock "github.com/golang/mock/gomock" -) - -// MockAccessChecker is a mock of AccessChecker interface. -type MockAccessChecker struct { - ctrl *gomock.Controller - recorder *MockAccessCheckerMockRecorder -} - -// MockAccessCheckerMockRecorder is the mock recorder for MockAccessChecker. -type MockAccessCheckerMockRecorder struct { - mock *MockAccessChecker -} - -// NewMockAccessChecker creates a new mock instance. -func NewMockAccessChecker(ctrl *gomock.Controller) *MockAccessChecker { - mock := &MockAccessChecker{ctrl: ctrl} - mock.recorder = &MockAccessCheckerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAccessChecker) EXPECT() *MockAccessCheckerMockRecorder { - return m.recorder -} - -// Check mocks base method. -func (m *MockAccessChecker) Check(arg0 string) bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Check", arg0) - ret0, _ := ret[0].(bool) - return ret0 -} - -// Check indicates an expected call of Check. -func (mr *MockAccessCheckerMockRecorder) Check(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Check", reflect.TypeOf((*MockAccessChecker)(nil).Check), arg0) -} From 6cc186bffb06e18180efcd13d82ab6a74424e2cd Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Mon, 29 Apr 2024 14:12:53 -0500 Subject: [PATCH 63/79] bumping imgutil dependency Signed-off-by: Juan Bustamante --- go.mod | 5 +---- go.sum | 5 ++--- internal/commands/manifest.go | 12 ++++++++++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 7937581045..3596d568a5 100644 --- a/go.mod +++ b/go.mod @@ -94,7 +94,6 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect @@ -138,8 +137,6 @@ require ( go.opentelemetry.io/otel/metric v1.25.0 // indirect go.opentelemetry.io/otel/trace v1.25.0 // indirect golang.org/x/net v0.23.0 // indirect - golang.org/x/tools v0.17.0 // indirect - google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -147,4 +144,4 @@ require ( go 1.22 -replace github.com/buildpacks/imgutil => github.com/husni-faiz/imgutil v0.0.0-20240426182921-f79d933eb4e1 +replace github.com/buildpacks/imgutil => github.com/husni-faiz/imgutil v0.0.0-20240429172311-d4a3c74baf28 diff --git a/go.sum b/go.sum index c40af0a285..d91f36e588 100644 --- a/go.sum +++ b/go.sum @@ -221,8 +221,8 @@ github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLa github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0= github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/husni-faiz/imgutil v0.0.0-20240426182921-f79d933eb4e1 h1:B+i0nziQ6k42hkAr0bgua/zdWqm4R6dZuw3V2Wvrex4= -github.com/husni-faiz/imgutil v0.0.0-20240426182921-f79d933eb4e1/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk= +github.com/husni-faiz/imgutil v0.0.0-20240429172311-d4a3c74baf28 h1:3ZEG4lRtlmu/G9gWJXIGgxR5ulebIJ/M9euxLwtPO0g= +github.com/husni-faiz/imgutil v0.0.0-20240429172311-d4a3c74baf28/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -539,7 +539,6 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go. google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index f6984a89fe..2f4da9934a 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -9,8 +9,16 @@ import ( func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "manifest", - Short: "Interact with image index", - RunE: nil, + Short: "Interact with OCI image index", + Long: `The image index is a higher-level manifest which points to specific image manifests, ideal for one or more platforms, see: https://github.com/opencontainers/image-spec/ for more details + +'pack manifest' commands provides a set of tooling to create, update, delete or push images indexes to a remote registry, +'pack' will save a local copy (local storage) of the image index at '$PACK_HOME/manifests', also the environment variable +'XDG_RUNTIME_DIR' can be set to override the location, allowing to perform operations like 'pack manifest annotate' to +update attributes in the index before pushing it to a registry. + +This commands are experimental and the original RFC can be found at https://github.com/buildpacks/rfcs/blob/main/text/0124-pack-manifest-list-commands.md`, + RunE: nil, } cmd.AddCommand(ManifestCreate(logger, client)) From 6099f8202c63515af258d3770975aebfeee08e8c Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Mon, 29 Apr 2024 14:25:14 -0500 Subject: [PATCH 64/79] removing description from unit test Signed-off-by: Juan Bustamante --- internal/commands/manifest_test.go | 1 - pkg/client/push_manifest.go | 15 +++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/commands/manifest_test.go b/internal/commands/manifest_test.go index 983598e9f2..0cd300a492 100644 --- a/internal/commands/manifest_test.go +++ b/internal/commands/manifest_test.go @@ -46,7 +46,6 @@ func testNewManifestCommand(t *testing.T, when spec.G, it spec.S) { h.AssertNilE(t, err) output := outBuf.String() - h.AssertContains(t, output, "Interact with image index") h.AssertContains(t, output, "Usage:") for _, command := range []string{"create", "add", "annotate", "inspect", "remove", "rm"} { h.AssertContains(t, output, command) diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index 0867467805..0056862f22 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -1,6 +1,8 @@ package client import ( + "github.com/buildpacks/imgutil" + "github.com/google/go-containerregistry/pkg/v1/types" "github.com/pkg/errors" ) @@ -20,13 +22,14 @@ type PushManifestOptions struct { // PushManifest implements commands.PackClient. func (c *Client) PushManifest(opts PushManifestOptions) (err error) { + ops := parseOptions(opts) + idx, err := c.indexFactory.LoadIndex(opts.IndexRepoName) if err != nil { return } - // TODO pass through the options - if err = idx.Push(); err != nil { + if err = idx.Push(ops...); err != nil { return errors.Wrapf(err, "pushing index '%s'", opts.IndexRepoName) } @@ -38,9 +41,10 @@ func (c *Client) PushManifest(opts PushManifestOptions) (err error) { return idx.DeleteDir() } -/* -func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions []imgutil.IndexOption) { - idxOptions = append(idxOptions, imgutil.WithInsecure()) +func parseOptions(opts PushManifestOptions) (idxOptions []imgutil.IndexOption) { + if opts.Insecure { + idxOptions = append(idxOptions, imgutil.WithInsecure()) + } if opts.Purge { idxOptions = append(idxOptions, imgutil.WithPurge(true)) @@ -55,4 +59,3 @@ func parseFalgsForImgUtil(opts PushManifestOptions) (idxOptions []imgutil.IndexO return idxOptions } } -*/ From 8cdc107940bc42f407ff6bb7f7961db0a15ed03f Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Mon, 29 Apr 2024 18:16:49 -0500 Subject: [PATCH 65/79] adding more test coverage, some refactoring, fixing comments form Natalie review Signed-off-by: Juan Bustamante --- internal/commands/commands.go | 14 ++ internal/commands/manifest.go | 13 +- internal/commands/manifest_add.go | 12 +- internal/commands/manifest_annotate.go | 18 ++- internal/commands/manifest_create.go | 27 ++-- internal/commands/manifest_create_test.go | 90 +++++++++---- internal/commands/manifest_inspect.go | 8 +- internal/commands/manifest_push.go | 25 ++-- internal/commands/manifest_push_test.go | 2 +- internal/commands/manifest_remove.go | 6 +- internal/commands/manifest_rm.go | 15 +-- pkg/client/add_manifest.go | 13 +- pkg/client/add_manifest_test.go | 44 +------ pkg/client/common.go | 17 +++ pkg/client/create_manifest.go | 33 ++--- pkg/client/create_manifest_test.go | 152 +++++++++++++++++----- pkg/client/push_manifest.go | 11 +- pkg/client/push_manifest_test.go | 32 +---- pkg/client/remove_manifest_test.go | 5 +- testhelpers/image_index.go | 124 ++++++++++++++++++ 20 files changed, 425 insertions(+), 236 deletions(-) create mode 100644 testhelpers/image_index.go diff --git a/internal/commands/commands.go b/internal/commands/commands.go index edadfaf50f..fcc3bfaf68 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -7,6 +7,7 @@ import ( "os/signal" "syscall" + "github.com/google/go-containerregistry/pkg/v1/types" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -114,3 +115,16 @@ func isTrustedBuilder(cfg config.Config, builder string) bool { func deprecationWarning(logger logging.Logger, oldCmd, replacementCmd string) { logger.Warnf("Command %s has been deprecated, please use %s instead", style.Symbol("pack "+oldCmd), style.Symbol("pack "+replacementCmd)) } + +func parseFormatFlag(value string) (types.MediaType, error) { + var format types.MediaType + switch value { + case "oci": + format = types.OCIImageIndex + case "docker": + format = types.DockerManifestList + default: + return format, errors.Errorf("%s invalid media type format", value) + } + return format, nil +} diff --git a/internal/commands/manifest.go b/internal/commands/manifest.go index 2f4da9934a..e4cb5f8a71 100644 --- a/internal/commands/manifest.go +++ b/internal/commands/manifest.go @@ -9,15 +9,14 @@ import ( func NewManifestCommand(logger logging.Logger, client PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "manifest", - Short: "Interact with OCI image index", - Long: `The image index is a higher-level manifest which points to specific image manifests, ideal for one or more platforms, see: https://github.com/opencontainers/image-spec/ for more details + Short: "Interact with OCI image indexes", + Long: `An image index is a higher-level manifest which points to specific image manifests and is ideal for one or more platforms; see: https://github.com/opencontainers/image-spec/ for more details -'pack manifest' commands provides a set of tooling to create, update, delete or push images indexes to a remote registry, -'pack' will save a local copy (local storage) of the image index at '$PACK_HOME/manifests', also the environment variable -'XDG_RUNTIME_DIR' can be set to override the location, allowing to perform operations like 'pack manifest annotate' to -update attributes in the index before pushing it to a registry. +'pack manifest' commands provide tooling to create, update, or delete images indexes or push them to a remote registry. +'pack' will save a local copy of the image index at '$PACK_HOME/manifests'; the environment variable 'XDG_RUNTIME_DIR' +can be set to override the location, allowing manifests to be edited locally before being pushed to a registry. -This commands are experimental and the original RFC can be found at https://github.com/buildpacks/rfcs/blob/main/text/0124-pack-manifest-list-commands.md`, +These commands are experimental. For more information, consult the RFC which can be found at https://github.com/buildpacks/rfcs/blob/main/text/0124-pack-manifest-list-commands.md`, RunE: nil, } diff --git a/internal/commands/manifest_add.go b/internal/commands/manifest_add.go index 9343f80dcf..e5fefe6250 100644 --- a/internal/commands/manifest_add.go +++ b/internal/commands/manifest_add.go @@ -7,15 +7,13 @@ import ( "github.com/buildpacks/pack/pkg/logging" ) -// ManifestAdd modifies a manifest list (Image index) and add a new image to the list of manifests. +// ManifestAdd adds a new image to a manifest list (image index). func ManifestAdd(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ - Use: "add [OPTIONS] [flags]", - Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), - Short: "Add an image to a manifest list or image index.", - Example: `pack manifest add cnbs/sample-package:hello-multiarch-universe \ - cnbs/sample-package:hello-universe-riscv-linux`, - Long: `manifest add modifies a manifest list (Image index) and add a new image to the list of manifests.`, + Use: "add [OPTIONS] [flags]", + Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), + Short: "Add an image to a manifest list.", + Example: `pack manifest add my-image-index my-image:some-arch`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { return pack.AddManifest(cmd.Context(), client.ManifestAddOptions{ IndexRepoName: args[0], diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 7ea8407fda..5762ff7887 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -13,17 +13,15 @@ type ManifestAnnotateFlags struct { annotations map[string]string } -// ManifestAnnotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list. +// ManifestAnnotate modifies a manifest list and updates the platform information within the index for an image in the list. func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestAnnotateFlags cmd := &cobra.Command{ - Use: "annotate [OPTIONS] [flags]", - Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), - Short: "Add or update information about an entry in a manifest list or image index.", - Example: `pack manifest annotate cnbs/sample-package:hello-universe-multiarch \ - cnbs/sample-package:hello-universe --arch amd64`, - Long: `manifest annotate modifies a manifest list (Image index) and update the platform information for an image included in the manifest list.`, + Use: "annotate [OPTIONS] [flags]", + Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), + Short: "Add or update information about an entry in a manifest list.", + Example: `pack manifest annotate my-image-index my-image:some-arch --arch some-other-arch`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { return pack.AnnotateManifest(cmd.Context(), client.ManifestAnnotateOptions{ IndexRepoName: args[0], @@ -36,10 +34,10 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { }), } - cmd.Flags().StringVar(&flags.os, "os", "", "Set the architecture") + cmd.Flags().StringVar(&flags.os, "os", "", "Set the OS") cmd.Flags().StringVar(&flags.arch, "arch", "", "Set the architecture") - cmd.Flags().StringVar(&flags.variant, "variant", "", "Set the architecture") - cmd.Flags().StringToStringVar(&flags.annotations, "annotations", make(map[string]string, 0), "set an `annotation` for the specified image") + cmd.Flags().StringVar(&flags.variant, "variant", "", "Set the architecture variant") + cmd.Flags().StringToStringVar(&flags.annotations, "annotations", make(map[string]string, 0), "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 2b053c18f8..46ba215df2 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -1,6 +1,8 @@ package commands import ( + "strings" + "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -13,25 +15,28 @@ type ManifestCreateFlags struct { insecure, publish bool } -// ManifestCreate creates an image-index/image-list for a multi-arch image +// ManifestCreate creates an image index for a multi-arch image func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestCreateFlags cmd := &cobra.Command{ - Use: "create [ ... ] [flags]", - Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), - Short: "Create a manifest list or image index.", - Example: `pack manifest create cnbs/sample-package:hello-multiarch-universe \ - cnbs/sample-package:hello-universe \ - cnbs/sample-package:hello-universe-windows`, - Long: `Generate manifest list for a multi-arch image which will be stored locally for manipulating images within index`, + Use: "create [ ... ] [flags]", + Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), + Short: "Create a new manifest list.", + Example: `pack manifest create my-image-index my-image:some-arch my-image:some-other-arch`, + Long: `Create a new manifest list (e.g., for multi-arch images) which will be stored locally for manipulating images within the index`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + format, err := parseFormatFlag(strings.ToLower(flags.format)) + if err != nil { + return err + } + return pack.CreateManifest( cmd.Context(), client.CreateManifestOptions{ IndexRepoName: args[0], RepoNames: args[1:], - Format: flags.format, + Format: format, Insecure: flags.insecure, Publish: flags.publish, }, @@ -41,8 +46,8 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmdFlags := cmd.Flags() - cmdFlags.StringVarP(&flags.format, "format", "f", "v2s2", "Format to save image index as ('OCI' or 'V2S2')") - cmdFlags.BoolVar(&flags.insecure, "insecure", false, "Allow publishing to insecure registry") + cmdFlags.StringVarP(&flags.format, "format", "f", "oci", "Media type to use when saving the image index. Accepted values are: oci, docker") + cmdFlags.BoolVar(&flags.insecure, "insecure", false, "When pushing the index to a registry, do not use TLS encryption or certificate verification") cmdFlags.BoolVar(&flags.publish, "publish", false, "Publish to registry") AddHelpFlag(cmd, "create") diff --git a/internal/commands/manifest_create_test.go b/internal/commands/manifest_create_test.go index b7927685a9..156d0f1dee 100644 --- a/internal/commands/manifest_create_test.go +++ b/internal/commands/manifest_create_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/v1/types" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" @@ -12,6 +13,7 @@ import ( "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" h "github.com/buildpacks/pack/testhelpers" ) @@ -20,7 +22,7 @@ func TestManifestCreateCommand(t *testing.T) { color.Disable(true) defer color.Disable(false) - spec.Run(t, "Commands", testManifestCreateCommand, spec.Random(), spec.Report(report.Terminal{})) + spec.Run(t, "Commands", testManifestCreateCommand, spec.Parallel(), spec.Report(report.Terminal{})) } func testManifestCreateCommand(t *testing.T, when spec.G, it spec.S) { @@ -39,36 +41,70 @@ func testManifestCreateCommand(t *testing.T, when spec.G, it spec.S) { command = commands.ManifestCreate(logger, mockClient) }) - it("should annotate images with given flags", func() { - prepareCreateManifest(t, mockClient) - command.SetArgs([]string{ - "some-index", - "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", - "--format", - "v2s2", - "--insecure", - "--publish", + when("valid arguments", func() { + it.Before(func() { + mockClient. + EXPECT(). + CreateManifest(gomock.Any(), + client.CreateManifestOptions{ + IndexRepoName: "some-index", + RepoNames: []string{"some-manifest"}, + Format: types.DockerManifestList, + Insecure: true, + Publish: true, + }, + ). + AnyTimes(). + Return(nil) + }) + + it("should annotate images with given flags", func() { + command.SetArgs([]string{ + "some-index", "some-manifest", + "--format", "docker", + "--insecure", "--publish", + }) + h.AssertNil(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") }) - h.AssertNil(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") }) - it("should have help flag", func() { - prepareCreateManifest(t, mockClient) - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") + when("invalid arguments", func() { + when("invalid media type", func() { + var format string + it.Before(func() { + format = "invalid" + mockClient. + EXPECT(). + CreateManifest(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil) + }) + + it("error a message", func() { + command.SetArgs([]string{ + "some-index", "some-manifest", + "--format", format, + }) + h.AssertNotNil(t, command.Execute()) + }) + }) }) -} -func prepareCreateManifest(t *testing.T, mockClient *testmocks.MockPackClient) { - mockClient. - EXPECT(). - CreateManifest( - gomock.Any(), - gomock.Any(), - ). - AnyTimes(). - Return(nil) + when("help is invoke", func() { + it.Before(func() { + mockClient. + EXPECT(). + CreateManifest(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil) + }) + + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) + }) } diff --git a/internal/commands/manifest_inspect.go b/internal/commands/manifest_inspect.go index 3779a0af80..aaa61710f9 100644 --- a/internal/commands/manifest_inspect.go +++ b/internal/commands/manifest_inspect.go @@ -8,15 +8,13 @@ import ( "github.com/buildpacks/pack/pkg/logging" ) -// ManifestInspect shows the manifest information stored in local storage +// ManifestInspect shows the manifest information stored locally func ManifestInspect(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "inspect ", Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), - Short: "Display a manifest list or image index.", - Example: `pack manifest inspect cnbs/sample-builder:multiarch`, - Long: `manifest inspect shows the manifest information stored in local storage. - The inspect command will help users to view how their local manifest list looks like`, + Short: "Display information about a manifest list.", + Example: `pack manifest inspect my-image-index`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { if args[0] == "" { return errors.New("'' is required") diff --git a/internal/commands/manifest_push.go b/internal/commands/manifest_push.go index 109b5fce72..1b5b1e1309 100644 --- a/internal/commands/manifest_push.go +++ b/internal/commands/manifest_push.go @@ -1,6 +1,8 @@ package commands import ( + "strings" + "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -13,30 +15,35 @@ type ManifestPushFlags struct { insecure, purge bool } -// ManifestPush pushes a manifest list (Image index) to a registry. +// ManifestPush pushes a manifest list to a remote registry. func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestPushFlags cmd := &cobra.Command{ Use: "push [OPTIONS] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs), - Short: "Push a manifest list or image index to a registry.", - Example: `pack manifest push cnbs/sample-package:hello-multiarch-universe`, - Long: `manifest push pushes a manifest list (Image index) to a registry. - Once a manifest list is ready to be published into the registry, the push command can be used`, + Short: "Push a manifest list to a registry.", + Example: `pack manifest push my-image-index`, + Long: `manifest push' pushes a manifest list to a registry. +Use other 'pack manifest' commands to prepare the manifest list locally, then use the push command.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { + format, err := parseFormatFlag(strings.ToLower(flags.format)) + if err != nil { + return err + } + return pack.PushManifest(client.PushManifestOptions{ IndexRepoName: args[0], - Format: flags.format, + Format: format, Insecure: flags.insecure, Purge: flags.purge, }) }), } - cmd.Flags().StringVarP(&flags.format, "format", "f", "", "Format to save image index as ('OCI' or 'V2S2')") - cmd.Flags().BoolVar(&flags.insecure, "insecure", false, "Allow publishing to insecure registry") - cmd.Flags().BoolVar(&flags.purge, "purge", false, "Delete the manifest list or image index from local storage if pushing succeeds") + cmd.Flags().StringVarP(&flags.format, "format", "f", "oci", "Media type to use when saving the image index. Accepted values are: oci, docker") + cmd.Flags().BoolVar(&flags.insecure, "insecure", false, "When pushing the index to a registry, do not use TLS encryption or certificate verification") + cmd.Flags().BoolVar(&flags.purge, "purge", false, "Delete the manifest list from local storage if pushing succeeds") AddHelpFlag(cmd, "push") return cmd diff --git a/internal/commands/manifest_push_test.go b/internal/commands/manifest_push_test.go index 42360e6654..dd6ded4a74 100644 --- a/internal/commands/manifest_push_test.go +++ b/internal/commands/manifest_push_test.go @@ -46,7 +46,7 @@ func testManifestPushCommand(t *testing.T, when spec.G, it spec.S) { command.SetArgs([]string{ "some-index", "-f", - "v2s2", + "docker", "--purge", "--insecure", }) diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index 7a4f99f85c..34d0cf4cf4 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -12,10 +12,8 @@ func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "remove [manifest-list] [manifest-list...]", Args: cobra.MatchAll(cobra.MinimumNArgs(1), cobra.OnlyValidArgs), - Short: "Remove an image from a manifest list or image index.", - Example: `pack manifest remove cnbs/sample-package:hello-multiarch-universe`, - Long: `Delete one or more manifest lists from local storage. - When a manifest list exits locally, users can remove existing images from a manifest list`, + Short: "Remove one or more manifest lists from local storage", + Example: `pack manifest remove my-image-index`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { return NewErrors(pack.DeleteManifest(cmd.Context(), args)).Error() }), diff --git a/internal/commands/manifest_rm.go b/internal/commands/manifest_rm.go index 33bf998576..383f7660e4 100644 --- a/internal/commands/manifest_rm.go +++ b/internal/commands/manifest_rm.go @@ -9,14 +9,13 @@ import ( // ManifestRemove will remove the specified image manifest if it is already referenced in the index func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { cmd := &cobra.Command{ - Use: "rm [manifest-list] [manifest] [manifest...] [flags]", - Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), - Short: "Remove manifest list or image index from local storage.", - Example: `pack manifest rm cnbs/sample-package:hello-multiarch-universe \ - cnbs/sample-package@sha256:42969d8175941c21ab739d3064e9cd7e93c972a0a6050602938ed501d156e452`, - Long: `manifest rm will remove the specified image manifest if it is already referenced in the index. - User must pass digest of the image in oder to delete it from index. - Sometimes users can just experiment with the feature locally and they want to discard all the local information created by pack. 'rm' command just delete the local manifest list`, + Use: "rm [manifest-list] [manifest] [manifest...] [flags]", + Args: cobra.MatchAll(cobra.MinimumNArgs(2), cobra.OnlyValidArgs), + Short: "Remove an image manifest from a manifest list.", + Example: `pack manifest rm my-image-index my-image@sha256:`, + Long: `'manifest rm' will remove the specified image manifest if it is already referenced in the index. +Users must pass the digest of the image in order to delete it from the index. +To discard __all__ the images in an index and the index itself, use 'manifest delete'.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { return NewErrors(pack.RemoveManifest(cmd.Context(), args[0], args[1:])).Error() }), diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 367f3ecfc6..071b70c3fa 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -3,10 +3,6 @@ package client import ( "context" "fmt" - - "github.com/google/go-containerregistry/pkg/name" - - "github.com/buildpacks/pack/pkg/image" ) type ManifestAddOptions struct { @@ -24,17 +20,10 @@ func (c *Client) AddManifest(ctx context.Context, opts ManifestAddOptions) (err return err } - imageRef, err := name.ParseReference(opts.RepoName, name.WeakValidation) - if err != nil { - return fmt.Errorf("'%s' is not a valid manifest reference: %s", opts.RepoName, err) - } - - imageToAdd, err := c.imageFetcher.Fetch(ctx, imageRef.Name(), image.FetchOptions{Daemon: false}) - if err != nil { + if err = c.addManifestToIndex(ctx, opts.IndexRepoName, opts.RepoName, idx); err != nil { return err } - idx.AddManifest(imageToAdd.UnderlyingImage()) if err = idx.SaveDir(); err != nil { return fmt.Errorf("'%s' could not be saved in the local storage: %s", opts.RepoName, err) } diff --git a/pkg/client/add_manifest_test.go b/pkg/client/add_manifest_test.go index 8dce955eb5..95e6a0d6b6 100644 --- a/pkg/client/add_manifest_test.go +++ b/pkg/client/add_manifest_test.go @@ -171,22 +171,8 @@ func prepareIndexWithoutLocallyExists(mockIndexFactory testmocks.MockIndexFactor Return(nil, errors.New("index not found locally")) } -func randomCNBIndex(t *testing.T, repoName string) *imgutil.CNBIndex { - ridx, err := random.Index(1024, 1, 2) - h.AssertNil(t, err) - options := &imgutil.IndexOptions{ - BaseIndex: ridx, - LayoutIndexOptions: imgutil.LayoutIndexOptions{ - XdgPath: os.Getenv("XDG_RUNTIME_DIR"), - }, - } - idx, err := imgutil.NewCNBIndex(repoName, *options) - h.AssertNil(t, err) - return idx -} - func prepareLoadIndex(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { - idx := randomCNBIndex(t, repoName) + idx := h.RandomCNBIndex(t, repoName, 1, 2) mockIndexFactory. EXPECT(). LoadIndex(gomock.Eq(repoName), gomock.Any()). @@ -197,8 +183,8 @@ func prepareLoadIndex(t *testing.T, repoName string, mockIndexFactory testmocks. } func prepareLoadIndexWithErrorOnSave(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { - cnbIdx := randomCNBIndex(t, repoName) - idx := &mockImageIndex{ + cnbIdx := h.RandomCNBIndex(t, repoName, 1, 2) + idx := &h.MockImageIndex{ CNBIndex: *cnbIdx, ErrorOnSave: true, } @@ -219,27 +205,3 @@ type testImage struct { func (t *testImage) UnderlyingImage() v1.Image { return t.underlyingImage } - -type mockImageIndex struct { - imgutil.CNBIndex - ErrorOnSave bool - PushCalled bool - DeleteDirCalled bool -} - -func (i *mockImageIndex) SaveDir() error { - if i.ErrorOnSave { - return errors.New("something failed writing the index on disk") - } - return i.CNBIndex.SaveDir() -} - -func (i *mockImageIndex) Push(_ ...imgutil.IndexOption) error { - i.PushCalled = true - return nil -} - -func (i *mockImageIndex) DeleteDir() error { - i.DeleteDirCalled = true - return nil -} diff --git a/pkg/client/common.go b/pkg/client/common.go index e8e7f07ab8..8ce0752d61 100644 --- a/pkg/client/common.go +++ b/pkg/client/common.go @@ -1,9 +1,11 @@ package client import ( + "context" "errors" "fmt" + "github.com/buildpacks/imgutil" "github.com/google/go-containerregistry/pkg/name" "github.com/buildpacks/pack/internal/builder" @@ -14,6 +16,21 @@ import ( "github.com/buildpacks/pack/pkg/logging" ) +func (c *Client) addManifestToIndex(ctx context.Context, indexRepoName, repoName string, index imgutil.ImageIndex) error { + imageRef, err := name.ParseReference(repoName, name.WeakValidation) + if err != nil { + return fmt.Errorf("'%s' is not a valid manifest reference: %s", style.Symbol(indexRepoName), err) + } + + imageToAdd, err := c.imageFetcher.Fetch(ctx, imageRef.Name(), image.FetchOptions{Daemon: false}) + if err != nil { + return err + } + + index.AddManifest(imageToAdd.UnderlyingImage()) + return nil +} + func (c *Client) parseTagReference(imageName string) (name.Reference, error) { if imageName == "" { return nil, errors.New("image is a required parameter") diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 7e36abec3c..10a03c8656 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -5,11 +5,10 @@ import ( "fmt" "github.com/buildpacks/imgutil" - "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/types" "github.com/pkg/errors" - "github.com/buildpacks/pack/pkg/image" + "github.com/buildpacks/pack/internal/style" ) type CreateManifestOptions struct { @@ -20,7 +19,7 @@ type CreateManifestOptions struct { RepoNames []string // Media type of the index - Format string + Format types.MediaType // true if we want to publish to an insecure registry Insecure bool @@ -43,25 +42,16 @@ func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) } for _, repoName := range opts.RepoNames { - // TODO same code to add_manifest.go externalize it! - imageRef, err := name.ParseReference(repoName, name.WeakValidation) - if err != nil { - return fmt.Errorf("'%s' is not a valid manifest reference: %s", repoName, err) - } - - imageToAdd, err := c.imageFetcher.Fetch(ctx, imageRef.Name(), image.FetchOptions{Daemon: false}) - if err != nil { + if err = c.addManifestToIndex(ctx, opts.IndexRepoName, repoName, index); err != nil { return err } - - index.AddManifest(imageToAdd.UnderlyingImage()) } if err = index.SaveDir(); err != nil { - return fmt.Errorf("'%s' could not be saved in the local storage: %s", opts.IndexRepoName, err) + return fmt.Errorf("'%s' could not be saved in the local storage: %s", style.Symbol(opts.IndexRepoName), err) } - c.logger.Infof("successfully created index: '%s'\n", opts.IndexRepoName) + c.logger.Infof("successfully created index: '%s'\n", style.Symbol(opts.IndexRepoName)) if !opts.Publish { return nil } @@ -70,25 +60,18 @@ func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) return err } - c.logger.Infof("successfully pushed '%s' to registry \n", opts.IndexRepoName) + c.logger.Infof("successfully pushed '%s' to registry \n", style.Symbol(opts.IndexRepoName)) return nil } func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []imgutil.IndexOption) { - var format types.MediaType - switch opts.Format { - case "oci": - format = types.OCIImageIndex - default: - format = types.DockerManifestList - } if opts.Insecure { return []imgutil.IndexOption{ - imgutil.WithMediaType(format), + imgutil.WithMediaType(opts.Format), imgutil.WithInsecure(), } } return []imgutil.IndexOption{ - imgutil.WithMediaType(format), + imgutil.WithMediaType(opts.Format), } } diff --git a/pkg/client/create_manifest_test.go b/pkg/client/create_manifest_test.go index fb7707d83d..88d9993379 100644 --- a/pkg/client/create_manifest_test.go +++ b/pkg/client/create_manifest_test.go @@ -4,17 +4,17 @@ import ( "bytes" "context" "os" + "path/filepath" "testing" + "github.com/buildpacks/imgutil" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/types" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/imgutil" - ifakes "github.com/buildpacks/pack/internal/fakes" "github.com/buildpacks/pack/pkg/logging" "github.com/buildpacks/pack/pkg/testmocks" @@ -65,26 +65,129 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { }) when("#CreateManifest", func() { + var indexRepoName string when("index doesn't exists", func() { - when("remote manifest exists", func() { + var indexLocalPath string + + when("remote manifest is provided", func() { it.Before(func() { fakeImage := setUpRemoteImageForIndex(t, nil) fakeImageFetcher.RemoteImages["index.docker.io/library/busybox:1.36-musl"] = fakeImage + }) + + when("publish is false", func() { + it.Before(func() { + // We want to actually create an index, so no need to mock the index factory + subject, err = NewClient( + WithLogger(logger), + WithFetcher(fakeImageFetcher), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + }) - prepareMockImageFactoryForValidCreateIndex(t, mockIndexFactory) + when("no errors on save", func() { + it.Before(func() { + indexRepoName = newIndexRepoName() + indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + }) + + it("creates the index adding the manifest", func() { + err = subject.CreateManifest( + context.TODO(), + CreateManifestOptions{ + IndexRepoName: indexRepoName, + RepoNames: []string{"busybox:1.36-musl"}, + Format: types.OCIImageIndex, + }, + ) + h.AssertNil(t, err) + index := h.ReadIndexManifest(t, indexLocalPath) + h.AssertEq(t, len(index.Manifests), 1) + h.AssertEq(t, index.MediaType, types.OCIImageIndex) + }) + }) }) + when("publish is true", func() { + var index *h.MockImageIndex + + when("no errors on save", func() { + it.Before(func() { + indexRepoName = newIndexRepoName() + indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + + // index stub return to check if push operation was called + index = h.NewMockImageIndex(t, indexRepoName, 0, 0) + + // We need to mock the index factory to inject a stub index to be pushed. + mockIndexFactory.EXPECT().Exists(gomock.Eq(indexRepoName)).Return(false) + mockIndexFactory.EXPECT().CreateIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(index, nil) + }) + + it("creates the index adding the manifest and push it to the registry", func() { + err = subject.CreateManifest( + context.TODO(), + CreateManifestOptions{ + IndexRepoName: indexRepoName, + RepoNames: []string{"busybox:1.36-musl"}, + Format: types.OCIImageIndex, + Publish: true, + }, + ) + h.AssertNil(t, err) + + indexOnDisk := h.ReadIndexManifest(t, indexLocalPath) + h.AssertEq(t, len(indexOnDisk.Manifests), 1) + h.AssertEq(t, indexOnDisk.MediaType, types.OCIImageIndex) + + h.AssertTrue(t, index.PushCalled) + }) + }) + }) + }) + + when("no manifest is provided", func() { when("no errors on save", func() { - it("creates the index with the given manifest", func() { + it.Before(func() { + // We want to actually create an index, so no need to mock the index factory + subject, err = NewClient( + WithLogger(logger), + WithFetcher(fakeImageFetcher), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + + indexRepoName = newIndexRepoName() + indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + }) + + it("creates an empty index with OCI media-type", func() { + err = subject.CreateManifest( + context.TODO(), + CreateManifestOptions{ + IndexRepoName: indexRepoName, + Format: types.OCIImageIndex, + }, + ) + h.AssertNil(t, err) + index := h.ReadIndexManifest(t, indexLocalPath) + h.AssertEq(t, len(index.Manifests), 0) + h.AssertEq(t, index.MediaType, types.OCIImageIndex) + }) + + it("creates an empty index with Docker media-type", func() { err = subject.CreateManifest( context.TODO(), CreateManifestOptions{ - IndexRepoName: "pack/imgutil", - RepoNames: []string{"busybox:1.36-musl"}, - Insecure: true, + IndexRepoName: indexRepoName, + Format: types.DockerManifestList, }, ) h.AssertNil(t, err) + index := h.ReadIndexManifest(t, indexLocalPath) + h.AssertEq(t, len(index.Manifests), 0) + h.AssertEq(t, index.MediaType, types.DockerManifestList) }) }) }) @@ -92,17 +195,17 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { when("index exists", func() { it.Before(func() { - mockIndexFactory.EXPECT(). - Exists(gomock.Any()).AnyTimes().Return(true) + indexRepoName = newIndexRepoName() + + // mock the index factory to simulate the index exists + mockIndexFactory.EXPECT().Exists(gomock.Eq(indexRepoName)).AnyTimes().Return(true) }) - it("return an error when index exists already", func() { + it("return an error when index already exists", func() { err = subject.CreateManifest( context.TODO(), CreateManifestOptions{ - IndexRepoName: "pack/imgutil", - RepoNames: []string{"busybox:1.36-musl"}, - Insecure: true, + IndexRepoName: indexRepoName, }, ) h.AssertEq(t, err.Error(), "exits in your local storage, use 'pack manifest remove' if you want to delete it") @@ -111,21 +214,6 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { }) } -func prepareMockImageFactoryForValidCreateIndex(t *testing.T, mockIndexFactory *testmocks.MockIndexFactory) { - ridx, err := random.Index(1024, 1, 2) - h.AssertNil(t, err) - - options := &imgutil.IndexOptions{ - BaseIndex: ridx, - } - idx, err := imgutil.NewCNBIndex("foo", *options) - h.AssertNil(t, err) - - mockIndexFactory.EXPECT(). - Exists(gomock.Any()).AnyTimes().Return(false) - - mockIndexFactory.EXPECT(). - CreateIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(idx, err) +func newIndexRepoName() string { + return "test-create-index-" + h.RandString(10) } diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index 0056862f22..37268ba85a 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -11,7 +11,7 @@ type PushManifestOptions struct { IndexRepoName string // Index media-type - Format string + Format types.MediaType // true if we want to publish to an insecure registry Insecure bool @@ -50,12 +50,5 @@ func parseOptions(opts PushManifestOptions) (idxOptions []imgutil.IndexOption) { idxOptions = append(idxOptions, imgutil.WithPurge(true)) } - switch opts.Format { - case "oci": - return append(idxOptions, imgutil.WithMediaType(types.OCIImageIndex)) - case "v2s2": - return append(idxOptions, imgutil.WithMediaType(types.DockerManifestList)) - default: - return idxOptions - } + return append(idxOptions, imgutil.WithMediaType(opts.Format)) } diff --git a/pkg/client/push_manifest_test.go b/pkg/client/push_manifest_test.go index 88090f324c..03c6ea02e8 100644 --- a/pkg/client/push_manifest_test.go +++ b/pkg/client/push_manifest_test.go @@ -54,12 +54,13 @@ func testPushManifest(t *testing.T, when spec.G, it spec.S) { when("#PushManifest", func() { when("index exists locally", func() { - var index *mockImageIndex + var index *h.MockImageIndex it.Before(func() { - index = newMockImageIndex(t, "some-index", *mockIndexFactory) + index = h.NewMockImageIndex(t, "some-index", 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq("some-index"), gomock.Any()).Return(index, nil) }) - it("should push index to registry", func() { + it("push the index to the registry", func() { err = subject.PushManifest(PushManifestOptions{ IndexRepoName: "some-index", }) @@ -70,10 +71,10 @@ func testPushManifest(t *testing.T, when spec.G, it spec.S) { when("index doesn't exist locally", func() { it.Before(func() { - prepareLoadIndexWithError(*mockIndexFactory) + mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("ErrNoImageOrIndexFoundWithGivenDigest")) }) - it("should not have local image index", func() { + it("error a message", func() { err = subject.PushManifest(PushManifestOptions{ IndexRepoName: "some-index", }) @@ -82,24 +83,3 @@ func testPushManifest(t *testing.T, when spec.G, it spec.S) { }) }) } - -func prepareLoadIndexWithError(mockIndexFactory testmocks.MockIndexFactory) { - mockIndexFactory. - EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - Return(nil, errors.New("ErrNoImageOrIndexFoundWithGivenDigest")) -} - -func newMockImageIndex(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) *mockImageIndex { - cnbIdx := randomCNBIndex(t, repoName) - idx := &mockImageIndex{ - CNBIndex: *cnbIdx, - } - mockIndexFactory. - EXPECT(). - LoadIndex(gomock.Eq(repoName), gomock.Any()). - Return(idx, nil). - AnyTimes() - - return idx -} diff --git a/pkg/client/remove_manifest_test.go b/pkg/client/remove_manifest_test.go index 812d8bbac5..9bb493c6f0 100644 --- a/pkg/client/remove_manifest_test.go +++ b/pkg/client/remove_manifest_test.go @@ -69,9 +69,10 @@ func testDeleteManifest(t *testing.T, when spec.G, it spec.S) { }) when("index exists", func() { - var index *mockImageIndex + var index *h.MockImageIndex it.Before(func() { - index = newMockImageIndex(t, "some-index", *mockIndexFactory) + index = h.NewMockImageIndex(t, "some-index", 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq("some-index"), gomock.Any()).Return(index, nil) }) it("should delete local index", func() { diff --git a/testhelpers/image_index.go b/testhelpers/image_index.go new file mode 100644 index 0000000000..d2f391042d --- /dev/null +++ b/testhelpers/image_index.go @@ -0,0 +1,124 @@ +package testhelpers + +import ( + "encoding/json" + "errors" + "net/http" + "os" + "path/filepath" + "testing" + + "github.com/buildpacks/imgutil" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/types" +) + +func AssertRemoteImageIndex(t *testing.T, repoName string, mediaType types.MediaType, expectedNumberOfManifests int) { + t.Helper() + + remoteIndex := FetchImageIndexDescriptor(t, repoName) + AssertNotNil(t, remoteIndex) + remoteIndexMediaType, err := remoteIndex.MediaType() + AssertNil(t, err) + AssertEq(t, remoteIndexMediaType, mediaType) + remoteIndexManifest, err := remoteIndex.IndexManifest() + AssertNil(t, err) + AssertNotNil(t, remoteIndexManifest) + AssertEq(t, len(remoteIndexManifest.Manifests), expectedNumberOfManifests) +} + +func AssertPathExists(t *testing.T, path string) { + t.Helper() + _, err := os.Stat(path) + if os.IsNotExist(err) { + t.Errorf("Expected %q to exist", path) + } else if err != nil { + t.Fatalf("Error stating %q: %v", path, err) + } +} + +func FetchImageIndexDescriptor(t *testing.T, repoName string) v1.ImageIndex { + t.Helper() + + r, err := name.ParseReference(repoName, name.WeakValidation) + AssertNil(t, err) + + auth, err := authn.DefaultKeychain.Resolve(r.Context().Registry) + AssertNil(t, err) + + index, err := remote.Index(r, remote.WithTransport(http.DefaultTransport), remote.WithAuth(auth)) + AssertNil(t, err) + + return index +} + +func ReadIndexManifest(t *testing.T, path string) *v1.IndexManifest { + t.Helper() + + indexPath := filepath.Join(path, "index.json") + AssertPathExists(t, filepath.Join(path, "oci-layout")) + AssertPathExists(t, indexPath) + + // check index file + data, err := os.ReadFile(indexPath) + AssertNil(t, err) + + index := &v1.IndexManifest{} + err = json.Unmarshal(data, index) + AssertNil(t, err) + return index +} + +func RandomCNBIndex(t *testing.T, repoName string, layers, count int64) *imgutil.CNBIndex { + t.Helper() + + randomIndex, err := random.Index(1024, layers, count) + AssertNil(t, err) + options := &imgutil.IndexOptions{ + BaseIndex: randomIndex, + LayoutIndexOptions: imgutil.LayoutIndexOptions{ + XdgPath: os.Getenv("XDG_RUNTIME_DIR"), + }, + } + idx, err := imgutil.NewCNBIndex(repoName, *options) + AssertNil(t, err) + return idx +} + +// MockImageIndex wraps a real CNBIndex to record if some key methods are invoke +type MockImageIndex struct { + imgutil.CNBIndex + ErrorOnSave bool + PushCalled bool + DeleteDirCalled bool +} + +// NewMockImageIndex creates a random index with the given number of layers and manifests count +func NewMockImageIndex(t *testing.T, repoName string, layers, count int64) *MockImageIndex { + cnbIdx := RandomCNBIndex(t, repoName, layers, count) + idx := &MockImageIndex{ + CNBIndex: *cnbIdx, + } + return idx +} + +func (i *MockImageIndex) SaveDir() error { + if i.ErrorOnSave { + return errors.New("something failed writing the index on disk") + } + return i.CNBIndex.SaveDir() +} + +func (i *MockImageIndex) Push(_ ...imgutil.IndexOption) error { + i.PushCalled = true + return nil +} + +func (i *MockImageIndex) DeleteDir() error { + i.DeleteDirCalled = true + return nil +} From 88ca857efb1f2a4e9c5d991c9c4934b4616fa14c Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Tue, 30 Apr 2024 09:05:06 -0500 Subject: [PATCH 66/79] updating publish flag description Signed-off-by: Juan Bustamante --- internal/commands/manifest_create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index 46ba215df2..f8c9c5be3e 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -48,7 +48,7 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmdFlags.StringVarP(&flags.format, "format", "f", "oci", "Media type to use when saving the image index. Accepted values are: oci, docker") cmdFlags.BoolVar(&flags.insecure, "insecure", false, "When pushing the index to a registry, do not use TLS encryption or certificate verification") - cmdFlags.BoolVar(&flags.publish, "publish", false, "Publish to registry") + cmdFlags.BoolVar(&flags.publish, "publish", false, "Publish directly to a registry without saving a local copy") AddHelpFlag(cmd, "create") return cmd From bd58f8d6fe576e234c848da522fc723619b7b062 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Tue, 30 Apr 2024 15:34:16 -0500 Subject: [PATCH 67/79] updating to latest imgutil version, it fixes the issue overriding the media-type, also polishing the tests a little bit Signed-off-by: Juan Bustamante --- go.mod | 2 +- go.sum | 4 +- pkg/client/add_manifest.go | 2 +- pkg/client/add_manifest_test.go | 87 +++++++--------------------- pkg/client/annotate_manifest_test.go | 82 ++++++++++---------------- pkg/client/common.go | 4 +- pkg/client/create_manifest.go | 24 ++++---- pkg/client/create_manifest_test.go | 23 +++----- pkg/client/inspect_manifest.go | 5 +- pkg/client/push_manifest.go | 9 ++- pkg/client/remove_manifest.go | 2 +- pkg/client/remove_manifest_test.go | 3 +- pkg/client/rm_manifest.go | 4 +- pkg/client/rm_manifest_test.go | 12 +--- testhelpers/image_index.go | 70 +++++++++++++++++----- 15 files changed, 152 insertions(+), 181 deletions(-) diff --git a/go.mod b/go.mod index 3596d568a5..02d71f171b 100644 --- a/go.mod +++ b/go.mod @@ -144,4 +144,4 @@ require ( go 1.22 -replace github.com/buildpacks/imgutil => github.com/husni-faiz/imgutil v0.0.0-20240429172311-d4a3c74baf28 +replace github.com/buildpacks/imgutil => github.com/husni-faiz/imgutil v0.0.0-20240430180646-2e42a193c20c diff --git a/go.sum b/go.sum index d91f36e588..71dd929088 100644 --- a/go.sum +++ b/go.sum @@ -221,8 +221,8 @@ github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLa github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0= github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/husni-faiz/imgutil v0.0.0-20240429172311-d4a3c74baf28 h1:3ZEG4lRtlmu/G9gWJXIGgxR5ulebIJ/M9euxLwtPO0g= -github.com/husni-faiz/imgutil v0.0.0-20240429172311-d4a3c74baf28/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk= +github.com/husni-faiz/imgutil v0.0.0-20240430180646-2e42a193c20c h1:MNq3VV23VeiV2svOaBrXYuv2ZIgdffwLtRxBiuHlPsc= +github.com/husni-faiz/imgutil v0.0.0-20240430180646-2e42a193c20c/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= diff --git a/pkg/client/add_manifest.go b/pkg/client/add_manifest.go index 071b70c3fa..eec0010edf 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/add_manifest.go @@ -20,7 +20,7 @@ func (c *Client) AddManifest(ctx context.Context, opts ManifestAddOptions) (err return err } - if err = c.addManifestToIndex(ctx, opts.IndexRepoName, opts.RepoName, idx); err != nil { + if err = c.addManifestToIndex(ctx, opts.RepoName, idx); err != nil { return err } diff --git a/pkg/client/add_manifest_test.go b/pkg/client/add_manifest_test.go index 95e6a0d6b6..f601806b42 100644 --- a/pkg/client/add_manifest_test.go +++ b/pkg/client/add_manifest_test.go @@ -7,12 +7,8 @@ import ( "os" "testing" - "github.com/buildpacks/imgutil" - "github.com/buildpacks/imgutil/fakes" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/random" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" @@ -27,8 +23,7 @@ func TestAddManifest(t *testing.T) { color.Disable(true) defer color.Disable(false) - // TODO I think we can make this test to run in parallel - spec.Run(t, "build", testAddManifest, spec.Report(report.Terminal{})) + spec.Run(t, "build", testAddManifest, spec.Parallel(), spec.Report(report.Terminal{})) } func testAddManifest(t *testing.T, when spec.G, it spec.S) { @@ -64,7 +59,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) // Create a remote image to be fetched when adding to the image index - fakeImage := setUpRemoteImageForIndex(t, nil) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, nil) fakeImageFetcher.RemoteImages["index.docker.io/pack/image:latest"] = fakeImage }) it.After(func() { @@ -75,7 +70,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { when("#AddManifest", func() { when("index doesn't exists", func() { it.Before(func() { - prepareIndexWithoutLocallyExists(*mockIndexFactory) + mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("index not found locally")) }) it("should return an error", func() { @@ -91,16 +86,22 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { }) when("index exists", func() { + var indexRepoName string + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + }) + when("no errors on save", func() { it.Before(func() { - prepareLoadIndex(t, "pack/index", *mockIndexFactory) + idx := h.RandomCNBIndex(t, indexRepoName, 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) }) it("adds the given image", func() { err = subject.AddManifest( context.TODO(), ManifestAddOptions{ - IndexRepoName: "pack/index", + IndexRepoName: indexRepoName, RepoName: "pack/image", }, ) @@ -112,7 +113,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { err = subject.AddManifest( context.TODO(), ManifestAddOptions{ - IndexRepoName: "pack/index", + IndexRepoName: indexRepoName, RepoName: "pack@@image", }, ) @@ -124,7 +125,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { err = subject.AddManifest( context.TODO(), ManifestAddOptions{ - IndexRepoName: "pack/index", + IndexRepoName: indexRepoName, RepoName: "pack/image-not-found", }, ) @@ -135,14 +136,20 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { when("errors on save", func() { it.Before(func() { - prepareLoadIndexWithErrorOnSave(t, "pack/index-error-on-saved", *mockIndexFactory) + cnbIdx := h.NewMockImageIndex(t, indexRepoName, 1, 2) + cnbIdx.ErrorOnSave = true + mockIndexFactory. + EXPECT(). + LoadIndex(gomock.Eq(indexRepoName), gomock.Any()). + Return(cnbIdx, nil). + AnyTimes() }) it("error when manifest couldn't be saved locally", func() { err = subject.AddManifest( context.TODO(), ManifestAddOptions{ - IndexRepoName: "pack/index-error-on-saved", + IndexRepoName: indexRepoName, RepoName: "pack/image", }, ) @@ -153,55 +160,3 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { }) }) } - -func setUpRemoteImageForIndex(t *testing.T, identifier imgutil.Identifier) *testImage { - fakeCNBImage := fakes.NewImage("pack/image", "", identifier) - underlyingImage, err := random.Image(1024, 1) - h.AssertNil(t, err) - return &testImage{ - Image: fakeCNBImage, - underlyingImage: underlyingImage, - } -} - -func prepareIndexWithoutLocallyExists(mockIndexFactory testmocks.MockIndexFactory) { - mockIndexFactory. - EXPECT(). - LoadIndex(gomock.Any(), gomock.Any()). - Return(nil, errors.New("index not found locally")) -} - -func prepareLoadIndex(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { - idx := h.RandomCNBIndex(t, repoName, 1, 2) - mockIndexFactory. - EXPECT(). - LoadIndex(gomock.Eq(repoName), gomock.Any()). - Return(idx, nil). - AnyTimes() - - return idx -} - -func prepareLoadIndexWithErrorOnSave(t *testing.T, repoName string, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { - cnbIdx := h.RandomCNBIndex(t, repoName, 1, 2) - idx := &h.MockImageIndex{ - CNBIndex: *cnbIdx, - ErrorOnSave: true, - } - mockIndexFactory. - EXPECT(). - LoadIndex(gomock.Eq(repoName), gomock.Any()). - Return(idx, nil). - AnyTimes() - - return idx -} - -type testImage struct { - *fakes.Image - underlyingImage v1.Image -} - -func (t *testImage) UnderlyingImage() v1.Image { - return t.underlyingImage -} diff --git a/pkg/client/annotate_manifest_test.go b/pkg/client/annotate_manifest_test.go index 1353dce16c..c697795429 100644 --- a/pkg/client/annotate_manifest_test.go +++ b/pkg/client/annotate_manifest_test.go @@ -11,6 +11,7 @@ import ( "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "github.com/heroku/color" + "github.com/pkg/errors" "github.com/sclevine/spec" "github.com/sclevine/spec/report" @@ -20,7 +21,7 @@ import ( h "github.com/buildpacks/pack/testhelpers" ) -const digestStr = "sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56" +const invalidDigest = "sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56" func TestAnnotateManifest(t *testing.T) { color.Disable(true) @@ -67,9 +68,14 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }) when("#AnnotateManifest", func() { + var ( + digest name.Digest + idx imgutil.ImageIndex + indexRepoName string + ) when("index doesn't exists", func() { it.Before(func() { - prepareIndexWithoutLocallyExists(*mockIndexFactory) + mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("index not found locally")) }) it("should return an error", func() { @@ -86,21 +92,11 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { when("index exists", func() { when("no errors on save", func() { - var digest name.Digest - var idx imgutil.ImageIndex - it.Before(func() { - idx = prepareLoadIndex(t, "some/repo", *mockIndexFactory) - imgIdx, ok := idx.(*imgutil.CNBIndex) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err = name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) - - fakeImage := setUpRemoteImageForIndex(t, digest) + indexRepoName = h.NewRandomIndexRepoName() + idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, digest) fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage }) @@ -108,7 +104,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { err = subject.AnnotateManifest( context.TODO(), ManifestAnnotateOptions{ - IndexRepoName: "some/repo", + IndexRepoName: indexRepoName, RepoName: digest.Name(), OS: "some-os", }, @@ -124,7 +120,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { err = subject.AnnotateManifest( context.TODO(), ManifestAnnotateOptions{ - IndexRepoName: "some/repo", + IndexRepoName: indexRepoName, RepoName: digest.Name(), OSArch: "some-arch", }, @@ -140,7 +136,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { err = subject.AnnotateManifest( context.TODO(), ManifestAnnotateOptions{ - IndexRepoName: "some/repo", + IndexRepoName: indexRepoName, RepoName: digest.Name(), OSVariant: "some-variant", }, @@ -156,7 +152,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { err = subject.AnnotateManifest( context.TODO(), ManifestAnnotateOptions{ - IndexRepoName: "some/repo", + IndexRepoName: indexRepoName, RepoName: digest.Name(), Annotations: map[string]string{"some-key": "some-value"}, }, @@ -179,7 +175,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { err = subject.AnnotateManifest( context.TODO(), ManifestAnnotateOptions{ - IndexRepoName: "some/repo", + IndexRepoName: indexRepoName, RepoName: digest.Name(), OS: fakeOS, OSArch: fakeArch, @@ -222,61 +218,43 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { }) when("return an error", func() { - it("has no Index locally by given Name", func() { - prepareIndexWithoutLocallyExists(*mockIndexFactory) - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: "some/repo", - RepoName: "", - }, - ) - h.AssertEq(t, err.Error(), "index not found locally") - }) - it("has no image with given digest for OS", func() { - prepareLoadIndex(t, "some/repo", *mockIndexFactory) + var nonExistentDigest string - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: "some/repo", - RepoName: "busybox@" + digestStr, - OS: "some-os", - }, - ) - h.AssertNotNil(t, err) + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + idx = h.RandomCNBIndex(t, indexRepoName, 1, 2) + nonExistentDigest = "busybox@" + invalidDigest + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) }) + it("has no image with given digest for Arch", func() { - prepareLoadIndex(t, "some/repo", *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), ManifestAnnotateOptions{ - IndexRepoName: "some/repo", - RepoName: "busybox@" + digestStr, + IndexRepoName: indexRepoName, + RepoName: nonExistentDigest, OSArch: "some-arch", }, ) h.AssertNotNil(t, err) }) it("has no image with given digest for Variant", func() { - prepareLoadIndex(t, "some/repo", *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), ManifestAnnotateOptions{ - IndexRepoName: "some/repo", - RepoName: "busybox@" + digestStr, + IndexRepoName: indexRepoName, + RepoName: nonExistentDigest, OSVariant: "some-variant", }, ) h.AssertNotNil(t, err) }) it("has no image with given digest for Annotations", func() { - prepareLoadIndex(t, "some/repo", *mockIndexFactory) err = subject.AnnotateManifest( context.TODO(), ManifestAnnotateOptions{ - IndexRepoName: "some/repo", - RepoName: "busybox@" + digestStr, + IndexRepoName: indexRepoName, + RepoName: nonExistentDigest, Annotations: map[string]string{"some-key": "some-value"}, }, ) diff --git a/pkg/client/common.go b/pkg/client/common.go index 8ce0752d61..a019d416df 100644 --- a/pkg/client/common.go +++ b/pkg/client/common.go @@ -16,10 +16,10 @@ import ( "github.com/buildpacks/pack/pkg/logging" ) -func (c *Client) addManifestToIndex(ctx context.Context, indexRepoName, repoName string, index imgutil.ImageIndex) error { +func (c *Client) addManifestToIndex(ctx context.Context, repoName string, index imgutil.ImageIndex) error { imageRef, err := name.ParseReference(repoName, name.WeakValidation) if err != nil { - return fmt.Errorf("'%s' is not a valid manifest reference: %s", style.Symbol(indexRepoName), err) + return fmt.Errorf("'%s' is not a valid manifest reference: %s", style.Symbol(repoName), err) } imageToAdd, err := c.imageFetcher.Fetch(ctx, imageRef.Name(), image.FetchOptions{Daemon: false}) diff --git a/pkg/client/create_manifest.go b/pkg/client/create_manifest.go index 10a03c8656..bfa9ee6ef0 100644 --- a/pkg/client/create_manifest.go +++ b/pkg/client/create_manifest.go @@ -6,7 +6,6 @@ import ( "github.com/buildpacks/imgutil" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/pkg/errors" "github.com/buildpacks/pack/internal/style" ) @@ -33,7 +32,8 @@ func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) ops := parseOptsToIndexOptions(opts) if c.indexFactory.Exists(opts.IndexRepoName) { - return errors.New("exits in your local storage, use 'pack manifest remove' if you want to delete it") + return fmt.Errorf("manifest list '%s' already exists in local storage; use 'pack manifest remove' to "+ + "remove it before creating a new manifest list with the same name", style.Symbol(opts.IndexRepoName)) } index, err := c.indexFactory.CreateIndex(opts.IndexRepoName, ops...) @@ -42,25 +42,27 @@ func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) } for _, repoName := range opts.RepoNames { - if err = c.addManifestToIndex(ctx, opts.IndexRepoName, repoName, index); err != nil { + if err = c.addManifestToIndex(ctx, repoName, index); err != nil { return err } } - if err = index.SaveDir(); err != nil { - return fmt.Errorf("'%s' could not be saved in the local storage: %s", style.Symbol(opts.IndexRepoName), err) - } + if opts.Publish { + // push to a registry without saving a local copy + ops = append(ops, imgutil.WithPurge(true)) + if err = index.Push(ops...); err != nil { + return err + } - c.logger.Infof("successfully created index: '%s'\n", style.Symbol(opts.IndexRepoName)) - if !opts.Publish { + c.logger.Infof("Successfully pushed manifest list '%s' to registry", style.Symbol(opts.IndexRepoName)) return nil } - if err = index.Push(ops...); err != nil { - return err + if err = index.SaveDir(); err != nil { + return fmt.Errorf("manifest list '%s' could not be saved to local storage: %w", style.Symbol(opts.IndexRepoName), err) } - c.logger.Infof("successfully pushed '%s' to registry \n", style.Symbol(opts.IndexRepoName)) + c.logger.Infof("Successfully created manifest list '%s'", style.Symbol(opts.IndexRepoName)) return nil } diff --git a/pkg/client/create_manifest_test.go b/pkg/client/create_manifest_test.go index 88d9993379..05173541f6 100644 --- a/pkg/client/create_manifest_test.go +++ b/pkg/client/create_manifest_test.go @@ -71,7 +71,7 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { when("remote manifest is provided", func() { it.Before(func() { - fakeImage := setUpRemoteImageForIndex(t, nil) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, nil) fakeImageFetcher.RemoteImages["index.docker.io/library/busybox:1.36-musl"] = fakeImage }) @@ -88,7 +88,7 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { when("no errors on save", func() { it.Before(func() { - indexRepoName = newIndexRepoName() + indexRepoName = h.NewRandomIndexRepoName() indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) }) @@ -114,7 +114,7 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { when("no errors on save", func() { it.Before(func() { - indexRepoName = newIndexRepoName() + indexRepoName = h.NewRandomIndexRepoName() indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) // index stub return to check if push operation was called @@ -137,11 +137,10 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { ) h.AssertNil(t, err) - indexOnDisk := h.ReadIndexManifest(t, indexLocalPath) - h.AssertEq(t, len(indexOnDisk.Manifests), 1) - h.AssertEq(t, indexOnDisk.MediaType, types.OCIImageIndex) - + // index is not saved locally and push it to the registry + h.AssertPathDoesNotExists(t, indexLocalPath) h.AssertTrue(t, index.PushCalled) + h.AssertTrue(t, index.PurgeOption) }) }) }) @@ -158,7 +157,7 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { WithKeychain(authn.DefaultKeychain), ) - indexRepoName = newIndexRepoName() + indexRepoName = h.NewRandomIndexRepoName() indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) }) @@ -195,7 +194,7 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { when("index exists", func() { it.Before(func() { - indexRepoName = newIndexRepoName() + indexRepoName = h.NewRandomIndexRepoName() // mock the index factory to simulate the index exists mockIndexFactory.EXPECT().Exists(gomock.Eq(indexRepoName)).AnyTimes().Return(true) @@ -208,12 +207,8 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { IndexRepoName: indexRepoName, }, ) - h.AssertEq(t, err.Error(), "exits in your local storage, use 'pack manifest remove' if you want to delete it") + h.AssertError(t, err, "already exists in local storage; use 'pack manifest remove' to remove it before creating a new manifest list with the same name") }) }) }) } - -func newIndexRepoName() string { - return "test-create-index-" + h.RandString(10) -} diff --git a/pkg/client/inspect_manifest.go b/pkg/client/inspect_manifest.go index c3aa189807..3cb4e3673c 100644 --- a/pkg/client/inspect_manifest.go +++ b/pkg/client/inspect_manifest.go @@ -1,8 +1,9 @@ package client import ( + "fmt" + "github.com/buildpacks/imgutil" - "github.com/pkg/errors" ) // InspectManifest implements commands.PackClient. @@ -19,7 +20,7 @@ func (c *Client) InspectManifest(indexRepoName string) error { } if indexStr, err = index.Inspect(); err != nil { - return errors.Wrapf(err, "'%s' printing the index", indexRepoName) + return fmt.Errorf("failed to inspect manifest list '%s': %w", indexRepoName, err) } c.logger.Info(indexStr) diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index 37268ba85a..754997386f 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -1,9 +1,12 @@ package client import ( + "fmt" + "github.com/buildpacks/imgutil" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/pkg/errors" + + "github.com/buildpacks/pack/internal/style" ) type PushManifestOptions struct { @@ -30,11 +33,11 @@ func (c *Client) PushManifest(opts PushManifestOptions) (err error) { } if err = idx.Push(ops...); err != nil { - return errors.Wrapf(err, "pushing index '%s'", opts.IndexRepoName) + return fmt.Errorf("failed to push manifest list '%s': %w", style.Symbol(opts.IndexRepoName), err) } if !opts.Purge { - c.logger.Infof("successfully pushed index: '%s'\n", opts.IndexRepoName) + c.logger.Infof("Successfully pushed manifest list '%s'", style.Symbol(opts.IndexRepoName)) return nil } diff --git a/pkg/client/remove_manifest.go b/pkg/client/remove_manifest.go index a363fbced4..42d90c56ab 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/remove_manifest.go @@ -19,7 +19,7 @@ func (c *Client) DeleteManifest(ctx context.Context, names []string) (errs []err } if len(errs) == 0 { - c.logger.Info("successfully deleted indexes \n") + c.logger.Info("Successfully deleted manifest lists") } return errs } diff --git a/pkg/client/remove_manifest_test.go b/pkg/client/remove_manifest_test.go index 9bb493c6f0..0f10035da9 100644 --- a/pkg/client/remove_manifest_test.go +++ b/pkg/client/remove_manifest_test.go @@ -9,6 +9,7 @@ import ( "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" "github.com/heroku/color" + "github.com/pkg/errors" "github.com/sclevine/spec" "github.com/sclevine/spec/report" @@ -60,7 +61,7 @@ func testDeleteManifest(t *testing.T, when spec.G, it spec.S) { when("#DeleteManifest", func() { when("index doesn't exists", func() { it.Before(func() { - prepareIndexWithoutLocallyExists(*mockIndexFactory) + mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("index not found locally")) }) it("should return an error when index is already deleted", func() { errs := subject.DeleteManifest(context.TODO(), []string{"pack/none-existent-index"}) diff --git a/pkg/client/rm_manifest.go b/pkg/client/rm_manifest.go index 7f8563c85f..bf094f5f54 100644 --- a/pkg/client/rm_manifest.go +++ b/pkg/client/rm_manifest.go @@ -17,7 +17,7 @@ func (c *Client) RemoveManifest(ctx context.Context, name string, images []strin for _, image := range images { ref, err := gccrName.NewDigest(image, gccrName.WeakValidation, gccrName.Insecure) if err != nil { - errs = append(errs, fmt.Errorf(`invalid instance "%s": %v`, image, err)) + errs = append(errs, fmt.Errorf("invalid instance '%s': %w", image, err)) } if err = imgIndex.RemoveManifest(ref); err != nil { @@ -30,7 +30,7 @@ func (c *Client) RemoveManifest(ctx context.Context, name string, images []strin } if len(errs) == 0 { - fmt.Printf("Successfully removed images from index: '%s' \n", name) + c.logger.Infof("Successfully removed images from index: '%s'", name) } return errs diff --git a/pkg/client/rm_manifest_test.go b/pkg/client/rm_manifest_test.go index eed97c2479..f263b1746a 100644 --- a/pkg/client/rm_manifest_test.go +++ b/pkg/client/rm_manifest_test.go @@ -65,16 +65,10 @@ func testRemoveManifest(t *testing.T, when spec.G, it spec.S) { var idx imgutil.ImageIndex it.Before(func() { - idx = prepareLoadIndex(t, "some/repo", *mockIndexFactory) - imgIdx, ok := idx.(*imgutil.CNBIndex) - h.AssertEq(t, ok, true) - - mfest, err := imgIdx.IndexManifest() - h.AssertNil(t, err) - - digest, err = name.NewDigest("some/repo@" + mfest.Manifests[0].Digest.String()) - h.AssertNil(t, err) + idx, digest = h.RandomCNBIndexAndDigest(t, "some/repo", 1, 1) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq("some/repo"), gomock.Any()).Return(idx, nil) }) + it("should remove local index", func() { errs := subject.RemoveManifest(context.TODO(), "some/repo", []string{digest.Name()}) h.AssertEq(t, len(errs), 0) diff --git a/testhelpers/image_index.go b/testhelpers/image_index.go index d2f391042d..2c4335dfcf 100644 --- a/testhelpers/image_index.go +++ b/testhelpers/image_index.go @@ -3,32 +3,23 @@ package testhelpers import ( "encoding/json" "errors" + "fmt" "net/http" "os" "path/filepath" "testing" "github.com/buildpacks/imgutil" + "github.com/buildpacks/imgutil/fakes" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" - "github.com/google/go-containerregistry/pkg/v1/types" ) -func AssertRemoteImageIndex(t *testing.T, repoName string, mediaType types.MediaType, expectedNumberOfManifests int) { - t.Helper() - - remoteIndex := FetchImageIndexDescriptor(t, repoName) - AssertNotNil(t, remoteIndex) - remoteIndexMediaType, err := remoteIndex.MediaType() - AssertNil(t, err) - AssertEq(t, remoteIndexMediaType, mediaType) - remoteIndexManifest, err := remoteIndex.IndexManifest() - AssertNil(t, err) - AssertNotNil(t, remoteIndexManifest) - AssertEq(t, len(remoteIndexManifest.Manifests), expectedNumberOfManifests) +func NewRandomIndexRepoName() string { + return "test-index-" + RandString(10) } func AssertPathExists(t *testing.T, path string) { @@ -41,6 +32,14 @@ func AssertPathExists(t *testing.T, path string) { } } +func AssertPathDoesNotExists(t *testing.T, path string) { + t.Helper() + _, err := os.Stat(path) + if err == nil { + t.Errorf("Expected %q to not exists", path) + } +} + func FetchImageIndexDescriptor(t *testing.T, repoName string) v1.ImageIndex { t.Helper() @@ -89,11 +88,27 @@ func RandomCNBIndex(t *testing.T, repoName string, layers, count int64) *imgutil return idx } +func RandomCNBIndexAndDigest(t *testing.T, repoName string, layers, count int64) (idx imgutil.ImageIndex, digest name.Digest) { + idx = RandomCNBIndex(t, repoName, layers, count) + + imgIdx, ok := idx.(*imgutil.CNBIndex) + AssertEq(t, ok, true) + + mfest, err := imgIdx.IndexManifest() + AssertNil(t, err) + + digest, err = name.NewDigest(fmt.Sprintf("%s@%s", repoName, mfest.Manifests[0].Digest.String())) + AssertNil(t, err) + + return idx, digest +} + // MockImageIndex wraps a real CNBIndex to record if some key methods are invoke type MockImageIndex struct { imgutil.CNBIndex ErrorOnSave bool PushCalled bool + PurgeOption bool DeleteDirCalled bool } @@ -113,8 +128,16 @@ func (i *MockImageIndex) SaveDir() error { return i.CNBIndex.SaveDir() } -func (i *MockImageIndex) Push(_ ...imgutil.IndexOption) error { +func (i *MockImageIndex) Push(ops ...imgutil.IndexOption) error { + var pushOps = &imgutil.IndexOptions{} + for _, op := range ops { + if err := op(pushOps); err != nil { + return err + } + } + i.PushCalled = true + i.PurgeOption = pushOps.Purge return nil } @@ -122,3 +145,22 @@ func (i *MockImageIndex) DeleteDir() error { i.DeleteDirCalled = true return nil } + +func NewFakeWithRandomUnderlyingV1Image(t *testing.T, identifier imgutil.Identifier) *FakeWithRandomUnderlyingImage { + fakeCNBImage := fakes.NewImage("pack/image", "", identifier) + underlyingImage, err := random.Image(1024, 1) + AssertNil(t, err) + return &FakeWithRandomUnderlyingImage{ + Image: fakeCNBImage, + underlyingImage: underlyingImage, + } +} + +type FakeWithRandomUnderlyingImage struct { + *fakes.Image + underlyingImage v1.Image +} + +func (t *FakeWithRandomUnderlyingImage) UnderlyingImage() v1.Image { + return t.underlyingImage +} From 59fa667a5d58b0cd50ea443bdb541f15bab8f0cf Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Tue, 30 Apr 2024 15:51:30 -0500 Subject: [PATCH 68/79] fixing a problem with the push media-type Signed-off-by: Juan Bustamante --- pkg/client/push_manifest.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/client/push_manifest.go b/pkg/client/push_manifest.go index 754997386f..0e2656832a 100644 --- a/pkg/client/push_manifest.go +++ b/pkg/client/push_manifest.go @@ -25,6 +25,9 @@ type PushManifestOptions struct { // PushManifest implements commands.PackClient. func (c *Client) PushManifest(opts PushManifestOptions) (err error) { + if opts.Format == "" { + opts.Format = types.OCIImageIndex + } ops := parseOptions(opts) idx, err := c.indexFactory.LoadIndex(opts.IndexRepoName) From 5f1a28c43d03bac55f658453706a00e292e9b60f Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Thu, 2 May 2024 09:20:44 -0500 Subject: [PATCH 69/79] Fixing Natalie May 1 reviews comments Signed-off-by: Juan Bustamante --- internal/commands/manifest_create.go | 2 +- pkg/client/annotate_manifest_test.go | 265 ---------------- .../{add_manifest.go => manifest_add.go} | 6 +- ..._manifest_test.go => manifest_add_test.go} | 18 +- ...otate_manifest.go => manifest_annotate.go} | 17 +- pkg/client/manifest_annotate_test.go | 284 ++++++++++++++++++ ...{create_manifest.go => manifest_create.go} | 0 ...nifest_test.go => manifest_create_test.go} | 6 +- ...nspect_manifest.go => manifest_inspect.go} | 0 ...ifest_test.go => manifest_inspect_test.go} | 40 +-- .../{push_manifest.go => manifest_push.go} | 0 ...manifest_test.go => manifest_push_test.go} | 4 +- ...{remove_manifest.go => manifest_remove.go} | 2 +- ...nifest_test.go => manifest_remove_test.go} | 24 +- pkg/client/{rm_manifest.go => manifest_rm.go} | 2 +- ...m_manifest_test.go => manifest_rm_test.go} | 21 +- 16 files changed, 379 insertions(+), 312 deletions(-) delete mode 100644 pkg/client/annotate_manifest_test.go rename pkg/client/{add_manifest.go => manifest_add.go} (70%) rename pkg/client/{add_manifest_test.go => manifest_add_test.go} (87%) rename pkg/client/{annotate_manifest.go => manifest_annotate.go} (69%) create mode 100644 pkg/client/manifest_annotate_test.go rename pkg/client/{create_manifest.go => manifest_create.go} (100%) rename pkg/client/{create_manifest_test.go => manifest_create_test.go} (97%) rename pkg/client/{inspect_manifest.go => manifest_inspect.go} (100%) rename pkg/client/{inspect_manifest_test.go => manifest_inspect_test.go} (68%) rename pkg/client/{push_manifest.go => manifest_push.go} (100%) rename pkg/client/{push_manifest_test.go => manifest_push_test.go} (96%) rename pkg/client/{remove_manifest.go => manifest_remove.go} (85%) rename pkg/client/{remove_manifest_test.go => manifest_remove_test.go} (74%) rename pkg/client/{rm_manifest.go => manifest_rm.go} (91%) rename pkg/client/{rm_manifest_test.go => manifest_rm_test.go} (71%) diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index f8c9c5be3e..f1ce258e88 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -47,7 +47,7 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { cmdFlags := cmd.Flags() cmdFlags.StringVarP(&flags.format, "format", "f", "oci", "Media type to use when saving the image index. Accepted values are: oci, docker") - cmdFlags.BoolVar(&flags.insecure, "insecure", false, "When pushing the index to a registry, do not use TLS encryption or certificate verification") + cmdFlags.BoolVar(&flags.insecure, "insecure", false, "When pushing the index to a registry, do not use TLS encryption or certificate verification; use with --publish") cmdFlags.BoolVar(&flags.publish, "publish", false, "Publish directly to a registry without saving a local copy") AddHelpFlag(cmd, "create") diff --git a/pkg/client/annotate_manifest_test.go b/pkg/client/annotate_manifest_test.go deleted file mode 100644 index c697795429..0000000000 --- a/pkg/client/annotate_manifest_test.go +++ /dev/null @@ -1,265 +0,0 @@ -package client - -import ( - "bytes" - "context" - "os" - "testing" - - "github.com/buildpacks/imgutil" - "github.com/golang/mock/gomock" - "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/name" - "github.com/heroku/color" - "github.com/pkg/errors" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" - - ifakes "github.com/buildpacks/pack/internal/fakes" - "github.com/buildpacks/pack/pkg/logging" - "github.com/buildpacks/pack/pkg/testmocks" - h "github.com/buildpacks/pack/testhelpers" -) - -const invalidDigest = "sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56" - -func TestAnnotateManifest(t *testing.T) { - color.Disable(true) - defer color.Disable(false) - // TODO I think we can make this test to run in parallel - spec.Run(t, "build", testAnnotateManifest, spec.Report(report.Terminal{})) -} - -func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { - var ( - mockController *gomock.Controller - mockIndexFactory *testmocks.MockIndexFactory - fakeImageFetcher *ifakes.FakeImageFetcher - out bytes.Buffer - logger logging.Logger - subject *Client - err error - tmpDir string - ) - - it.Before(func() { - fakeImageFetcher = ifakes.NewFakeImageFetcher() - logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) - mockController = gomock.NewController(t) - mockIndexFactory = testmocks.NewMockIndexFactory(mockController) - - tmpDir, err = os.MkdirTemp("", "annotate-manifest-test") - h.AssertNil(t, err) - os.Setenv("XDG_RUNTIME_DIR", tmpDir) - - subject, err = NewClient( - WithLogger(logger), - WithFetcher(fakeImageFetcher), - WithIndexFactory(mockIndexFactory), - WithExperimental(true), - WithKeychain(authn.DefaultKeychain), - ) - h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) - h.AssertNil(t, err) - }) - it.After(func() { - mockController.Finish() - h.AssertNil(t, os.RemoveAll(tmpDir)) - }) - - when("#AnnotateManifest", func() { - var ( - digest name.Digest - idx imgutil.ImageIndex - indexRepoName string - ) - when("index doesn't exists", func() { - it.Before(func() { - mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("index not found locally")) - }) - - it("should return an error", func() { - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: "pack/index", - RepoName: "pack/image", - }, - ) - h.AssertEq(t, err.Error(), "index not found locally") - }) - }) - - when("index exists", func() { - when("no errors on save", func() { - it.Before(func() { - indexRepoName = h.NewRandomIndexRepoName() - idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) - mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) - fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, digest) - fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage - }) - - it("should set OS for given image", func() { - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: indexRepoName, - RepoName: digest.Name(), - OS: "some-os", - }, - ) - h.AssertNil(t, err) - - os, err := idx.OS(digest) - h.AssertNil(t, err) - h.AssertEq(t, os, "some-os") - }) - - it("should set Arch for given image", func() { - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: indexRepoName, - RepoName: digest.Name(), - OSArch: "some-arch", - }, - ) - h.AssertNil(t, err) - - arch, err := idx.Architecture(digest) - h.AssertNil(t, err) - h.AssertEq(t, arch, "some-arch") - }) - - it("should set Variant for given image", func() { - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: indexRepoName, - RepoName: digest.Name(), - OSVariant: "some-variant", - }, - ) - h.AssertNil(t, err) - - variant, err := idx.Variant(digest) - h.AssertNil(t, err) - h.AssertEq(t, variant, "some-variant") - }) - - it("should set Annotations for given image", func() { - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: indexRepoName, - RepoName: digest.Name(), - Annotations: map[string]string{"some-key": "some-value"}, - }, - ) - h.AssertNil(t, err) - - annos, err := idx.Annotations(digest) - h.AssertNil(t, err) - h.AssertEq(t, annos, map[string]string{"some-key": "some-value"}) - }) - - it("should save annotated index", func() { - var ( - fakeOS = "some-os" - fakeArch = "some-arch" - fakeVariant = "some-variant" - fakeAnnotations = map[string]string{"some-key": "some-value"} - ) - - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: indexRepoName, - RepoName: digest.Name(), - OS: fakeOS, - OSArch: fakeArch, - OSVariant: fakeVariant, - Annotations: fakeAnnotations, - }, - ) - h.AssertNil(t, err) - - err = idx.SaveDir() - h.AssertNil(t, err) - - os, err := idx.OS(digest) - h.AssertNil(t, err) - h.AssertEq(t, os, fakeOS) - - arch, err := idx.Architecture(digest) - h.AssertNil(t, err) - h.AssertEq(t, arch, fakeArch) - - variant, err := idx.Variant(digest) - h.AssertNil(t, err) - h.AssertEq(t, variant, fakeVariant) - - /* TODO Getters are still available in the imgutil.ImageIndex interface but we removed the Setters - osVersion, err := idx.OSVersion(digest) - h.AssertNil(t, err) - h.AssertEq(t, osVersion, fakeVersion) - - osFeatures, err := idx.OSFeatures(digest) - h.AssertNil(t, err) - h.AssertEq(t, osFeatures, []string{"some-OSFeatures", "some-OSFeatures"}) - */ - - annos, err := idx.Annotations(digest) - h.AssertNil(t, err) - h.AssertEq(t, annos, fakeAnnotations) - }) - }) - }) - - when("return an error", func() { - var nonExistentDigest string - - it.Before(func() { - indexRepoName = h.NewRandomIndexRepoName() - idx = h.RandomCNBIndex(t, indexRepoName, 1, 2) - nonExistentDigest = "busybox@" + invalidDigest - mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) - }) - - it("has no image with given digest for Arch", func() { - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: indexRepoName, - RepoName: nonExistentDigest, - OSArch: "some-arch", - }, - ) - h.AssertNotNil(t, err) - }) - it("has no image with given digest for Variant", func() { - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: indexRepoName, - RepoName: nonExistentDigest, - OSVariant: "some-variant", - }, - ) - h.AssertNotNil(t, err) - }) - it("has no image with given digest for Annotations", func() { - err = subject.AnnotateManifest( - context.TODO(), - ManifestAnnotateOptions{ - IndexRepoName: indexRepoName, - RepoName: nonExistentDigest, - Annotations: map[string]string{"some-key": "some-value"}, - }, - ) - h.AssertNotNil(t, err) - }) - }) - }) -} diff --git a/pkg/client/add_manifest.go b/pkg/client/manifest_add.go similarity index 70% rename from pkg/client/add_manifest.go rename to pkg/client/manifest_add.go index eec0010edf..6c7a41d282 100644 --- a/pkg/client/add_manifest.go +++ b/pkg/client/manifest_add.go @@ -3,6 +3,8 @@ package client import ( "context" "fmt" + + "github.com/buildpacks/pack/internal/style" ) type ManifestAddOptions struct { @@ -25,9 +27,9 @@ func (c *Client) AddManifest(ctx context.Context, opts ManifestAddOptions) (err } if err = idx.SaveDir(); err != nil { - return fmt.Errorf("'%s' could not be saved in the local storage: %s", opts.RepoName, err) + return fmt.Errorf("failed to save manifest list '%s' to local storage: %w", style.Symbol(opts.RepoName), err) } - c.logger.Infof("successfully added to index: '%s'\n", opts.RepoName) + c.logger.Infof("Successfully added image '%s' to index", style.Symbol(opts.RepoName)) return nil } diff --git a/pkg/client/add_manifest_test.go b/pkg/client/manifest_add_test.go similarity index 87% rename from pkg/client/add_manifest_test.go rename to pkg/client/manifest_add_test.go index f601806b42..d851f27ebd 100644 --- a/pkg/client/add_manifest_test.go +++ b/pkg/client/manifest_add_test.go @@ -5,8 +5,10 @@ import ( "context" "errors" "os" + "path/filepath" "testing" + "github.com/buildpacks/imgutil" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" "github.com/heroku/color" @@ -68,7 +70,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { }) when("#AddManifest", func() { - when("index doesn't exists", func() { + when("index doesn't exist", func() { it.Before(func() { mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("index not found locally")) }) @@ -92,7 +94,11 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { }) when("no errors on save", func() { + var indexPath string + it.Before(func() { + indexPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + // Initialize the Index with 2 image manifest idx := h.RandomCNBIndex(t, indexRepoName, 1, 2) mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) }) @@ -107,9 +113,13 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { ) h.AssertNil(t, err) h.AssertContains(t, out.String(), "successfully added to index: 'pack/image'") + + // We expect one more manifest to be added + index := h.ReadIndexManifest(t, indexPath) + h.AssertEq(t, len(index.Manifests), 3) }) - it("error when invalid manifest reference name is used", func() { + it("errors when invalid manifest reference name is used", func() { err = subject.AddManifest( context.TODO(), ManifestAddOptions{ @@ -121,7 +131,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertError(t, err, "is not a valid manifest reference") }) - it("error when manifest reference doesn't exist in a registry", func() { + it("errors when manifest reference doesn't exist in the registry", func() { err = subject.AddManifest( context.TODO(), ManifestAddOptions{ @@ -145,7 +155,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { AnyTimes() }) - it("error when manifest couldn't be saved locally", func() { + it("errors when the manifest list couldn't be saved locally", func() { err = subject.AddManifest( context.TODO(), ManifestAddOptions{ diff --git a/pkg/client/annotate_manifest.go b/pkg/client/manifest_annotate.go similarity index 69% rename from pkg/client/annotate_manifest.go rename to pkg/client/manifest_annotate.go index 655ed287bf..91d1d18455 100644 --- a/pkg/client/annotate_manifest.go +++ b/pkg/client/manifest_annotate.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" + "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/pkg/image" ) @@ -13,7 +14,7 @@ type ManifestAnnotateOptions struct { // Image index we want to update IndexRepoName string - // Name of image we wish to update into the image index + // Name of image within the index that we wish to update RepoName string // 'os' of the image we wish to update in the image index @@ -38,7 +39,7 @@ func (c *Client) AnnotateManifest(ctx context.Context, opts ManifestAnnotateOpti imageRef, err := name.ParseReference(opts.RepoName, name.WeakValidation) if err != nil { - return fmt.Errorf("'%s' is not a valid manifest reference: %s", opts.RepoName, err) + return fmt.Errorf("'%s' is not a valid image reference: %s", opts.RepoName, err) } imageToAnnotate, err := c.imageFetcher.Fetch(ctx, imageRef.Name(), image.FetchOptions{Daemon: false}) @@ -58,29 +59,29 @@ func (c *Client) AnnotateManifest(ctx context.Context, opts ManifestAnnotateOpti if opts.OS != "" { if err = idx.SetOS(digest, opts.OS); err != nil { - return fmt.Errorf("'%s' setting the 'os': %s", opts.RepoName, err) + return fmt.Errorf("failed to set the 'os' for '%s': %w", opts.RepoName, err) } } if opts.OSArch != "" { if err = idx.SetArchitecture(digest, opts.OSArch); err != nil { - return fmt.Errorf("'%s' setting the 'arch': %s", opts.RepoName, err) + return fmt.Errorf("failed to set the 'arch' for '%s': %w", opts.RepoName, err) } } if opts.OSVariant != "" { if err = idx.SetVariant(digest, opts.OSVariant); err != nil { - return fmt.Errorf("'%s' setting the 'os variant': %s", opts.RepoName, err) + return fmt.Errorf("failed to set the 'os variant' for '%s': %w", opts.RepoName, err) } } if len(opts.Annotations) != 0 { if err = idx.SetAnnotations(digest, opts.Annotations); err != nil { - return fmt.Errorf("'%s' updating the 'annotations': %s", opts.RepoName, err) + return fmt.Errorf("failed to set the 'annotations' for '%s': %w", opts.RepoName, err) } } if err = idx.SaveDir(); err != nil { - return fmt.Errorf("'%s' could not be saved in the local storage: %s", opts.RepoName, err) + return fmt.Errorf("failed to save manifest list '%s' to local storage: %w", opts.RepoName, err) } - c.logger.Infof("successfully annotated image '%s' in index '%s'\n", opts.RepoName, opts.IndexRepoName) + c.logger.Infof("Successfully annotated image '%s' in index '%s'", style.Symbol(opts.RepoName), style.Symbol(opts.IndexRepoName)) return nil } diff --git a/pkg/client/manifest_annotate_test.go b/pkg/client/manifest_annotate_test.go new file mode 100644 index 0000000000..3fae692959 --- /dev/null +++ b/pkg/client/manifest_annotate_test.go @@ -0,0 +1,284 @@ +package client + +import ( + "bytes" + "context" + "os" + "testing" + + "github.com/buildpacks/imgutil" + "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + "github.com/heroku/color" + "github.com/pkg/errors" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + ifakes "github.com/buildpacks/pack/internal/fakes" + "github.com/buildpacks/pack/pkg/logging" + "github.com/buildpacks/pack/pkg/testmocks" + h "github.com/buildpacks/pack/testhelpers" +) + +const invalidDigest = "sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933baf7516dd1fe0f56" + +func TestAnnotateManifest(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + spec.Run(t, "build", testAnnotateManifest, spec.Parallel(), spec.Report(report.Terminal{})) +} + +func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { + var ( + mockController *gomock.Controller + mockIndexFactory *testmocks.MockIndexFactory + fakeImageFetcher *ifakes.FakeImageFetcher + out bytes.Buffer + logger logging.Logger + subject *Client + err error + tmpDir string + ) + + it.Before(func() { + fakeImageFetcher = ifakes.NewFakeImageFetcher() + logger = logging.NewLogWithWriters(&out, &out, logging.WithVerbose()) + mockController = gomock.NewController(t) + mockIndexFactory = testmocks.NewMockIndexFactory(mockController) + + tmpDir, err = os.MkdirTemp("", "annotate-manifest-test") + h.AssertNil(t, err) + os.Setenv("XDG_RUNTIME_DIR", tmpDir) + + subject, err = NewClient( + WithLogger(logger), + WithFetcher(fakeImageFetcher), + WithIndexFactory(mockIndexFactory), + WithExperimental(true), + WithKeychain(authn.DefaultKeychain), + ) + h.AssertSameInstance(t, mockIndexFactory, subject.indexFactory) + h.AssertNil(t, err) + }) + it.After(func() { + mockController.Finish() + os.RemoveAll(tmpDir) + }) + + when("#AnnotateManifest", func() { + var ( + digest name.Digest + idx imgutil.ImageIndex + indexRepoName string + ) + when("index doesn't exist", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("index not found locally")) + }) + + it("should return an error", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: "pack/image", + }, + ) + h.AssertEq(t, err.Error(), "index not found locally") + }) + }) + + when("index exists", func() { + when("no errors on save", func() { + when("OS is given", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, digest) + fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage + }) + + it("should set OS for given image", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: digest.Name(), + OS: "some-os", + }, + ) + h.AssertNil(t, err) + + os, err := idx.OS(digest) + h.AssertNil(t, err) + h.AssertEq(t, os, "some-os") + }) + }) + when("Arch is given", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, digest) + fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage + }) + + it("should set Arch for given image", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: digest.Name(), + OSArch: "some-arch", + }, + ) + h.AssertNil(t, err) + + arch, err := idx.Architecture(digest) + h.AssertNil(t, err) + h.AssertEq(t, arch, "some-arch") + }) + }) + when("OS Variant is given", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, digest) + fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage + }) + + it("should set Variant for given image", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: digest.Name(), + OSVariant: "some-variant", + }, + ) + h.AssertNil(t, err) + + variant, err := idx.Variant(digest) + h.AssertNil(t, err) + h.AssertEq(t, variant, "some-variant") + }) + }) + when("Annotations are given", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, digest) + fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage + }) + + it("should set Annotations for given image", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: digest.Name(), + Annotations: map[string]string{"some-key": "some-value"}, + }, + ) + h.AssertNil(t, err) + + annos, err := idx.Annotations(digest) + h.AssertNil(t, err) + h.AssertEq(t, annos, map[string]string{"some-key": "some-value"}) + }) + + it("should save the annotated index", func() { + var ( + fakeOS = "some-os" + fakeArch = "some-arch" + fakeVariant = "some-variant" + fakeAnnotations = map[string]string{"some-key": "some-value"} + ) + + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: digest.Name(), + OS: fakeOS, + OSArch: fakeArch, + OSVariant: fakeVariant, + Annotations: fakeAnnotations, + }, + ) + h.AssertNil(t, err) + + err = idx.SaveDir() + h.AssertNil(t, err) + + os, err := idx.OS(digest) + h.AssertNil(t, err) + h.AssertEq(t, os, fakeOS) + + arch, err := idx.Architecture(digest) + h.AssertNil(t, err) + h.AssertEq(t, arch, fakeArch) + + variant, err := idx.Variant(digest) + h.AssertNil(t, err) + h.AssertEq(t, variant, fakeVariant) + + annos, err := idx.Annotations(digest) + h.AssertNil(t, err) + h.AssertEq(t, annos, fakeAnnotations) + }) + }) + }) + }) + + when("image does not exist with given digest", func() { + var nonExistentDigest string + + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + idx = h.RandomCNBIndex(t, indexRepoName, 1, 2) + nonExistentDigest = "busybox@" + invalidDigest + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + }) + + it("errors for Arch", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: nonExistentDigest, + OSArch: "some-arch", + }, + ) + h.AssertNotNil(t, err) + }) + it("errors for Variant", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: nonExistentDigest, + OSVariant: "some-variant", + }, + ) + h.AssertNotNil(t, err) + }) + it("errors for Annotations", func() { + err = subject.AnnotateManifest( + context.TODO(), + ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: nonExistentDigest, + Annotations: map[string]string{"some-key": "some-value"}, + }, + ) + h.AssertNotNil(t, err) + }) + }) + }) +} diff --git a/pkg/client/create_manifest.go b/pkg/client/manifest_create.go similarity index 100% rename from pkg/client/create_manifest.go rename to pkg/client/manifest_create.go diff --git a/pkg/client/create_manifest_test.go b/pkg/client/manifest_create_test.go similarity index 97% rename from pkg/client/create_manifest_test.go rename to pkg/client/manifest_create_test.go index 05173541f6..d61530a0d1 100644 --- a/pkg/client/create_manifest_test.go +++ b/pkg/client/manifest_create_test.go @@ -66,7 +66,7 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { when("#CreateManifest", func() { var indexRepoName string - when("index doesn't exists", func() { + when("index doesn't exist", func() { var indexLocalPath string when("remote manifest is provided", func() { @@ -125,7 +125,7 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { mockIndexFactory.EXPECT().CreateIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(index, nil) }) - it("creates the index adding the manifest and push it to the registry", func() { + it("creates the index adding the manifest and pushes it to the registry", func() { err = subject.CreateManifest( context.TODO(), CreateManifestOptions{ @@ -200,7 +200,7 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { mockIndexFactory.EXPECT().Exists(gomock.Eq(indexRepoName)).AnyTimes().Return(true) }) - it("return an error when index already exists", func() { + it("returns an error when index already exists", func() { err = subject.CreateManifest( context.TODO(), CreateManifestOptions{ diff --git a/pkg/client/inspect_manifest.go b/pkg/client/manifest_inspect.go similarity index 100% rename from pkg/client/inspect_manifest.go rename to pkg/client/manifest_inspect.go diff --git a/pkg/client/inspect_manifest_test.go b/pkg/client/manifest_inspect_test.go similarity index 68% rename from pkg/client/inspect_manifest_test.go rename to pkg/client/manifest_inspect_test.go index 49eaf6f5e1..34420849fd 100644 --- a/pkg/client/inspect_manifest_test.go +++ b/pkg/client/manifest_inspect_test.go @@ -2,11 +2,13 @@ package client import ( "bytes" + "encoding/json" "testing" "github.com/buildpacks/imgutil" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" + v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/heroku/color" "github.com/pkg/errors" @@ -55,50 +57,54 @@ func testInspectManifest(t *testing.T, when spec.G, it spec.S) { }) when("#InspectManifest", func() { + var indexRepoName string + when("index doesn't exits", func() { it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() mockIndexFactory. EXPECT(). - FindIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(nil, errors.New("index not found")) + FindIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(nil, errors.New("index not found")) }) it("should return an error when index not found", func() { - err = subject.InspectManifest("some/name") + err = subject.InspectManifest(indexRepoName) h.AssertEq(t, err.Error(), "index not found") }) }) when("index exists", func() { + var indexManifest *v1.IndexManifest + it.Before(func() { - setUpIndex(t, "some/name", *mockIndexFactory) + indexRepoName = h.NewRandomIndexRepoName() + idx := setUpIndex(t, indexRepoName, *mockIndexFactory) + indexManifest, err = idx.IndexManifest() + h.AssertNil(t, err) }) it("should return formatted IndexManifest", func() { - err = subject.InspectManifest("some/name") + err = subject.InspectManifest(indexRepoName) h.AssertNil(t, err) - h.AssertEq(t, stderr.String(), "") + + printedIndex := &v1.IndexManifest{} + err = json.Unmarshal(stdout.Bytes(), printedIndex) + h.AssertEq(t, indexManifest, printedIndex) }) }) }) } -func setUpIndex(t *testing.T, indexRepoName string, mockIndexFactory testmocks.MockIndexFactory) imgutil.ImageIndex { - ridx, err := random.Index(1024, 1, 2) +func setUpIndex(t *testing.T, indexRepoName string, mockIndexFactory testmocks.MockIndexFactory) v1.ImageIndex { + randomUnderlyingIndex, err := random.Index(1024, 1, 2) h.AssertNil(t, err) options := &imgutil.IndexOptions{ - BaseIndex: ridx, + BaseIndex: randomUnderlyingIndex, } idx, err := imgutil.NewCNBIndex(indexRepoName, *options) h.AssertNil(t, err) - mockIndexFactory. - EXPECT(). - FindIndex(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(idx, nil) - - return idx + mockIndexFactory.EXPECT().FindIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + return randomUnderlyingIndex } diff --git a/pkg/client/push_manifest.go b/pkg/client/manifest_push.go similarity index 100% rename from pkg/client/push_manifest.go rename to pkg/client/manifest_push.go diff --git a/pkg/client/push_manifest_test.go b/pkg/client/manifest_push_test.go similarity index 96% rename from pkg/client/push_manifest_test.go rename to pkg/client/manifest_push_test.go index 03c6ea02e8..3a0752d5dd 100644 --- a/pkg/client/push_manifest_test.go +++ b/pkg/client/manifest_push_test.go @@ -60,7 +60,7 @@ func testPushManifest(t *testing.T, when spec.G, it spec.S) { index = h.NewMockImageIndex(t, "some-index", 1, 2) mockIndexFactory.EXPECT().LoadIndex(gomock.Eq("some-index"), gomock.Any()).Return(index, nil) }) - it("push the index to the registry", func() { + it("pushes the index to the registry", func() { err = subject.PushManifest(PushManifestOptions{ IndexRepoName: "some-index", }) @@ -74,7 +74,7 @@ func testPushManifest(t *testing.T, when spec.G, it spec.S) { mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("ErrNoImageOrIndexFoundWithGivenDigest")) }) - it("error a message", func() { + it("errors with a message", func() { err = subject.PushManifest(PushManifestOptions{ IndexRepoName: "some-index", }) diff --git a/pkg/client/remove_manifest.go b/pkg/client/manifest_remove.go similarity index 85% rename from pkg/client/remove_manifest.go rename to pkg/client/manifest_remove.go index 42d90c56ab..6f1bae67ed 100644 --- a/pkg/client/remove_manifest.go +++ b/pkg/client/manifest_remove.go @@ -19,7 +19,7 @@ func (c *Client) DeleteManifest(ctx context.Context, names []string) (errs []err } if len(errs) == 0 { - c.logger.Info("Successfully deleted manifest lists") + c.logger.Info("Successfully deleted manifest list(s) from local storage") } return errs } diff --git a/pkg/client/remove_manifest_test.go b/pkg/client/manifest_remove_test.go similarity index 74% rename from pkg/client/remove_manifest_test.go rename to pkg/client/manifest_remove_test.go index 0f10035da9..739ca17500 100644 --- a/pkg/client/remove_manifest_test.go +++ b/pkg/client/manifest_remove_test.go @@ -4,8 +4,10 @@ import ( "bytes" "context" "os" + "path/filepath" "testing" + "github.com/buildpacks/imgutil" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/authn" "github.com/heroku/color" @@ -59,6 +61,11 @@ func testDeleteManifest(t *testing.T, when spec.G, it spec.S) { }) when("#DeleteManifest", func() { + var ( + indexPath string + indexRepoName string + ) + when("index doesn't exists", func() { it.Before(func() { mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("index not found locally")) @@ -70,16 +77,23 @@ func testDeleteManifest(t *testing.T, when spec.G, it spec.S) { }) when("index exists", func() { - var index *h.MockImageIndex + var idx imgutil.ImageIndex + it.Before(func() { - index = h.NewMockImageIndex(t, "some-index", 1, 2) - mockIndexFactory.EXPECT().LoadIndex(gomock.Eq("some-index"), gomock.Any()).Return(index, nil) + indexRepoName = h.NewRandomIndexRepoName() + indexPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + idx = h.RandomCNBIndex(t, indexRepoName, 1, 1) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + + // Let's write the index on disk + h.AssertNil(t, idx.SaveDir()) }) it("should delete local index", func() { - errs := subject.DeleteManifest(context.TODO(), []string{"some-index"}) + errs := subject.DeleteManifest(context.TODO(), []string{indexRepoName}) h.AssertEq(t, len(errs), 0) - h.AssertTrue(t, index.DeleteDirCalled) + h.AssertContains(t, out.String(), "Successfully deleted manifest list(s) from local storage") + h.AssertPathDoesNotExists(t, indexPath) }) }) }) diff --git a/pkg/client/rm_manifest.go b/pkg/client/manifest_rm.go similarity index 91% rename from pkg/client/rm_manifest.go rename to pkg/client/manifest_rm.go index bf094f5f54..b5717245f6 100644 --- a/pkg/client/rm_manifest.go +++ b/pkg/client/manifest_rm.go @@ -30,7 +30,7 @@ func (c *Client) RemoveManifest(ctx context.Context, name string, images []strin } if len(errs) == 0 { - c.logger.Infof("Successfully removed images from index: '%s'", name) + c.logger.Infof("Successfully removed image(s) from index: '%s'", name) } return errs diff --git a/pkg/client/rm_manifest_test.go b/pkg/client/manifest_rm_test.go similarity index 71% rename from pkg/client/rm_manifest_test.go rename to pkg/client/manifest_rm_test.go index f263b1746a..b3fe58e466 100644 --- a/pkg/client/rm_manifest_test.go +++ b/pkg/client/manifest_rm_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "os" + "path/filepath" "testing" "github.com/buildpacks/imgutil" @@ -60,18 +61,32 @@ func testRemoveManifest(t *testing.T, when spec.G, it spec.S) { }) when("#RemoveManifest", func() { + var ( + indexPath string + indexRepoName string + ) + when("index exists", func() { var digest name.Digest var idx imgutil.ImageIndex it.Before(func() { - idx, digest = h.RandomCNBIndexAndDigest(t, "some/repo", 1, 1) - mockIndexFactory.EXPECT().LoadIndex(gomock.Eq("some/repo"), gomock.Any()).Return(idx, nil) + indexRepoName = h.NewRandomIndexRepoName() + indexPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + + // Initialize the Index with 2 image manifest + idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) }) it("should remove local index", func() { - errs := subject.RemoveManifest(context.TODO(), "some/repo", []string{digest.Name()}) + errs := subject.RemoveManifest(context.TODO(), indexRepoName, []string{digest.Name()}) h.AssertEq(t, len(errs), 0) + + // We expect one manifest after removing one of them + index := h.ReadIndexManifest(t, indexPath) + h.AssertEq(t, len(index.Manifests), 1) + h.AssertNotEq(t, index.Manifests[0].Digest.String(), digest.Name()) }) }) }) From c7d4c060e96ad00101750d0e9a0997356b5b19cb Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Thu, 2 May 2024 10:14:21 -0500 Subject: [PATCH 70/79] fixing test Signed-off-by: Juan Bustamante --- pkg/client/manifest_add.go | 4 +- pkg/client/manifest_add_test.go | 123 +++++++++++++++++++------------- 2 files changed, 75 insertions(+), 52 deletions(-) diff --git a/pkg/client/manifest_add.go b/pkg/client/manifest_add.go index 6c7a41d282..e465549711 100644 --- a/pkg/client/manifest_add.go +++ b/pkg/client/manifest_add.go @@ -27,9 +27,9 @@ func (c *Client) AddManifest(ctx context.Context, opts ManifestAddOptions) (err } if err = idx.SaveDir(); err != nil { - return fmt.Errorf("failed to save manifest list '%s' to local storage: %w", style.Symbol(opts.RepoName), err) + return fmt.Errorf("failed to save manifest list %s to local storage: %w", style.Symbol(opts.RepoName), err) } - c.logger.Infof("Successfully added image '%s' to index", style.Symbol(opts.RepoName)) + c.logger.Infof("Successfully added image %s to index", style.Symbol(opts.RepoName)) return nil } diff --git a/pkg/client/manifest_add_test.go b/pkg/client/manifest_add_test.go index d851f27ebd..d3f6a4581b 100644 --- a/pkg/client/manifest_add_test.go +++ b/pkg/client/manifest_add_test.go @@ -25,7 +25,7 @@ func TestAddManifest(t *testing.T) { color.Disable(true) defer color.Disable(false) - spec.Run(t, "build", testAddManifest, spec.Parallel(), spec.Report(report.Terminal{})) + spec.Run(t, "build", testAddManifest, spec.Report(report.Terminal{})) } func testAddManifest(t *testing.T, when spec.G, it spec.S) { @@ -66,7 +66,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { }) it.After(func() { mockController.Finish() - h.AssertNil(t, os.RemoveAll(tmpDir)) + os.RemoveAll(tmpDir) }) when("#AddManifest", func() { @@ -88,64 +88,87 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { }) when("index exists", func() { - var indexRepoName string - it.Before(func() { - indexRepoName = h.NewRandomIndexRepoName() - }) + var ( + indexPath string + indexRepoName string + ) when("no errors on save", func() { - var indexPath string - - it.Before(func() { - indexPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) - // Initialize the Index with 2 image manifest - idx := h.RandomCNBIndex(t, indexRepoName, 1, 2) - mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) - }) - - it("adds the given image", func() { - err = subject.AddManifest( - context.TODO(), - ManifestAddOptions{ - IndexRepoName: indexRepoName, - RepoName: "pack/image", - }, - ) - h.AssertNil(t, err) - h.AssertContains(t, out.String(), "successfully added to index: 'pack/image'") - - // We expect one more manifest to be added - index := h.ReadIndexManifest(t, indexPath) - h.AssertEq(t, len(index.Manifests), 3) + when("valid manifest is provided", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + indexPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + // Initialize the Index with 2 image manifest + idx := h.RandomCNBIndex(t, indexRepoName, 1, 2) + h.AssertNil(t, idx.SaveDir()) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + }) + + it("adds the given image", func() { + err = subject.AddManifest( + context.TODO(), + ManifestAddOptions{ + IndexRepoName: indexRepoName, + RepoName: "pack/image", + }, + ) + h.AssertNil(t, err) + h.AssertContains(t, out.String(), "Successfully added image 'pack/image' to index") + + // We expect one more manifest to be added + index := h.ReadIndexManifest(t, indexPath) + h.AssertEq(t, len(index.Manifests), 3) + }) }) - it("errors when invalid manifest reference name is used", func() { - err = subject.AddManifest( - context.TODO(), - ManifestAddOptions{ - IndexRepoName: indexRepoName, - RepoName: "pack@@image", - }, - ) - h.AssertNotNil(t, err) - h.AssertError(t, err, "is not a valid manifest reference") + when("invalid manifest reference name is used", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + indexPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + // Initialize the Index with 2 image manifest + idx := h.RandomCNBIndex(t, indexRepoName, 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + }) + + it("errors a message", func() { + err = subject.AddManifest( + context.TODO(), + ManifestAddOptions{ + IndexRepoName: indexRepoName, + RepoName: "pack@@image", + }, + ) + h.AssertNotNil(t, err) + h.AssertError(t, err, "is not a valid manifest reference") + }) }) - it("errors when manifest reference doesn't exist in the registry", func() { - err = subject.AddManifest( - context.TODO(), - ManifestAddOptions{ - IndexRepoName: indexRepoName, - RepoName: "pack/image-not-found", - }, - ) - h.AssertNotNil(t, err) - h.AssertError(t, err, "does not exist in registry") + when("when manifest reference doesn't exist in the registry", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + indexPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + // Initialize the Index with 2 image manifest + idx := h.RandomCNBIndex(t, indexRepoName, 1, 2) + mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) + }) + + it("it errors a message", func() { + err = subject.AddManifest( + context.TODO(), + ManifestAddOptions{ + IndexRepoName: indexRepoName, + RepoName: "pack/image-not-found", + }, + ) + h.AssertNotNil(t, err) + h.AssertError(t, err, "does not exist in registry") + }) }) }) when("errors on save", func() { it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() cnbIdx := h.NewMockImageIndex(t, indexRepoName, 1, 2) cnbIdx.ErrorOnSave = true mockIndexFactory. @@ -164,7 +187,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { }, ) h.AssertNotNil(t, err) - h.AssertError(t, err, "could not be saved in the local storage") + h.AssertError(t, err, "failed to save manifest list") }) }) }) From c71db73d4a40ff301f875158ec83d5597833af96 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Thu, 2 May 2024 10:46:44 -0500 Subject: [PATCH 71/79] adding client manifest push commands tests Signed-off-by: Juan Bustamante --- internal/commands/manifest_push_test.go | 154 +++++++++++++++++------- pkg/client/client.go | 1 + 2 files changed, 113 insertions(+), 42 deletions(-) diff --git a/internal/commands/manifest_push_test.go b/internal/commands/manifest_push_test.go index dd6ded4a74..d82d67c777 100644 --- a/internal/commands/manifest_push_test.go +++ b/internal/commands/manifest_push_test.go @@ -2,17 +2,19 @@ package commands_test import ( "bytes" - "errors" "testing" "github.com/golang/mock/gomock" + "github.com/google/go-containerregistry/pkg/v1/types" "github.com/heroku/color" + "github.com/pkg/errors" "github.com/sclevine/spec" "github.com/sclevine/spec/report" "github.com/spf13/cobra" "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" h "github.com/buildpacks/pack/testhelpers" ) @@ -40,50 +42,118 @@ func testManifestPushCommand(t *testing.T, when spec.G, it spec.S) { command = commands.ManifestPush(logger, mockClient) }) - it("should annotate images with given flags", func() { - preparePushManifest(t, mockClient) - - command.SetArgs([]string{ - "some-index", - "-f", - "docker", - "--purge", - "--insecure", + + when("args are valid", func() { + var indexRepoName string + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() }) - h.AssertNil(t, command.Execute()) - }) - it("should return an error when index not exists locally", func() { - preparePushManifestWithError(t, mockClient) - command.SetArgs([]string{"some-index"}) - err := command.Execute() - h.AssertNotNil(t, err) - }) - it("should have help flag", func() { - preparePushManifest(t, mockClient) + when("index exists", func() { + when("no extra flag is provided", func() { + it.Before(func() { + mockClient.EXPECT(). + PushManifest(gomock.Eq(client.PushManifestOptions{ + IndexRepoName: indexRepoName, + Format: types.OCIImageIndex, + Insecure: false, + Purge: false, + })).Return(nil) + }) - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") - }) -} + it("should call push operation with default configuration", func() { + command.SetArgs([]string{indexRepoName}) + h.AssertNil(t, command.Execute()) + }) + }) -func preparePushManifest(_ *testing.T, mockClient *testmocks.MockPackClient) { - mockClient. - EXPECT(). - PushManifest( - gomock.Any(), - ). - AnyTimes(). - Return(nil) -} + when("--format is docker", func() { + it.Before(func() { + mockClient.EXPECT(). + PushManifest(gomock.Eq(client.PushManifestOptions{ + IndexRepoName: indexRepoName, + Format: types.DockerManifestList, + Insecure: false, + Purge: false, + })).Return(nil) + }) + + it("should call push operation with docker media type", func() { + command.SetArgs([]string{indexRepoName, "-f", "docker"}) + h.AssertNil(t, command.Execute()) + }) + }) + + when("--purge", func() { + it.Before(func() { + mockClient.EXPECT(). + PushManifest(gomock.Eq(client.PushManifestOptions{ + IndexRepoName: indexRepoName, + Format: types.OCIImageIndex, + Insecure: false, + Purge: true, + })).Return(nil) + }) + + it("should call push operation with purge enabled", func() { + command.SetArgs([]string{indexRepoName, "--purge"}) + h.AssertNil(t, command.Execute()) + }) + }) + + when("--insecure", func() { + it.Before(func() { + mockClient.EXPECT(). + PushManifest(gomock.Eq(client.PushManifestOptions{ + IndexRepoName: indexRepoName, + Format: types.OCIImageIndex, + Insecure: true, + Purge: false, + })).Return(nil) + }) -func preparePushManifestWithError(_ *testing.T, mockClient *testmocks.MockPackClient) { - mockClient. - EXPECT(). - PushManifest( - gomock.Any(), - ). - AnyTimes(). - Return(errors.New("unable to push Image")) + it("should call push operation with insecure enabled", func() { + command.SetArgs([]string{indexRepoName, "--insecure"}) + h.AssertNil(t, command.Execute()) + }) + }) + + when("--help", func() { + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) + }) + }) + + when("index doesn't exist", func() { + it.Before(func() { + mockClient. + EXPECT(). + PushManifest( + gomock.Any(), + ). + AnyTimes(). + Return(errors.New("unable to push Image")) + }) + + it("should return an error when index not exists locally", func() { + command.SetArgs([]string{"some-index"}) + err := command.Execute() + h.AssertNotNil(t, err) + }) + }) + }) + + when("args are invalid", func() { + when("--format is invalid", func() { + it("should return an error when index not exists locally", func() { + command.SetArgs([]string{"some-index", "-f", "bad-media-type"}) + err := command.Execute() + h.AssertNotNil(t, err) + h.AssertError(t, err, "invalid media type format") + }) + }) + }) } diff --git a/pkg/client/client.go b/pkg/client/client.go index e671c53599..c7e11613e2 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -38,6 +38,7 @@ import ( ) const ( + // Env variable to set the root folder for manifest list local storage xdgRuntimePath = "XDG_RUNTIME_DIR" ) From 000fb6002b44234366ba4becf8fe00d95eef8ab9 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Thu, 2 May 2024 14:21:09 -0500 Subject: [PATCH 72/79] updating to docker 26 the last code from main Signed-off-by: Juan Bustamante --- pkg/client/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/client/build.go b/pkg/client/build.go index cf391e79de..62ea009100 100644 --- a/pkg/client/build.go +++ b/pkg/client/build.go @@ -445,7 +445,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { } c.logger.Debugf("Selecting ephemeral lifecycle image %s for build", lifecycleImage.Name()) // cleanup the extended lifecycle image when done - defer c.docker.ImageRemove(context.Background(), lifecycleImage.Name(), types.ImageRemoveOptions{Force: true}) + defer c.docker.ImageRemove(context.Background(), lifecycleImage.Name(), types.RemoveOptions{Force: true}) } lifecycleOptsLifecycleImage = lifecycleImage.Name() From ca37f348bd55c7ef81522f1380b2409ed8403d13 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Thu, 2 May 2024 17:57:53 -0500 Subject: [PATCH 73/79] organizing the manifest commands unit test Signed-off-by: Juan Bustamante --- internal/commands/manifest_add_test.go | 49 ++++--- internal/commands/manifest_annotate.go | 13 +- internal/commands/manifest_annotate_test.go | 133 +++++++++++++---- internal/commands/manifest_create.go | 12 ++ internal/commands/manifest_create_test.go | 152 ++++++++++++++------ internal/commands/manifest_inspect_test.go | 43 +++--- internal/commands/manifest_remove_test.go | 79 +++++----- 7 files changed, 319 insertions(+), 162 deletions(-) diff --git a/internal/commands/manifest_add_test.go b/internal/commands/manifest_add_test.go index 0b148a1015..fcb352f7e1 100644 --- a/internal/commands/manifest_add_test.go +++ b/internal/commands/manifest_add_test.go @@ -12,6 +12,7 @@ import ( "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" h "github.com/buildpacks/pack/testhelpers" ) @@ -40,23 +41,36 @@ func testManifestAddCommand(t *testing.T, when spec.G, it spec.S) { }) when("args are valid", func() { + var indexRepoName string it.Before(func() { - prepareAddManifest(t, mockClient) + indexRepoName = h.NewRandomIndexRepoName() }) - it("should add image with current platform specs", func() { - command.SetArgs([]string{"some-index", "busybox:1.36-musl"}) - err := command.Execute() - h.AssertNil(t, err) - h.AssertEq(t, outBuf.String(), "") - }) + when("index exists", func() { + when("no extra flag is provided", func() { + it.Before(func() { + mockClient.EXPECT().AddManifest( + gomock.Any(), + gomock.Eq(client.ManifestAddOptions{ + IndexRepoName: indexRepoName, + RepoName: "busybox:1.36-musl", + }), + ).Return(nil) + }) - it("should have help flag", func() { - prepareAnnotateManifest(t, mockClient) + it("should call add manifest operation with the given arguments", func() { + command.SetArgs([]string{indexRepoName, "busybox:1.36-musl"}) + err := command.Execute() + h.AssertNil(t, err) + }) + }) - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") + when("--help", func() { + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNil(t, command.Execute()) + }) + }) }) }) @@ -69,14 +83,3 @@ func testManifestAddCommand(t *testing.T, when spec.G, it spec.S) { }) }) } - -func prepareAddManifest(_ *testing.T, mockClient *testmocks.MockPackClient) { - mockClient. - EXPECT(). - AddManifest( - gomock.Any(), - gomock.Any(), - ). - AnyTimes(). - Return(nil) -} diff --git a/internal/commands/manifest_annotate.go b/internal/commands/manifest_annotate.go index 5762ff7887..2a9346edab 100644 --- a/internal/commands/manifest_annotate.go +++ b/internal/commands/manifest_annotate.go @@ -1,6 +1,8 @@ package commands import ( + "fmt" + "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/client" @@ -16,13 +18,15 @@ type ManifestAnnotateFlags struct { // ManifestAnnotate modifies a manifest list and updates the platform information within the index for an image in the list. func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { var flags ManifestAnnotateFlags - cmd := &cobra.Command{ Use: "annotate [OPTIONS] [flags]", Args: cobra.MatchAll(cobra.ExactArgs(2), cobra.OnlyValidArgs), Short: "Add or update information about an entry in a manifest list.", Example: `pack manifest annotate my-image-index my-image:some-arch --arch some-other-arch`, RunE: logError(logger, func(cmd *cobra.Command, args []string) (err error) { + if err = validateManifestAnnotateFlags(flags); err != nil { + return err + } return pack.AnnotateManifest(cmd.Context(), client.ManifestAnnotateOptions{ IndexRepoName: args[0], RepoName: args[1], @@ -42,3 +46,10 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command { AddHelpFlag(cmd, "annotate") return cmd } + +func validateManifestAnnotateFlags(flags ManifestAnnotateFlags) error { + if flags.os == "" && flags.arch == "" && flags.variant == "" && len(flags.annotations) == 0 { + return fmt.Errorf("one of --os, --arch, or --variant is required") + } + return nil +} diff --git a/internal/commands/manifest_annotate_test.go b/internal/commands/manifest_annotate_test.go index 3da693ce48..e461edf1cb 100644 --- a/internal/commands/manifest_annotate_test.go +++ b/internal/commands/manifest_annotate_test.go @@ -12,6 +12,7 @@ import ( "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/commands/testmocks" + "github.com/buildpacks/pack/pkg/client" "github.com/buildpacks/pack/pkg/logging" h "github.com/buildpacks/pack/testhelpers" ) @@ -41,53 +42,125 @@ func testManifestAnnotateCommand(t *testing.T, when spec.G, it spec.S) { }) when("args are valid", func() { + var ( + indexRepoName string + repoName string + ) it.Before(func() { - prepareAnnotateManifest(t, mockClient) + indexRepoName = h.NewRandomIndexRepoName() + repoName = "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0" }) - it("should annotate images with given flags", func() { - command.SetArgs([]string{ - "some-index", - "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", - "--os", - "linux", - "--arch", - "arm", - "--variant", - "v6", + when("index exists", func() { + when("--os is provided", func() { + it.Before(func() { + mockClient.EXPECT(). + AnnotateManifest( + gomock.Any(), + gomock.Eq(client.ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: repoName, + OS: "linux", + Annotations: map[string]string{}, + }), + ). + Return(nil) + }) + + it("should annotate images with given flags", func() { + command.SetArgs([]string{indexRepoName, repoName, "--os", "linux"}) + h.AssertNilE(t, command.Execute()) + }) + }) + + when("--arch is provided", func() { + it.Before(func() { + mockClient.EXPECT(). + AnnotateManifest( + gomock.Any(), + gomock.Eq(client.ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: repoName, + OSArch: "amd64", + Annotations: map[string]string{}, + }), + ). + Return(nil) + }) + + it("should annotate images with given flags", func() { + command.SetArgs([]string{indexRepoName, repoName, "--arch", "amd64"}) + h.AssertNilE(t, command.Execute()) + }) + }) + + when("--variant is provided", func() { + it.Before(func() { + mockClient.EXPECT(). + AnnotateManifest( + gomock.Any(), + gomock.Eq(client.ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: repoName, + OSVariant: "V6", + Annotations: map[string]string{}, + }), + ). + Return(nil) + }) + + it("should annotate images with given flags", func() { + command.SetArgs([]string{indexRepoName, repoName, "--variant", "V6"}) + h.AssertNilE(t, command.Execute()) + }) }) - h.AssertNilE(t, command.Execute()) - }) - it("should have help flag", func() { - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) + when("--annotations are provided", func() { + it.Before(func() { + mockClient.EXPECT(). + AnnotateManifest( + gomock.Any(), + gomock.Eq(client.ManifestAnnotateOptions{ + IndexRepoName: indexRepoName, + RepoName: repoName, + Annotations: map[string]string{"foo": "bar"}, + }), + ). + Return(nil) + }) + + it("should annotate images with given flags", func() { + command.SetArgs([]string{indexRepoName, repoName, "--annotations", "foo=bar"}) + h.AssertNilE(t, command.Execute()) + }) + }) + + when("--help", func() { + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + }) + }) }) }) when("args are invalid", func() { - it("error when missing mandatory arguments", func() { + it("errors a message when no options are provided", func() { + command.SetArgs([]string{"foo", "bar"}) + h.AssertError(t, command.Execute(), "one of --os, --arch, or --variant is required") + }) + + it("errors when missing mandatory arguments", func() { command.SetArgs([]string{"some-index"}) err := command.Execute() h.AssertNotNil(t, err) h.AssertError(t, err, "accepts 2 arg(s), received 1") }) - it("should return an error when annotations defined invalidly", func() { - command.SetArgs([]string{"some-index", "busybox@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0", "--annotations", "some-key"}) + it("errors when annotations are invalid", func() { + command.SetArgs([]string{"some-index", "some-manifest", "--annotations", "some-key"}) err := command.Execute() h.AssertEq(t, err.Error(), `invalid argument "some-key" for "--annotations" flag: some-key must be formatted as key=value`) }) }) } - -func prepareAnnotateManifest(_ *testing.T, mockClient *testmocks.MockPackClient) { - mockClient. - EXPECT(). - AnnotateManifest( - gomock.Any(), - gomock.Any(), - ). - AnyTimes(). - Return(nil) -} diff --git a/internal/commands/manifest_create.go b/internal/commands/manifest_create.go index f1ce258e88..6162388fa4 100644 --- a/internal/commands/manifest_create.go +++ b/internal/commands/manifest_create.go @@ -1,6 +1,7 @@ package commands import ( + "fmt" "strings" "github.com/spf13/cobra" @@ -31,6 +32,10 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { return err } + if err = validateCreateManifestFlags(flags); err != nil { + return err + } + return pack.CreateManifest( cmd.Context(), client.CreateManifestOptions{ @@ -53,3 +58,10 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command { AddHelpFlag(cmd, "create") return cmd } + +func validateCreateManifestFlags(flags ManifestCreateFlags) error { + if flags.insecure && !flags.publish { + return fmt.Errorf("insecure flag requires the publish flag") + } + return nil +} diff --git a/internal/commands/manifest_create_test.go b/internal/commands/manifest_create_test.go index 156d0f1dee..03713e98fe 100644 --- a/internal/commands/manifest_create_test.go +++ b/internal/commands/manifest_create_test.go @@ -42,69 +42,127 @@ func testManifestCreateCommand(t *testing.T, when spec.G, it spec.S) { command = commands.ManifestCreate(logger, mockClient) }) - when("valid arguments", func() { + when("args are valid", func() { + var indexRepoName string it.Before(func() { - mockClient. - EXPECT(). - CreateManifest(gomock.Any(), - client.CreateManifestOptions{ - IndexRepoName: "some-index", - RepoNames: []string{"some-manifest"}, - Format: types.DockerManifestList, - Insecure: true, - Publish: true, - }, - ). - AnyTimes(). - Return(nil) + indexRepoName = h.NewRandomIndexRepoName() }) - it("should annotate images with given flags", func() { - command.SetArgs([]string{ - "some-index", "some-manifest", - "--format", "docker", - "--insecure", "--publish", + when("index exists", func() { + when("no extra flags are provided", func() { + it.Before(func() { + mockClient. + EXPECT(). + CreateManifest(gomock.Any(), + client.CreateManifestOptions{ + IndexRepoName: indexRepoName, + RepoNames: []string{"some-manifest"}, + Format: types.OCIImageIndex, + Insecure: false, + Publish: false, + }, + ).Return(nil) + }) + + it("should call create operation with default configuration", func() { + command.SetArgs([]string{indexRepoName, "some-manifest"}) + h.AssertNil(t, command.Execute()) + }) + }) + + when("--format is docker", func() { + it.Before(func() { + mockClient. + EXPECT(). + CreateManifest(gomock.Any(), + client.CreateManifestOptions{ + IndexRepoName: indexRepoName, + RepoNames: []string{"some-manifest"}, + Format: types.DockerManifestList, + Insecure: false, + Publish: false, + }, + ).Return(nil) + }) + + it("should call create operation with docker media type", func() { + command.SetArgs([]string{indexRepoName, "some-manifest", "-f", "docker"}) + h.AssertNil(t, command.Execute()) + }) + }) + + when("--publish", func() { + when("--insecure", func() { + it.Before(func() { + mockClient. + EXPECT(). + CreateManifest(gomock.Any(), + client.CreateManifestOptions{ + IndexRepoName: indexRepoName, + RepoNames: []string{"some-manifest"}, + Format: types.OCIImageIndex, + Insecure: true, + Publish: true, + }, + ).Return(nil) + }) + + it("should call create operation with publish and insecure", func() { + command.SetArgs([]string{indexRepoName, "some-manifest", "--publish", "--insecure"}) + h.AssertNil(t, command.Execute()) + }) + }) + + when("no --insecure", func() { + it.Before(func() { + mockClient. + EXPECT(). + CreateManifest(gomock.Any(), + client.CreateManifestOptions{ + IndexRepoName: indexRepoName, + RepoNames: []string{"some-manifest"}, + Format: types.OCIImageIndex, + Insecure: false, + Publish: true, + }, + ).Return(nil) + }) + + it("should call create operation with publish", func() { + command.SetArgs([]string{indexRepoName, "some-manifest", "--publish"}) + h.AssertNil(t, command.Execute()) + }) + }) + }) + + when("--help", func() { + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) }) - h.AssertNil(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") }) }) when("invalid arguments", func() { + when("--insecure is used without publish", func() { + it("errors a message", func() { + command.SetArgs([]string{"something", "some-manifest", "--insecure"}) + h.AssertError(t, command.Execute(), "insecure flag requires the publish flag") + }) + }) + when("invalid media type", func() { var format string it.Before(func() { format = "invalid" - mockClient. - EXPECT(). - CreateManifest(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(nil) }) - it("error a message", func() { - command.SetArgs([]string{ - "some-index", "some-manifest", - "--format", format, - }) + it("errors a message", func() { + command.SetArgs([]string{"some-index", "some-manifest", "--format", format}) h.AssertNotNil(t, command.Execute()) }) }) }) - - when("help is invoke", func() { - it.Before(func() { - mockClient. - EXPECT(). - CreateManifest(gomock.Any(), gomock.Any()). - AnyTimes(). - Return(nil) - }) - - it("should have help flag", func() { - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") - }) - }) } diff --git a/internal/commands/manifest_inspect_test.go b/internal/commands/manifest_inspect_test.go index d99f8df094..ddb63f783d 100644 --- a/internal/commands/manifest_inspect_test.go +++ b/internal/commands/manifest_inspect_test.go @@ -36,31 +36,34 @@ func testManifestInspectCommand(t *testing.T, when spec.G, it spec.S) { logger = logging.NewLogWithWriters(&outBuf, &outBuf) mockController = gomock.NewController(t) mockClient = testmocks.NewMockPackClient(mockController) - command = commands.ManifestInspect(logger, mockClient) }) - it("should annotate images with given flags", func() { - prepareInspectManifest(t, mockClient) - command.SetArgs([]string{ - "some-index", + when("args are valid", func() { + var indexRepoName string + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() }) - h.AssertNil(t, command.Execute()) - }) - it("should have help flag", func() { - prepareInspectManifest(t, mockClient) + when("index exists", func() { + when("no extra flags are provided", func() { + it.Before(func() { + mockClient.EXPECT().InspectManifest(indexRepoName).Return(nil) + }) - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") - }) -} + it("should call inspect operation with the given index repo name", func() { + command.SetArgs([]string{indexRepoName}) + h.AssertNil(t, command.Execute()) + }) + }) -func prepareInspectManifest(t *testing.T, mockClient *testmocks.MockPackClient) { - mockClient. - EXPECT(). - InspectManifest(gomock.Any()). - AnyTimes(). - Return(nil) + when("--help", func() { + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNilE(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) + }) + }) + }) } diff --git a/internal/commands/manifest_remove_test.go b/internal/commands/manifest_remove_test.go index 312e584a34..0a33b7c7be 100644 --- a/internal/commands/manifest_remove_test.go +++ b/internal/commands/manifest_remove_test.go @@ -2,11 +2,11 @@ package commands_test import ( "bytes" - "errors" "testing" "github.com/golang/mock/gomock" "github.com/heroku/color" + "github.com/pkg/errors" "github.com/sclevine/spec" "github.com/sclevine/spec/report" "github.com/spf13/cobra" @@ -37,53 +37,50 @@ func testManifestDeleteCommand(t *testing.T, when spec.G, it spec.S) { logger = logging.NewLogWithWriters(&outBuf, &outBuf) mockController = gomock.NewController(t) mockClient = testmocks.NewMockPackClient(mockController) - command = commands.ManifestDelete(logger, mockClient) }) - it("should delete index", func() { - prepareDeleteManifest(t, mockClient) - command.SetArgs([]string{ - "some-index", + when("args are valid", func() { + var indexRepoName string + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() }) - h.AssertNil(t, command.Execute()) - }) - it("should have help flag", func() { - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") - }) - it("should return an error", func() { - prepareDeleteManifest(t, mockClient) - command.SetArgs([]string{"some-index"}) - err := command.Execute() - h.AssertNil(t, err) + when("index exists", func() { + when("no extra flags are provided", func() { + it.Before(func() { + mockClient.EXPECT().DeleteManifest( + gomock.Any(), + gomock.Eq([]string{indexRepoName}), + ).Return(nil) + }) - err = command.Execute() - h.AssertNotNil(t, err) - }) -} + it("should delete index", func() { + command.SetArgs([]string{indexRepoName}) + h.AssertNil(t, command.Execute()) + }) + }) -func prepareDeleteManifest(t *testing.T, mockClient *testmocks.MockPackClient) { - mockClient. - EXPECT(). - DeleteManifest( - gomock.Any(), - gomock.Any(), - ). - AnyTimes(). - After( - mockClient. - EXPECT(). - DeleteManifest( - gomock.Any(), + when("--help", func() { + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNil(t, command.Execute()) + }) + }) + }) + + when("index does not exist", func() { + it.Before(func() { + mockClient.EXPECT().DeleteManifest( gomock.Any(), - ). - Times(1). - Return(nil), - ). - Return([]error{ - errors.New("image index doesn't exists"), + gomock.Eq([]string{"some-none-existent-index"}), + ).Return([]error{errors.New("image index doesn't exists")}) + }) + + it("should return an error", func() { + command.SetArgs([]string{"some-none-existent-index"}) + h.AssertNotNil(t, command.Execute()) + }) }) + }) } From 72ae7f2da4352af23b2fc698637cb22f18d592f4 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Thu, 2 May 2024 19:55:51 -0500 Subject: [PATCH 74/79] re-org the rm manifest tests Signed-off-by: Juan Bustamante --- internal/commands/manifest_rm_test.go | 91 ++++++++++++++------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/internal/commands/manifest_rm_test.go b/internal/commands/manifest_rm_test.go index 15cafd8c71..9e4927c650 100644 --- a/internal/commands/manifest_rm_test.go +++ b/internal/commands/manifest_rm_test.go @@ -37,56 +37,59 @@ func testManifestRemoveCommand(t *testing.T, when spec.G, it spec.S) { logger = logging.NewLogWithWriters(&outBuf, &outBuf) mockController = gomock.NewController(t) mockClient = testmocks.NewMockPackClient(mockController) - command = commands.ManifestRemove(logger, mockClient) }) - it("should remove index", func() { - prepareRemoveManifest(t, mockClient) - - command.SetArgs([]string{ - "some-index", - "some-image", + when("args are valid", func() { + var indexRepoName string + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() }) - h.AssertNil(t, command.Execute()) - }) - it("should return an error", func() { - prepareRemoveManifest(t, mockClient) - command.SetArgs([]string{"some-index", "some-image"}) - err := command.Execute() - h.AssertNil(t, err) + when("index exists", func() { + when("no extra flags are provided", func() { + it.Before(func() { + mockClient.EXPECT().RemoveManifest( + gomock.Any(), + gomock.Eq(indexRepoName), + gomock.Eq([]string{"some-image"}), + ).Return(nil) + }) + it("should remove index", func() { + command.SetArgs([]string{indexRepoName, "some-image"}) + h.AssertNil(t, command.Execute()) + }) + }) - err = command.Execute() - h.AssertNotNil(t, err) - }) - it("should have help flag", func() { - command.SetArgs([]string{"--help"}) - h.AssertNilE(t, command.Execute()) - h.AssertEq(t, outBuf.String(), "") - }) -} + when("--help", func() { + it("should have help flag", func() { + command.SetArgs([]string{"--help"}) + h.AssertNil(t, command.Execute()) + h.AssertEq(t, outBuf.String(), "") + }) + }) + }) -func prepareRemoveManifest(t *testing.T, mockClient *testmocks.MockPackClient) { - mockClient. - EXPECT(). - RemoveManifest( - gomock.Any(), - gomock.Any(), - gomock.Any(), - ). - AnyTimes(). - After( - mockClient. - EXPECT(). - RemoveManifest( + when("index does not exist", func() { + it.Before(func() { + mockClient.EXPECT().RemoveManifest( gomock.Any(), - gomock.Any(), - gomock.Any(), - ). - Times(1). - Return(nil), - ). - Return([]error{ - errors.New("image doesn't exists"), + gomock.Eq(indexRepoName), + gomock.Eq([]string{"some-image"}), + ).Return([]error{errors.New("image index doesn't exists")}) + }) + it("should return an error", func() { + command.SetArgs([]string{indexRepoName, "some-image"}) + h.AssertNotNil(t, command.Execute()) + }) + }) + }) + + when("args are invalid", func() { + it("errors when missing mandatory arguments", func() { + command.SetArgs([]string{"some-index"}) + err := command.Execute() + h.AssertNotNil(t, err) + h.AssertError(t, err, "requires at least 2 arg(s), only received 1") }) + }) } From 12cbadf58b17f7605d4520ba7e64cbe93091aaa4 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Fri, 3 May 2024 15:08:17 -0500 Subject: [PATCH 75/79] simplifying the error handling on manifest delete/remove command Signed-off-by: Juan Bustamante --- internal/commands/commands.go | 4 +-- internal/commands/manifest_remove.go | 29 +++---------------- internal/commands/manifest_remove_test.go | 4 +-- internal/commands/manifest_rm.go | 5 +++- internal/commands/manifest_rm_test.go | 4 +-- .../commands/testmocks/mock_pack_client.go | 20 ++++++------- pkg/client/manifest_remove.go | 15 +++++----- pkg/client/manifest_remove_test.go | 9 +++--- pkg/client/manifest_rm.go | 18 +++++++----- pkg/client/manifest_rm_test.go | 5 ++-- 10 files changed, 45 insertions(+), 68 deletions(-) diff --git a/internal/commands/commands.go b/internal/commands/commands.go index fcc3bfaf68..734344dba0 100644 --- a/internal/commands/commands.go +++ b/internal/commands/commands.go @@ -36,8 +36,8 @@ type PackClient interface { CreateManifest(ctx context.Context, opts client.CreateManifestOptions) error AnnotateManifest(ctx context.Context, opts client.ManifestAnnotateOptions) error AddManifest(ctx context.Context, opts client.ManifestAddOptions) error - DeleteManifest(ctx context.Context, name []string) []error - RemoveManifest(ctx context.Context, name string, images []string) []error + DeleteManifest(name []string) error + RemoveManifest(name string, images []string) error PushManifest(client.PushManifestOptions) error InspectManifest(string) error } diff --git a/internal/commands/manifest_remove.go b/internal/commands/manifest_remove.go index 34d0cf4cf4..590bb84e33 100644 --- a/internal/commands/manifest_remove.go +++ b/internal/commands/manifest_remove.go @@ -1,7 +1,6 @@ package commands import ( - "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/buildpacks/pack/pkg/logging" @@ -15,33 +14,13 @@ func ManifestDelete(logger logging.Logger, pack PackClient) *cobra.Command { Short: "Remove one or more manifest lists from local storage", Example: `pack manifest remove my-image-index`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - return NewErrors(pack.DeleteManifest(cmd.Context(), args)).Error() + if err := pack.DeleteManifest(args); err != nil { + return err + } + return nil }), } AddHelpFlag(cmd, "remove") return cmd } - -type Errors struct { - errs []error -} - -func NewErrors(errs []error) Errors { - return Errors{ - errs: errs, - } -} - -func (e Errors) Error() error { - var errMsg string - if len(e.errs) == 0 { - return nil - } - - for _, err := range e.errs { - errMsg += err.Error() - } - - return errors.New(errMsg) -} diff --git a/internal/commands/manifest_remove_test.go b/internal/commands/manifest_remove_test.go index 0a33b7c7be..4b0bd61b51 100644 --- a/internal/commands/manifest_remove_test.go +++ b/internal/commands/manifest_remove_test.go @@ -50,7 +50,6 @@ func testManifestDeleteCommand(t *testing.T, when spec.G, it spec.S) { when("no extra flags are provided", func() { it.Before(func() { mockClient.EXPECT().DeleteManifest( - gomock.Any(), gomock.Eq([]string{indexRepoName}), ).Return(nil) }) @@ -72,9 +71,8 @@ func testManifestDeleteCommand(t *testing.T, when spec.G, it spec.S) { when("index does not exist", func() { it.Before(func() { mockClient.EXPECT().DeleteManifest( - gomock.Any(), gomock.Eq([]string{"some-none-existent-index"}), - ).Return([]error{errors.New("image index doesn't exists")}) + ).Return(errors.New("image index doesn't exists")) }) it("should return an error", func() { diff --git a/internal/commands/manifest_rm.go b/internal/commands/manifest_rm.go index 383f7660e4..115d123a79 100644 --- a/internal/commands/manifest_rm.go +++ b/internal/commands/manifest_rm.go @@ -17,7 +17,10 @@ func ManifestRemove(logger logging.Logger, pack PackClient) *cobra.Command { Users must pass the digest of the image in order to delete it from the index. To discard __all__ the images in an index and the index itself, use 'manifest delete'.`, RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - return NewErrors(pack.RemoveManifest(cmd.Context(), args[0], args[1:])).Error() + if err := pack.RemoveManifest(args[0], args[1:]); err != nil { + return err + } + return nil }), } diff --git a/internal/commands/manifest_rm_test.go b/internal/commands/manifest_rm_test.go index 9e4927c650..615f3daed6 100644 --- a/internal/commands/manifest_rm_test.go +++ b/internal/commands/manifest_rm_test.go @@ -49,7 +49,6 @@ func testManifestRemoveCommand(t *testing.T, when spec.G, it spec.S) { when("no extra flags are provided", func() { it.Before(func() { mockClient.EXPECT().RemoveManifest( - gomock.Any(), gomock.Eq(indexRepoName), gomock.Eq([]string{"some-image"}), ).Return(nil) @@ -72,10 +71,9 @@ func testManifestRemoveCommand(t *testing.T, when spec.G, it spec.S) { when("index does not exist", func() { it.Before(func() { mockClient.EXPECT().RemoveManifest( - gomock.Any(), gomock.Eq(indexRepoName), gomock.Eq([]string{"some-image"}), - ).Return([]error{errors.New("image index doesn't exists")}) + ).Return(errors.New("image index doesn't exists")) }) it("should return an error", func() { command.SetArgs([]string{indexRepoName, "some-image"}) diff --git a/internal/commands/testmocks/mock_pack_client.go b/internal/commands/testmocks/mock_pack_client.go index 58d7699353..981704c090 100644 --- a/internal/commands/testmocks/mock_pack_client.go +++ b/internal/commands/testmocks/mock_pack_client.go @@ -107,17 +107,17 @@ func (mr *MockPackClientMockRecorder) CreateManifest(arg0, arg1 interface{}) *go } // DeleteManifest mocks base method. -func (m *MockPackClient) DeleteManifest(arg0 context.Context, arg1 []string) []error { +func (m *MockPackClient) DeleteManifest(arg0 []string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteManifest", arg0, arg1) - ret0, _ := ret[0].([]error) + ret := m.ctrl.Call(m, "DeleteManifest", arg0) + ret0, _ := ret[0].(error) return ret0 } // DeleteManifest indicates an expected call of DeleteManifest. -func (mr *MockPackClientMockRecorder) DeleteManifest(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) DeleteManifest(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteManifest", reflect.TypeOf((*MockPackClient)(nil).DeleteManifest), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteManifest", reflect.TypeOf((*MockPackClient)(nil).DeleteManifest), arg0) } // DownloadSBOM mocks base method. @@ -312,17 +312,17 @@ func (mr *MockPackClientMockRecorder) RegisterBuildpack(arg0, arg1 interface{}) } // RemoveManifest mocks base method. -func (m *MockPackClient) RemoveManifest(arg0 context.Context, arg1 string, arg2 []string) []error { +func (m *MockPackClient) RemoveManifest(arg0 string, arg1 []string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RemoveManifest", arg0, arg1, arg2) - ret0, _ := ret[0].([]error) + ret := m.ctrl.Call(m, "RemoveManifest", arg0, arg1) + ret0, _ := ret[0].(error) return ret0 } // RemoveManifest indicates an expected call of RemoveManifest. -func (mr *MockPackClientMockRecorder) RemoveManifest(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockPackClientMockRecorder) RemoveManifest(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveManifest", reflect.TypeOf((*MockPackClient)(nil).RemoveManifest), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveManifest", reflect.TypeOf((*MockPackClient)(nil).RemoveManifest), arg0, arg1) } // YankBuildpack mocks base method. diff --git a/pkg/client/manifest_remove.go b/pkg/client/manifest_remove.go index 6f1bae67ed..41a79865d2 100644 --- a/pkg/client/manifest_remove.go +++ b/pkg/client/manifest_remove.go @@ -1,25 +1,24 @@ package client -import ( - "context" -) +import "errors" // DeleteManifest implements commands.PackClient. -func (c *Client) DeleteManifest(ctx context.Context, names []string) (errs []error) { +func (c *Client) DeleteManifest(names []string) error { + var allErrors error for _, name := range names { imgIndex, err := c.indexFactory.LoadIndex(name) if err != nil { - errs = append(errs, err) + allErrors = errors.Join(allErrors, err) continue } if err := imgIndex.DeleteDir(); err != nil { - errs = append(errs, err) + allErrors = errors.Join(allErrors, err) } } - if len(errs) == 0 { + if allErrors == nil { c.logger.Info("Successfully deleted manifest list(s) from local storage") } - return errs + return allErrors } diff --git a/pkg/client/manifest_remove_test.go b/pkg/client/manifest_remove_test.go index 739ca17500..5f31f2dfa3 100644 --- a/pkg/client/manifest_remove_test.go +++ b/pkg/client/manifest_remove_test.go @@ -2,7 +2,6 @@ package client import ( "bytes" - "context" "os" "path/filepath" "testing" @@ -71,8 +70,8 @@ func testDeleteManifest(t *testing.T, when spec.G, it spec.S) { mockIndexFactory.EXPECT().LoadIndex(gomock.Any(), gomock.Any()).Return(nil, errors.New("index not found locally")) }) it("should return an error when index is already deleted", func() { - errs := subject.DeleteManifest(context.TODO(), []string{"pack/none-existent-index"}) - h.AssertNotEq(t, len(errs), 0) + err = subject.DeleteManifest([]string{"pack/none-existent-index"}) + h.AssertNotNil(t, err) }) }) @@ -90,8 +89,8 @@ func testDeleteManifest(t *testing.T, when spec.G, it spec.S) { }) it("should delete local index", func() { - errs := subject.DeleteManifest(context.TODO(), []string{indexRepoName}) - h.AssertEq(t, len(errs), 0) + err = subject.DeleteManifest([]string{indexRepoName}) + h.AssertNil(t, err) h.AssertContains(t, out.String(), "Successfully deleted manifest list(s) from local storage") h.AssertPathDoesNotExists(t, indexPath) }) diff --git a/pkg/client/manifest_rm.go b/pkg/client/manifest_rm.go index b5717245f6..98ac8a0926 100644 --- a/pkg/client/manifest_rm.go +++ b/pkg/client/manifest_rm.go @@ -1,37 +1,39 @@ package client import ( - "context" + "errors" "fmt" gccrName "github.com/google/go-containerregistry/pkg/name" ) // RemoveManifest implements commands.PackClient. -func (c *Client) RemoveManifest(ctx context.Context, name string, images []string) (errs []error) { +func (c *Client) RemoveManifest(name string, images []string) error { + var allErrors error + imgIndex, err := c.indexFactory.LoadIndex(name) if err != nil { - return append(errs, err) + return err } for _, image := range images { ref, err := gccrName.NewDigest(image, gccrName.WeakValidation, gccrName.Insecure) if err != nil { - errs = append(errs, fmt.Errorf("invalid instance '%s': %w", image, err)) + allErrors = errors.Join(allErrors, fmt.Errorf("invalid instance '%s': %w", image, err)) } if err = imgIndex.RemoveManifest(ref); err != nil { - errs = append(errs, err) + allErrors = errors.Join(allErrors, err) } if err = imgIndex.SaveDir(); err != nil { - errs = append(errs, err) + allErrors = errors.Join(allErrors, err) } } - if len(errs) == 0 { + if allErrors == nil { c.logger.Infof("Successfully removed image(s) from index: '%s'", name) } - return errs + return allErrors } diff --git a/pkg/client/manifest_rm_test.go b/pkg/client/manifest_rm_test.go index b3fe58e466..bc683c2063 100644 --- a/pkg/client/manifest_rm_test.go +++ b/pkg/client/manifest_rm_test.go @@ -2,7 +2,6 @@ package client import ( "bytes" - "context" "os" "path/filepath" "testing" @@ -80,8 +79,8 @@ func testRemoveManifest(t *testing.T, when spec.G, it spec.S) { }) it("should remove local index", func() { - errs := subject.RemoveManifest(context.TODO(), indexRepoName, []string{digest.Name()}) - h.AssertEq(t, len(errs), 0) + err = subject.RemoveManifest(indexRepoName, []string{digest.Name()}) + h.AssertNil(t, err) // We expect one manifest after removing one of them index := h.ReadIndexManifest(t, indexPath) From b2ad4634218fd66d29ad1448fef4925b47d1079c Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Fri, 3 May 2024 16:41:51 -0500 Subject: [PATCH 76/79] adding IndexFactory test coverage Signed-off-by: Juan Bustamante --- pkg/index/index_factory_test.go | 199 ++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 pkg/index/index_factory_test.go diff --git a/pkg/index/index_factory_test.go b/pkg/index/index_factory_test.go new file mode 100644 index 0000000000..d4bfe7191b --- /dev/null +++ b/pkg/index/index_factory_test.go @@ -0,0 +1,199 @@ +package index_test + +import ( + "fmt" + "os" + "testing" + + "github.com/buildpacks/imgutil" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/heroku/color" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + + "github.com/buildpacks/pack/pkg/index" + h "github.com/buildpacks/pack/testhelpers" +) + +var dockerRegistry *h.TestRegistryConfig + +func TestIndexFactory(t *testing.T) { + color.Disable(true) + defer color.Disable(false) + + h.RequireDocker(t) + + dockerRegistry = h.RunRegistry(t) + defer dockerRegistry.StopRegistry(t) + + os.Setenv("DOCKER_CONFIG", dockerRegistry.DockerConfigDir) + spec.Run(t, "Fetcher", testIndexFactory, spec.Parallel(), spec.Report(report.Terminal{})) +} + +func testIndexFactory(t *testing.T, when spec.G, it spec.S) { + var ( + indexFactory *index.IndexFactory + imageIndex imgutil.ImageIndex + indexRepoName string + err error + tmpDir string + ) + + it.Before(func() { + tmpDir, err = os.MkdirTemp("", "index-factory-test") + h.AssertNil(t, err) + indexFactory = index.NewIndexFactory(authn.DefaultKeychain, tmpDir) + }) + + it.After(func() { + os.RemoveAll(tmpDir) + }) + + when("#CreateIndex", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + }) + + when("no options are provided", func() { + it("creates an image index", func() { + imageIndex, err = indexFactory.CreateIndex(indexRepoName) + h.AssertNil(t, err) + h.AssertNotNil(t, imageIndex) + }) + }) + }) + + when("#Exists", func() { + when("index exists on disk", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + setUpLocalIndex(t, indexFactory, indexRepoName) + }) + + it("returns true", func() { + h.AssertTrue(t, indexFactory.Exists(indexRepoName)) + }) + }) + + when("index does not exist on disk", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + }) + + it("returns false", func() { + h.AssertFalse(t, indexFactory.Exists(indexRepoName)) + }) + }) + }) + + when("#LoadIndex", func() { + when("index exists on disk", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + setUpLocalIndex(t, indexFactory, indexRepoName) + }) + + it("loads the index from disk", func() { + imageIndex, err = indexFactory.LoadIndex(indexRepoName) + h.AssertNil(t, err) + h.AssertNotNil(t, imageIndex) + }) + }) + + when("index does not exist on disk", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + }) + + it("errors a message", func() { + _, err = indexFactory.LoadIndex(indexRepoName) + h.AssertError(t, err, fmt.Sprintf("Image: '%s' not found", indexRepoName)) + }) + }) + }) + + when("#FetchIndex", func() { + when("index exists in a remote registry", func() { + var remoteIndexRepoName string + + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + remoteIndexRepoName = newTestImageIndexName("fetch-remote") + setUpRandomRemoteIndex(t, remoteIndexRepoName, 1, 1) + }) + + it("creates an index with the underlying remote index", func() { + _, err = indexFactory.FetchIndex(indexRepoName, imgutil.FromBaseIndex(remoteIndexRepoName)) + h.AssertNil(t, err) + }) + }) + + when("index does not exist in a remote registry", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + }) + + it("errors a message", func() { + _, err = indexFactory.FetchIndex(indexRepoName, imgutil.FromBaseIndex(indexRepoName)) + h.AssertNotNil(t, err) + }) + }) + }) + + when("#FindIndex", func() { + when("index exists on disk", func() { + it.Before(func() { + indexRepoName = h.NewRandomIndexRepoName() + setUpLocalIndex(t, indexFactory, indexRepoName) + }) + + it("finds the index on disk", func() { + imageIndex, err = indexFactory.FindIndex(indexRepoName) + h.AssertNil(t, err) + h.AssertNotNil(t, imageIndex) + }) + }) + + when("index exists in a remote registry", func() { + it.Before(func() { + indexRepoName = newTestImageIndexName("find-remote") + setUpRandomRemoteIndex(t, indexRepoName, 1, 1) + }) + + it("finds the index in the remote registry", func() { + imageIndex, err = indexFactory.FindIndex(indexRepoName) + h.AssertNil(t, err) + h.AssertNotNil(t, imageIndex) + }) + }) + }) +} + +func setUpLocalIndex(t *testing.T, indexFactory *index.IndexFactory, indexRepoName string) { + imageIndex, err := indexFactory.CreateIndex(indexRepoName) + h.AssertNil(t, err) + h.AssertNil(t, imageIndex.SaveDir()) +} + +func newTestImageIndexName(name string) string { + return dockerRegistry.RepoName(name + "-" + h.RandString(10)) +} + +// setUpRandomRemoteIndex creates a random image index with the provided (count) number of manifest +// each manifest will have the provided number of layers +func setUpRandomRemoteIndex(t *testing.T, repoName string, layers, count int64) v1.ImageIndex { + ref, err := name.ParseReference(repoName, name.WeakValidation) + h.AssertNil(t, err) + + randomIndex, err := random.Index(1024, layers, count) + h.AssertNil(t, err) + + err = remote.WriteIndex(ref, randomIndex, remote.WithAuthFromKeychain(authn.DefaultKeychain)) + h.AssertNil(t, err) + + return randomIndex +} From 6e7f0f29885acbd43441a8cbf8f8b7ec5ccb292b Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Mon, 6 May 2024 15:56:56 -0500 Subject: [PATCH 77/79] Fixing an error when format is not specified when creating an image index Signed-off-by: Juan Bustamante --- pkg/client/manifest_create.go | 3 ++ pkg/client/manifest_create_test.go | 46 +++++++++++++++++++++--------- pkg/index/index_factory_test.go | 4 +-- 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/pkg/client/manifest_create.go b/pkg/client/manifest_create.go index bfa9ee6ef0..81ffe3c03e 100644 --- a/pkg/client/manifest_create.go +++ b/pkg/client/manifest_create.go @@ -73,6 +73,9 @@ func parseOptsToIndexOptions(opts CreateManifestOptions) (idxOpts []imgutil.Inde imgutil.WithInsecure(), } } + if opts.Format == "" { + opts.Format = types.OCIImageIndex + } return []imgutil.IndexOption{ imgutil.WithMediaType(opts.Format), } diff --git a/pkg/client/manifest_create_test.go b/pkg/client/manifest_create_test.go index d61530a0d1..d1f65bff4d 100644 --- a/pkg/client/manifest_create_test.go +++ b/pkg/client/manifest_create_test.go @@ -92,19 +92,38 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) }) - it("creates the index adding the manifest", func() { - err = subject.CreateManifest( - context.TODO(), - CreateManifestOptions{ - IndexRepoName: indexRepoName, - RepoNames: []string{"busybox:1.36-musl"}, - Format: types.OCIImageIndex, - }, - ) - h.AssertNil(t, err) - index := h.ReadIndexManifest(t, indexLocalPath) - h.AssertEq(t, len(index.Manifests), 1) - h.AssertEq(t, index.MediaType, types.OCIImageIndex) + when("no media type is provided", func() { + it("creates the index adding the manifest", func() { + err = subject.CreateManifest( + context.TODO(), + CreateManifestOptions{ + IndexRepoName: indexRepoName, + RepoNames: []string{"busybox:1.36-musl"}, + }, + ) + h.AssertNil(t, err) + index := h.ReadIndexManifest(t, indexLocalPath) + h.AssertEq(t, len(index.Manifests), 1) + // By default uses OCI media-types + h.AssertEq(t, index.MediaType, types.OCIImageIndex) + }) + }) + + when("media type is provided", func() { + it("creates the index adding the manifest", func() { + err = subject.CreateManifest( + context.TODO(), + CreateManifestOptions{ + IndexRepoName: indexRepoName, + RepoNames: []string{"busybox:1.36-musl"}, + Format: types.DockerManifestList, + }, + ) + h.AssertNil(t, err) + index := h.ReadIndexManifest(t, indexLocalPath) + h.AssertEq(t, len(index.Manifests), 1) + h.AssertEq(t, index.MediaType, types.DockerManifestList) + }) }) }) }) @@ -131,7 +150,6 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { CreateManifestOptions{ IndexRepoName: indexRepoName, RepoNames: []string{"busybox:1.36-musl"}, - Format: types.OCIImageIndex, Publish: true, }, ) diff --git a/pkg/index/index_factory_test.go b/pkg/index/index_factory_test.go index d4bfe7191b..da3c791e46 100644 --- a/pkg/index/index_factory_test.go +++ b/pkg/index/index_factory_test.go @@ -109,7 +109,7 @@ func testIndexFactory(t *testing.T, when spec.G, it spec.S) { indexRepoName = h.NewRandomIndexRepoName() }) - it("errors a message", func() { + it("errors with a message", func() { _, err = indexFactory.LoadIndex(indexRepoName) h.AssertError(t, err, fmt.Sprintf("Image: '%s' not found", indexRepoName)) }) @@ -137,7 +137,7 @@ func testIndexFactory(t *testing.T, when spec.G, it spec.S) { indexRepoName = h.NewRandomIndexRepoName() }) - it("errors a message", func() { + it("errors with a message", func() { _, err = indexFactory.FetchIndex(indexRepoName, imgutil.FromBaseIndex(indexRepoName)) h.AssertNotNil(t, err) }) From 09a63734dbc92dbcbcde44d8090d8df0353f5a49 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Tue, 7 May 2024 08:41:21 -0500 Subject: [PATCH 78/79] bumping imgutil version to the one with image index implementation Signed-off-by: Juan Bustamante --- go.mod | 4 +--- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 02d71f171b..3301cb70e0 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/Microsoft/go-winio v0.6.2 github.com/apex/log v1.9.0 - github.com/buildpacks/imgutil v0.0.0-20240422175901-30b002586ecc + github.com/buildpacks/imgutil v0.0.0-20240507132533-9f7b96c3d09d github.com/buildpacks/lifecycle v0.19.4-0.20240416165809-82fdf23a6dbf github.com/docker/cli v26.0.1+incompatible github.com/docker/docker v26.0.1+incompatible @@ -143,5 +143,3 @@ require ( ) go 1.22 - -replace github.com/buildpacks/imgutil => github.com/husni-faiz/imgutil v0.0.0-20240430180646-2e42a193c20c diff --git a/go.sum b/go.sum index 71dd929088..7412d66dcf 100644 --- a/go.sum +++ b/go.sum @@ -91,6 +91,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/buildpacks/imgutil v0.0.0-20240507132533-9f7b96c3d09d h1:GVRuY/C8j4pjOddeeZelbKKLMMX+dYR3TlxE4L1hECU= +github.com/buildpacks/imgutil v0.0.0-20240507132533-9f7b96c3d09d/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk= github.com/buildpacks/lifecycle v0.19.4-0.20240416165809-82fdf23a6dbf h1:BN82j9sdRloVW0xQfbsF6+Bfz3F0NUzZ2LuM5UwX0s4= github.com/buildpacks/lifecycle v0.19.4-0.20240416165809-82fdf23a6dbf/go.mod h1:UbSf3hNT/QvQRBBtifDh5oMCMhIl0C8MwvoFPgjXhQE= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= @@ -221,8 +223,6 @@ github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLa github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0= github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/husni-faiz/imgutil v0.0.0-20240430180646-2e42a193c20c h1:MNq3VV23VeiV2svOaBrXYuv2ZIgdffwLtRxBiuHlPsc= -github.com/husni-faiz/imgutil v0.0.0-20240430180646-2e42a193c20c/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= From 922af8b9b8dffca57e379fda4e3efd7029db14f0 Mon Sep 17 00:00:00 2001 From: Juan Bustamante Date: Tue, 7 May 2024 16:11:31 -0500 Subject: [PATCH 79/79] Adding acceptance test for manifest commands Signed-off-by: Juan Bustamante --- acceptance/acceptance_test.go | 144 +++++++++++++++++++++++++++ acceptance/assertions/output.go | 36 +++++++ acceptance/invoke/pack.go | 4 + pkg/client/manifest_add_test.go | 2 +- pkg/client/manifest_annotate.go | 12 +-- pkg/client/manifest_annotate_test.go | 10 +- pkg/client/manifest_create.go | 6 +- pkg/client/manifest_create_test.go | 2 +- pkg/client/manifest_push.go | 4 +- testhelpers/image_index.go | 29 +++++- 10 files changed, 229 insertions(+), 20 deletions(-) diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index a6af6bce68..a218695492 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -18,10 +18,13 @@ import ( "testing" "time" + "github.com/buildpacks/imgutil" "github.com/buildpacks/lifecycle/api" dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/google/go-containerregistry/pkg/name" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/types" "github.com/pelletier/go-toml" "github.com/sclevine/spec" "github.com/sclevine/spec/report" @@ -623,6 +626,147 @@ func testWithoutSpecificBuilderRequirement( }) }) }) + + when("manifest", func() { + var ( + indexRepoName string + repoName1 string + repoName2 string + indexLocalPath string + tmpDir string + err error + ) + + it.Before(func() { + h.SkipIf(t, !pack.SupportsFeature(invoke.ManifestCommands), "pack manifest commands are available since 0.34.0") + + // local storage path + tmpDir, err = os.MkdirTemp("", "manifest-commands-test") + assert.Nil(err) + os.Setenv("XDG_RUNTIME_DIR", tmpDir) + + // manifest commands are experimental + pack.EnableExperimental() + + // used to avoid authentication issues with the local registry + os.Setenv("DOCKER_CONFIG", registryConfig.DockerConfigDir) + }) + + it.After(func() { + assert.Succeeds(os.RemoveAll(tmpDir)) + }) + + when("create", func() { + it.Before(func() { + it.Before(func() { + indexRepoName = registryConfig.RepoName(h.NewRandomIndexRepoName()) + + // Manifest 1 + repoName1 = fmt.Sprintf("%s:%s", indexRepoName, "busybox-amd64") + h.CreateRemoteImage(t, indexRepoName, "busybox-amd64", "busybox@sha256:a236a6469768c17ca1a6ac81a35fe6fbc1efd76b0dcdf5aebb1cf5f0774ee539") + + // Manifest 2 + repoName2 = fmt.Sprintf("%s:%s", indexRepoName, "busybox-arm64") + h.CreateRemoteImage(t, indexRepoName, "busybox-arm64", "busybox@sha256:0bcc1b827b855c65eaf6e031e894e682b6170160b8a676e1df7527a19d51fb1a") + }) + }) + when("--publish", func() { + it("creates and push the index to a remote registry", func() { + output := pack.RunSuccessfully("manifest", "create", "--publish", indexRepoName, repoName1, repoName2) + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulIndexPushed(indexRepoName) + h.AssertRemoteImageIndex(t, indexRepoName, types.OCIImageIndex, 2) + }) + }) + + when("no --publish", func() { + it("creates the index locally", func() { + output := pack.RunSuccessfully("manifest", "create", indexRepoName, repoName1, repoName2) + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulIndexLocallyCreated(indexRepoName) + + indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + index := h.ReadIndexManifest(t, indexLocalPath) + h.AssertEq(t, len(index.Manifests), 2) + h.AssertEq(t, index.MediaType, types.OCIImageIndex) + }) + }) + }) + + when("index is already created", func() { + var digest v1.Hash + + it.Before(func() { + indexRepoName = registryConfig.RepoName(h.NewRandomIndexRepoName()) + + // Manifest 1 + repoName1 = fmt.Sprintf("%s:%s", indexRepoName, "busybox-amd64") + image1 := h.CreateRemoteImage(t, indexRepoName, "busybox-amd64", "busybox@sha256:a236a6469768c17ca1a6ac81a35fe6fbc1efd76b0dcdf5aebb1cf5f0774ee539") + digest, err = image1.Digest() + assert.Nil(err) + + // Manifest 2 + repoName2 = fmt.Sprintf("%s:%s", indexRepoName, "busybox-arm64") + h.CreateRemoteImage(t, indexRepoName, "busybox-arm64", "busybox@sha256:0bcc1b827b855c65eaf6e031e894e682b6170160b8a676e1df7527a19d51fb1a") + + // create an index locally + pack.RunSuccessfully("manifest", "create", indexRepoName, repoName1) + indexLocalPath = filepath.Join(tmpDir, imgutil.MakeFileSafeName(indexRepoName)) + }) + + when("add", func() { + it("adds the manifest to the index", func() { + output := pack.RunSuccessfully("manifest", "add", indexRepoName, repoName2) + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulManifestAddedToIndex(repoName2) + + index := h.ReadIndexManifest(t, indexLocalPath) + h.AssertEq(t, len(index.Manifests), 2) + h.AssertEq(t, index.MediaType, types.OCIImageIndex) + }) + }) + + when("remove", func() { + it("removes the index from local storage", func() { + output := pack.RunSuccessfully("manifest", "remove", indexRepoName) + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulIndexDeleted() + + h.AssertPathDoesNotExists(t, indexLocalPath) + }) + }) + + when("annotate", func() { + it("adds annotations to the manifest in the index", func() { + output := pack.RunSuccessfully("manifest", "annotate", indexRepoName, repoName1, "--annotations", "foo=bar") + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulIndexAnnotated(repoName1, indexRepoName) + + index := h.ReadIndexManifest(t, indexLocalPath) + h.AssertEq(t, len(index.Manifests), 1) + h.AssertEq(t, len(index.Manifests[0].Annotations), 1) + }) + }) + + when("rm", func() { + it.Before(func() { + // we need to point to the manifest digest we want to delete + repoName1 = fmt.Sprintf("%s@%s", repoName1, digest.String()) + }) + + it("removes the manifest from the index", func() { + output := pack.RunSuccessfully("manifest", "rm", indexRepoName, repoName1) + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulRemoveManifestFromIndex(indexRepoName) + + index := h.ReadIndexManifest(t, indexLocalPath) + h.AssertEq(t, len(index.Manifests), 0) + }) + }) + + when("push", func() { + it("pushes the index to a remote registry", func() { + output := pack.RunSuccessfully("manifest", "push", indexRepoName) + assertions.NewOutputAssertionManager(t, output).ReportsSuccessfulIndexPushed(indexRepoName) + h.AssertRemoteImageIndex(t, indexRepoName, types.OCIImageIndex, 1) + }) + }) + }) + }) } func testAcceptance( diff --git a/acceptance/assertions/output.go b/acceptance/assertions/output.go index ed7a80af22..65f206a86e 100644 --- a/acceptance/assertions/output.go +++ b/acceptance/assertions/output.go @@ -32,6 +32,42 @@ func (o OutputAssertionManager) ReportsSuccessfulImageBuild(name string) { o.assert.ContainsF(o.output, "Successfully built image '%s'", name) } +func (o OutputAssertionManager) ReportsSuccessfulIndexLocallyCreated(name string) { + o.testObject.Helper() + + o.assert.ContainsF(o.output, "Successfully created manifest list '%s'", name) +} + +func (o OutputAssertionManager) ReportsSuccessfulIndexPushed(name string) { + o.testObject.Helper() + + o.assert.ContainsF(o.output, "Successfully pushed manifest list '%s' to registry", name) +} + +func (o OutputAssertionManager) ReportsSuccessfulManifestAddedToIndex(name string) { + o.testObject.Helper() + + o.assert.ContainsF(o.output, "Successfully added image '%s' to index", name) +} + +func (o OutputAssertionManager) ReportsSuccessfulIndexDeleted() { + o.testObject.Helper() + + o.assert.Contains(o.output, "Successfully deleted manifest list(s) from local storage") +} + +func (o OutputAssertionManager) ReportsSuccessfulIndexAnnotated(name, manifest string) { + o.testObject.Helper() + + o.assert.ContainsF(o.output, "Successfully annotated image '%s' in index '%s'", name, manifest) +} + +func (o OutputAssertionManager) ReportsSuccessfulRemoveManifestFromIndex(name string) { + o.testObject.Helper() + + o.assert.ContainsF(o.output, "Successfully removed image(s) from index: '%s'", name) +} + func (o OutputAssertionManager) ReportSuccessfulQuietBuild(name string) { o.testObject.Helper() o.testObject.Log("quiet mode") diff --git a/acceptance/invoke/pack.go b/acceptance/invoke/pack.go index 8c5379e60f..d63170d1eb 100644 --- a/acceptance/invoke/pack.go +++ b/acceptance/invoke/pack.go @@ -238,6 +238,7 @@ const ( PlatformRetries FlattenBuilderCreationV2 FixesRunImageMetadata + ManifestCommands ) var featureTests = map[Feature]func(i *PackInvoker) bool{ @@ -274,6 +275,9 @@ var featureTests = map[Feature]func(i *PackInvoker) bool{ FixesRunImageMetadata: func(i *PackInvoker) bool { return i.atLeast("v0.34.0") }, + ManifestCommands: func(i *PackInvoker) bool { + return i.atLeast("v0.34.0") + }, } func (i *PackInvoker) SupportsFeature(f Feature) bool { diff --git a/pkg/client/manifest_add_test.go b/pkg/client/manifest_add_test.go index d3f6a4581b..12b20c60db 100644 --- a/pkg/client/manifest_add_test.go +++ b/pkg/client/manifest_add_test.go @@ -61,7 +61,7 @@ func testAddManifest(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, err) // Create a remote image to be fetched when adding to the image index - fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, nil) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", nil) fakeImageFetcher.RemoteImages["index.docker.io/pack/image:latest"] = fakeImage }) it.After(func() { diff --git a/pkg/client/manifest_annotate.go b/pkg/client/manifest_annotate.go index 91d1d18455..f5d375c587 100644 --- a/pkg/client/manifest_annotate.go +++ b/pkg/client/manifest_annotate.go @@ -59,29 +59,29 @@ func (c *Client) AnnotateManifest(ctx context.Context, opts ManifestAnnotateOpti if opts.OS != "" { if err = idx.SetOS(digest, opts.OS); err != nil { - return fmt.Errorf("failed to set the 'os' for '%s': %w", opts.RepoName, err) + return fmt.Errorf("failed to set the 'os' for %s: %w", style.Symbol(opts.RepoName), err) } } if opts.OSArch != "" { if err = idx.SetArchitecture(digest, opts.OSArch); err != nil { - return fmt.Errorf("failed to set the 'arch' for '%s': %w", opts.RepoName, err) + return fmt.Errorf("failed to set the 'arch' for %s: %w", style.Symbol(opts.RepoName), err) } } if opts.OSVariant != "" { if err = idx.SetVariant(digest, opts.OSVariant); err != nil { - return fmt.Errorf("failed to set the 'os variant' for '%s': %w", opts.RepoName, err) + return fmt.Errorf("failed to set the 'os variant' for %s: %w", style.Symbol(opts.RepoName), err) } } if len(opts.Annotations) != 0 { if err = idx.SetAnnotations(digest, opts.Annotations); err != nil { - return fmt.Errorf("failed to set the 'annotations' for '%s': %w", opts.RepoName, err) + return fmt.Errorf("failed to set the 'annotations' for %s: %w", style.Symbol(opts.RepoName), err) } } if err = idx.SaveDir(); err != nil { - return fmt.Errorf("failed to save manifest list '%s' to local storage: %w", opts.RepoName, err) + return fmt.Errorf("failed to save manifest list %s to local storage: %w", style.Symbol(opts.RepoName), err) } - c.logger.Infof("Successfully annotated image '%s' in index '%s'", style.Symbol(opts.RepoName), style.Symbol(opts.IndexRepoName)) + c.logger.Infof("Successfully annotated image %s in index %s", style.Symbol(opts.RepoName), style.Symbol(opts.IndexRepoName)) return nil } diff --git a/pkg/client/manifest_annotate_test.go b/pkg/client/manifest_annotate_test.go index 3fae692959..4557accb06 100644 --- a/pkg/client/manifest_annotate_test.go +++ b/pkg/client/manifest_annotate_test.go @@ -26,7 +26,7 @@ const invalidDigest = "sha256:d4707523ce6e12afdbe9a3be5ad69027150a834870ca0933ba func TestAnnotateManifest(t *testing.T) { color.Disable(true) defer color.Disable(false) - spec.Run(t, "build", testAnnotateManifest, spec.Parallel(), spec.Report(report.Terminal{})) + spec.Run(t, "build", testAnnotateManifest, spec.Sequential(), spec.Report(report.Terminal{})) } func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { @@ -97,7 +97,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { indexRepoName = h.NewRandomIndexRepoName() idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) - fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, digest) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", digest) fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage }) @@ -122,7 +122,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { indexRepoName = h.NewRandomIndexRepoName() idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) - fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, digest) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", digest) fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage }) @@ -147,7 +147,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { indexRepoName = h.NewRandomIndexRepoName() idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) - fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, digest) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", digest) fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage }) @@ -172,7 +172,7 @@ func testAnnotateManifest(t *testing.T, when spec.G, it spec.S) { indexRepoName = h.NewRandomIndexRepoName() idx, digest = h.RandomCNBIndexAndDigest(t, indexRepoName, 1, 2) mockIndexFactory.EXPECT().LoadIndex(gomock.Eq(indexRepoName), gomock.Any()).Return(idx, nil) - fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, digest) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", digest) fakeImageFetcher.RemoteImages[digest.Name()] = fakeImage }) diff --git a/pkg/client/manifest_create.go b/pkg/client/manifest_create.go index 81ffe3c03e..9790c5fce5 100644 --- a/pkg/client/manifest_create.go +++ b/pkg/client/manifest_create.go @@ -54,15 +54,15 @@ func (c *Client) CreateManifest(ctx context.Context, opts CreateManifestOptions) return err } - c.logger.Infof("Successfully pushed manifest list '%s' to registry", style.Symbol(opts.IndexRepoName)) + c.logger.Infof("Successfully pushed manifest list %s to registry", style.Symbol(opts.IndexRepoName)) return nil } if err = index.SaveDir(); err != nil { - return fmt.Errorf("manifest list '%s' could not be saved to local storage: %w", style.Symbol(opts.IndexRepoName), err) + return fmt.Errorf("manifest list %s could not be saved to local storage: %w", style.Symbol(opts.IndexRepoName), err) } - c.logger.Infof("Successfully created manifest list '%s'", style.Symbol(opts.IndexRepoName)) + c.logger.Infof("Successfully created manifest list %s", style.Symbol(opts.IndexRepoName)) return nil } diff --git a/pkg/client/manifest_create_test.go b/pkg/client/manifest_create_test.go index d1f65bff4d..f7c724a04a 100644 --- a/pkg/client/manifest_create_test.go +++ b/pkg/client/manifest_create_test.go @@ -71,7 +71,7 @@ func testCreateManifest(t *testing.T, when spec.G, it spec.S) { when("remote manifest is provided", func() { it.Before(func() { - fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, nil) + fakeImage := h.NewFakeWithRandomUnderlyingV1Image(t, "pack/image", nil) fakeImageFetcher.RemoteImages["index.docker.io/library/busybox:1.36-musl"] = fakeImage }) diff --git a/pkg/client/manifest_push.go b/pkg/client/manifest_push.go index 0e2656832a..07defb8573 100644 --- a/pkg/client/manifest_push.go +++ b/pkg/client/manifest_push.go @@ -36,11 +36,11 @@ func (c *Client) PushManifest(opts PushManifestOptions) (err error) { } if err = idx.Push(ops...); err != nil { - return fmt.Errorf("failed to push manifest list '%s': %w", style.Symbol(opts.IndexRepoName), err) + return fmt.Errorf("failed to push manifest list %s: %w", style.Symbol(opts.IndexRepoName), err) } if !opts.Purge { - c.logger.Infof("Successfully pushed manifest list '%s'", style.Symbol(opts.IndexRepoName)) + c.logger.Infof("Successfully pushed manifest list %s to registry", style.Symbol(opts.IndexRepoName)) return nil } diff --git a/testhelpers/image_index.go b/testhelpers/image_index.go index 2c4335dfcf..29b8d155e1 100644 --- a/testhelpers/image_index.go +++ b/testhelpers/image_index.go @@ -11,11 +11,13 @@ import ( "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/fakes" + imgutilRemote "github.com/buildpacks/imgutil/remote" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/types" ) func NewRandomIndexRepoName() string { @@ -55,6 +57,29 @@ func FetchImageIndexDescriptor(t *testing.T, repoName string) v1.ImageIndex { return index } +func AssertRemoteImageIndex(t *testing.T, repoName string, mediaType types.MediaType, expectedNumberOfManifests int) { + t.Helper() + + remoteIndex := FetchImageIndexDescriptor(t, repoName) + AssertNotNil(t, remoteIndex) + remoteIndexMediaType, err := remoteIndex.MediaType() + AssertNil(t, err) + AssertEq(t, remoteIndexMediaType, mediaType) + remoteIndexManifest, err := remoteIndex.IndexManifest() + AssertNil(t, err) + AssertNotNil(t, remoteIndexManifest) + AssertEq(t, len(remoteIndexManifest.Manifests), expectedNumberOfManifests) +} + +func CreateRemoteImage(t *testing.T, repoName, tag, baseImage string) *imgutilRemote.Image { + img1RepoName := fmt.Sprintf("%s:%s", repoName, tag) + img1, err := imgutilRemote.NewImage(img1RepoName, authn.DefaultKeychain, imgutilRemote.FromBaseImage(baseImage)) + AssertNil(t, err) + err = img1.Save() + AssertNil(t, err) + return img1 +} + func ReadIndexManifest(t *testing.T, path string) *v1.IndexManifest { t.Helper() @@ -146,8 +171,8 @@ func (i *MockImageIndex) DeleteDir() error { return nil } -func NewFakeWithRandomUnderlyingV1Image(t *testing.T, identifier imgutil.Identifier) *FakeWithRandomUnderlyingImage { - fakeCNBImage := fakes.NewImage("pack/image", "", identifier) +func NewFakeWithRandomUnderlyingV1Image(t *testing.T, repoName string, identifier imgutil.Identifier) *FakeWithRandomUnderlyingImage { + fakeCNBImage := fakes.NewImage(repoName, "", identifier) underlyingImage, err := random.Image(1024, 1) AssertNil(t, err) return &FakeWithRandomUnderlyingImage{