Skip to content

Commit

Permalink
WIP: added runtime for storing Manifest List
Browse files Browse the repository at this point in the history
* added client logic for manifest cli

* added manifest push

Signed-off-by: WYGIN <[email protected]>

* WIP refactor code

Signed-off-by: WYGIN <[email protected]>

* WIP refactor code

Signed-off-by: WYGIN <[email protected]>

---------

Signed-off-by: WYGIN <[email protected]>
Signed-off-by: WYGIN <[email protected]>
Co-authored-by: WYGIN <[email protected]>
  • Loading branch information
SaiKiranSparkApps and WYGIN authored Nov 7, 2023
1 parent a89b275 commit 66b44e0
Show file tree
Hide file tree
Showing 20 changed files with 442 additions and 130 deletions.
3 changes: 1 addition & 2 deletions internal/commands/manifest_annotate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

// ManifestAnnotateFlags define flags provided to the ManifestAnnotate
type ManifestAnnotateFlags struct {
os, arch, variant, osVersion string
os, arch, variant, osVersion string
features, osFeatures, annotations []string
}

Expand Down Expand Up @@ -38,7 +38,6 @@ func ManifestAnnotate(logger logging.Logger, pack PackClient) *cobra.Command {
cmd.Flags().StringSliceVar(&flags.osFeatures, "os-features", nil, "override the os `features` of the specified image")
cmd.Flags().StringSliceVar(&flags.annotations, "annotations", nil, "set an `annotation` for the specified image")


AddHelpFlag(cmd, "annotate")
return cmd
}
8 changes: 4 additions & 4 deletions internal/commands/manifest_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// ManifestCreateFlags define flags provided to the ManifestCreate
type ManifestCreateFlags struct {
format, registry, os, arch string
format, registry, os, arch string
insecure, publish, all, amend bool
}

Expand Down Expand Up @@ -48,10 +48,10 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command {
}

id, err := pack.CreateManifest(cmd.Context(), imageIndex, manifests, client.CreateManifestOptions{
Format: flags.format,
Format: flags.format,
Registry: flags.registry,
Insecure: flags.insecure,
Publish: flags.publish,
Publish: flags.publish,
})

if err != nil {
Expand Down Expand Up @@ -89,4 +89,4 @@ func ManifestCreate(logger logging.Logger, pack PackClient) *cobra.Command {

func validateManifestCreateFlags(flags ManifestCreateFlags) error {
return nil
}
}
2 changes: 1 addition & 1 deletion internal/commands/manifest_exists.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func ManifestExists(logger logging.Logger, pack PackClient) *cobra.Command {
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
Short: "Delete one or more manifest lists from local storage",
Example: `pack manifest exists cnbs/sample-package:hello-multiarch-universe`,
Long: `Checks if a manifest list exists in local storage`,
Long: `Checks if a manifest list exists in local storage`,
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
if err := pack.ExistsManifest(cmd.Context(), args[0]); err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/manifest_exists_test.go
Original file line number Diff line number Diff line change
@@ -1 +1 @@
package commands_test
package commands_test
8 changes: 4 additions & 4 deletions internal/commands/manifest_push.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

// ManifestPushFlags define flags provided to the ManifestPush
type ManifestPushFlags struct {
format string
format string
insecure, purge, all, quite bool
}

Expand All @@ -31,9 +31,9 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command {
}

imageID, err := pack.PushManifest(cmd.Context(), args[0], client.PushManifestOptions{
Format: flags.format,
Format: flags.format,
Insecure: flags.insecure,
Purge: flags.purge,
Purge: flags.purge,
})

if err != nil {
Expand All @@ -57,4 +57,4 @@ func ManifestPush(logger logging.Logger, pack PackClient) *cobra.Command {

func parseFalgs(flags ManifestPushFlags) error {
return nil
}
}
2 changes: 1 addition & 1 deletion internal/commands/testmocks/mock_pack_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

144 changes: 144 additions & 0 deletions internal/runtime/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package runtime

import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/buildpacks/imgutil"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
)

type ImageIndex struct {
Index imgutil.Index
Instances map[string][]ManifestConfig `json:"instance"`
runtime *runtime
}

type ManifestConfig struct {
Hash v1.Hash `json:"hash"`
OS string `json:"os,omitempty"`
OSVersion string `json:"os-version,omitempty"`
Variant string `json:"variant,omitempty"`
Architecture string `json:"arch,omitempty"`
MediaType imgutil.MediaTypes `json:"mediaType,omitempty"`
Local bool `json:"local,omitempty"`
}

type PushOptions struct {
ManifestType string
All bool
Insecure bool
}

type imageIndex interface {
Save(names []string, mimeType string) (string, error)
Push(ctx context.Context, opts PushOptions) (name.Digest, error)
Add(ctx context.Context, ref name.Reference, all bool) (name.Digest, error)
Remove(digest name.Digest) error
Delete(name string) error
}

func (i *ImageIndex) Save(names []string, mimeType string) (err error) {
for _, name := range names {
ref, err := i.runtime.ParseReference(name)
if err != nil {
return err
}
writeManifest := func(path string) error {
manifest, err := json.MarshalIndent(i.Instances[ref.Identifier()], "", " ")
file, err := os.Create(path)
if err != nil {
return err
}
_, err = file.Write(manifest)
if err != nil {
return err
}
return nil
}
path := filepath.Join(i.runtime.manifestListPath, makeFilesafeName(ref.Identifier()))
if _, err := os.Stat(filepath.Join(path, makeFilesafeName(name)+".config.json")); err != nil {
fmt.Printf("overriding '%s'...", ref.Name())
if err := writeManifest(filepath.Join(path, makeFilesafeName(name)+".config.json")); err != nil {
return err
}
}
if err := writeManifest(filepath.Join(path, makeFilesafeName(name)+".config.json")); err != nil {
return err
}
i.Index.Save(path, name, i.runtime.ImageType(mimeType))
}
return nil
}

func (i *ImageIndex) Push(ctx context.Context, opts PushOptions) (digest name.Digest, err error) {
for _, manifest := range i.Index.Manifests {
for k, v := range i.Instances {
for _, m := range v {
if m.Hash.String() == manifest.Digest.String() && m.Local {
fmt.Errorf("image: '%s' is not found in registry", k)
}
}
}
}
digest, err = i.Index.Push(ctx, opts)
if err == nil {
fmt.Printf("successfully pushed ImageIndex to registry")
}
return
}

func (i *ImageIndex) Add(ctx context.Context, ref name.Reference, all bool) (digest name.Digest, err error) {
for _, v := range i.Instances[ref.Identifier()] {
var img string
if image, err := i.runtime.ParseReference(ref.Name()); err == nil {
img = strings.Split(image.Name(), ":")[0] + "@" + v.Hash.String()
}
if image, err := i.runtime.ParseDigest(ref.Name()); err == nil {
img = image.Name()
}
if img == "" {
return digest, fmt.Errorf("unable to parse the reference '%s'", ref.Name())
}
if all {
i.Index.Add(ctx, img, v.MediaType)
}
}

return ref.Context().Digest(ref.Identifier()), err
}

func (i *ImageIndex) Remove(name string) (err error) {
if ref, err := i.runtime.ParseDigest(name); err == nil {
err = i.Index.Remove(ref)
if err != nil {
return err
}
return nil
}
if ref, err := i.runtime.ParseReference(name); err == nil {
for _, v := range i.Instances[ref.Identifier()] {
name := strings.Split(ref.Name(), ":")
if ref, err := i.runtime.ParseDigest(name[0] + "@" + v.Hash.String()); err == nil {
if err := i.Index.Remove(ref); err == nil {
fmt.Printf("Successfully removed Image '%s'", ref.Name())
}
}
err = nil
}
}
return
}

func (i ImageIndex) Delete(name string) error {
if _, err := i.runtime.ParseReference(name); err != nil {
return err
}

return i.Index.Delete(name)
}
99 changes: 99 additions & 0 deletions internal/runtime/runtime.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package runtime

import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/buildpacks/imgutil"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"

"github.com/buildpacks/pack/internal/config"
)

type runtime struct {
manifestListPath string
}

func NewRuntime() (runtime, error) {
if value, ok := os.LookupEnv("XDG_RUNTIME_DIR"); ok {
return runtime{
manifestListPath: filepath.Join(value, "manifestList"),
}, nil
}
if home, err := config.PackHome(); err == nil {
return runtime{
manifestListPath: filepath.Join(home, "manifestList"),
}, nil
}
return runtime{}, fmt.Errorf("unable to runtime path")
}

func (r runtime) LookupImageIndex(name string) (index ImageIndex, err error) {
n, err := r.ParseReference(name)
if err != nil {
return index, err
}
filepath := filepath.Join(r.manifestListPath, makeFilesafeName(n.Name()))
_, err = os.Stat(filepath)
if err != nil {
return
}
manifestBytes, _ := os.ReadFile(filepath)
if err != nil {
return
}
var dockerManifest imgutil.DockerManifestList
var ociImageIndex v1.ImageIndex
if err := json.Unmarshal(manifestBytes, &dockerManifest); err != nil {
return ImageIndex{}, err
}
if err := json.Unmarshal(manifestBytes, &ociImageIndex); err != nil {
return ImageIndex{}, err
}
return ImageIndex{
docker: dockerManifest,
oci: ociImageIndex,
}, err
}

func (r runtime) ImageType(format string) (manifestType imgutil.MediaTypes) {
switch format {
case imgutil.ImageIndexTypes.OCI:
return imgutil.ImageIndexTypes.OCI
case imgutil.ImageIndexTypes.Index:
return imgutil.ImageIndexTypes.Index
default:
return imgutil.ImageIndexTypes.Docker
}
}

func (r runtime) ParseReference(image string) (ref name.Reference, err error) {
return name.ParseReference(image)
}

func (r runtime) ParseDigest(image string) (ref name.Digest, err error) {
return name.NewDigest(image)
}

func (r runtime) RemoveManifests(ctx context.Context, names []string) (err error) {
for _, name := range names {
name = makeFilesafeName(name)
if _, err := os.Stat(filepath.Join(r.manifestListPath, name, name)); err == nil {
err := os.Remove(filepath.Join(r.manifestListPath, name))
if err != nil {
errors = append(errors, err)
}
}
}
return
}

func makeFilesafeName(ref string) string {
fileName := strings.Replace(ref, ":", "-", -1)
return strings.Replace(fileName, "/", "_", -1)
}
Loading

0 comments on commit 66b44e0

Please sign in to comment.