Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test CI #1

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ad76a8f
index: add Image Index interface
husni-faiz Apr 3, 2023
aa12399
remote: index: function to create new index
husni-faiz Apr 3, 2023
91150d7
bugfix: index: Remove mutating media type to DockerManifestList
husni-faiz Apr 9, 2023
28f3b88
index: optimize: get image from descriptor
husni-faiz Apr 9, 2023
296a9a0
index: check for image architecture
husni-faiz Apr 9, 2023
8996e24
index: add default path to Save()
husni-faiz Apr 9, 2023
6634a56
index: add keychain to new index
husni-faiz Apr 9, 2023
aacdf1b
index: add image index types
husni-faiz Apr 9, 2023
111426e
indexOptions for creating image index
husni-faiz Apr 9, 2023
6e53d36
index: path and mediatype options for creating index
husni-faiz Apr 9, 2023
01d9c2d
index: add remove index method
husni-faiz Apr 9, 2023
aa2b4d7
remote: index: remove path option
husni-faiz Apr 25, 2023
9d9b146
remote: index: user docker mediatype by default
husni-faiz Apr 25, 2023
9d3ba44
remote: index: remove saving to layout
husni-faiz Apr 25, 2023
3bfc69c
remote: index: save index to registry
husni-faiz Apr 25, 2023
a4867ad
index: local: annotate and add functionality
husni-faiz May 2, 2023
0b16a72
remote/index: return error instead of calling panic
husni-faiz May 7, 2023
a0f69cf
remote/index: return a wrapped error if fetcing an image fails
husni-faiz May 7, 2023
7b02b56
local/index: return error instead of calling panic
husni-faiz May 7, 2023
515b5d8
remote/new_index: validate index name
husni-faiz May 7, 2023
ef2826b
local/index: refactor interface functions to use the index path
husni-faiz May 8, 2023
59d91ac
local/index_options: create new index with IndexManifest
husni-faiz May 8, 2023
74f6be7
remote/index_options: create new index with IndexManifest
husni-faiz May 8, 2023
2e3286b
remote/new_index: rename file
husni-faiz May 8, 2023
519f329
get IndexManifest from ggcr index
husni-faiz May 15, 2023
3c0c640
remote/index/save: return error if platform information is missing
husni-faiz May 17, 2023
da06d58
remote/index: ImageIndexTest to be used in unit tests
husni-faiz May 17, 2023
7e3a12e
remote/index_test: setup a registry for test
husni-faiz May 17, 2023
b79b4a3
remote/index_test: basic tests for remote index
husni-faiz May 17, 2023
410d68c
remote/new_index: NewIndexTest function to create a test index
husni-faiz May 17, 2023
f67e68a
remote/new_index: use index in registry if already exists
husni-faiz May 17, 2023
22d75d2
remote/index: remove ManifestSize unused function
husni-faiz May 20, 2023
e3176ca
index: add brief comments to all index functions
husni-faiz May 21, 2023
0f6f9fb
index: remove returning error when platform information is missing
husni-faiz May 21, 2023
8db0812
local/index: remove AppendManifet wrapper function
husni-faiz May 21, 2023
dcc8fb7
local/index: save indent formatted json output
husni-faiz May 26, 2023
40ae8a5
local/index save: use os module instead of layout package
husni-faiz May 27, 2023
6135128
local/index: delete method to remove index from local storage
husni-faiz May 27, 2023
8eaf547
local/index: wrap GetIndexManifest errors
husni-faiz May 28, 2023
ef13a9f
new_index: check for local index first
husni-faiz Jun 1, 2023
2e2238f
local/index: copy referenced images to same registry as index
husni-faiz Jun 1, 2023
bf70c7c
index: fix github action tests failing
husni-faiz Jun 1, 2023
e4a31d7
fix linter error: io/ioutil deprecated
husni-faiz Nov 20, 2023
2264178
Merge branch 'main' into sign
husni-faiz Nov 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package imgutil

import (
"fmt"
"strings"

"github.com/google/go-containerregistry/pkg/v1/types"
)

type ImageIndex interface {
// getters

Name() string

// modifiers
Add(repoName string) error
Remove(repoName string) error
Save(additionalNames ...string) error
}

func (t MediaTypes) IndexManifestType() types.MediaType {
switch t {
case OCITypes:
return types.OCIImageIndex
case DockerTypes:
return types.DockerManifestList
default:
return ""
}
}

type SaveIndexDiagnostic struct {
ImageIndexName string
Cause error
}

type SaveIndexError struct {
Errors []SaveIndexDiagnostic
}

func (e SaveIndexError) Error() string {
var errors []string
for _, d := range e.Errors {
errors = append(errors, fmt.Sprintf("[%s: %s]", d.ImageIndexName, d.Cause.Error()))
}
return fmt.Sprintf("failed to write image to the following tags: %s", strings.Join(errors, ","))
}
236 changes: 236 additions & 0 deletions local/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package local

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

"github.com/pkg/errors"

"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/match"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
)

type ImageIndex struct {
repoName string
path string
index v1.ImageIndex
}

// Add appends a new image manifest to the local ImageIndex/ManifestList.
// We have not implemented nested indexes yet.
// See specification for more info:
// https://github.com/opencontainers/image-spec/blob/0b40f0f367c396cc5a7d6a2e8c8842271d3d3844/image-index.md#image-index-property-descriptions
func (i *ImageIndex) Add(repoName string) error {
ref, err := name.ParseReference(repoName)
if err != nil {
return err
}

desc, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return err
}

img, err := desc.Image()
if err != nil {
return err
}

cfg, err := img.ConfigFile()

if err != nil {
return errors.Wrapf(err, "getting config file for image %q", repoName)
}
if cfg == nil {
return fmt.Errorf("missing config for image %q", repoName)
}

platform := v1.Platform{}
platform.Architecture = cfg.Architecture
platform.OS = cfg.OS

desc.Descriptor.Platform = &platform

indexRef, err := name.ParseReference(i.repoName)
if err != nil {
return err
}

// Check if the image is in the same repository as the index
// If it is in a different repository then copy the image to
// the same repository as the index
if ref.Context().Name() != indexRef.Context().Name() {
imgRefName := indexRef.Context().Name() + "@" + desc.Digest.Algorithm + ":" + desc.Digest.Hex
imgRef, err := name.ParseReference(imgRefName)
if err != nil {
return err
}

err = remote.Write(imgRef, img, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return errors.Wrapf(err, "failed to copy image '%s' to index repository", imgRef.Name())
}
}

i.index = mutate.AppendManifests(i.index, mutate.IndexAddendum{Add: img, Descriptor: desc.Descriptor})

return nil
}

// Remove method removes the specified manifest from the local index
func (i *ImageIndex) Remove(repoName string) error {
ref, err := name.ParseReference(repoName)
if err != nil {
return err
}

desc, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return err
}

i.index = mutate.RemoveManifests(i.index, match.Digests(desc.Digest))

return nil
}

// Delete method removes the specified index from the local storage
func (i *ImageIndex) Delete(additionalNames ...string) error {
_, err := name.ParseReference(i.repoName)
if err != nil {
return err
}

manifestPath := filepath.Join(i.path, makeFileSafeName(i.repoName))
err = os.Remove(manifestPath)
if err != nil {
return err
}

return nil
}

// Save stores the ImageIndex manifest information in a plain text in the ined file in JSON format.
func (i *ImageIndex) Save(additionalNames ...string) error {
indexManifest, err := i.index.IndexManifest()
if err != nil {
return err
}

rawManifest, err := json.MarshalIndent(indexManifest, "", " ")
if err != nil {
return err
}

manifestDir := filepath.Join(i.path, makeFileSafeName(i.repoName))

err = os.WriteFile(manifestDir, rawManifest, os.ModePerm)
if err != nil {
return err
}

return nil
}

// Change a reference name string into a valid file name
// Ex: cnbs/sample-package:hello-multiarch-universe
// to cnbs_sample-package-hello-multiarch-universe
func makeFileSafeName(ref string) string {
fileName := strings.ReplaceAll(ref, ":", "-")
return strings.ReplaceAll(fileName, "/", "_")
}

func (i *ImageIndex) Name() string {
return i.repoName
}

// Fields which are allowed to be annotated in a local index
type AnnotateFields struct {
Architecture string
OS string
Variant string
}

// AnnotateManifest changes the fields of the local index which
// are not empty string in the provided AnnotateField structure.
func (i *ImageIndex) AnnotateManifest(manifestName string, opts AnnotateFields) error {
path := filepath.Join(i.path, makeFileSafeName(i.repoName))

manifest, err := i.index.IndexManifest()
if err != nil {
return err
}

ref, err := name.ParseReference(manifestName)
if err != nil {
return err
}

desc, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
return err
}

for i, iDesc := range manifest.Manifests {
if iDesc.Digest.String() == desc.Digest.String() {
if opts.Architecture != "" {
manifest.Manifests[i].Platform.Architecture = opts.Architecture
}

if opts.OS != "" {
manifest.Manifests[i].Platform.OS = opts.OS
}

if opts.Variant != "" {
manifest.Manifests[i].Platform.Variant = opts.Variant
}

data, err := json.Marshal(manifest)
if err != nil {
return err
}

err = os.WriteFile(path, data, os.ModePerm)
if err != nil {
return err
}

return nil
}
}

return errors.Errorf("Manifest %s not found", manifestName)
}

// GetIndexManifest will look for a file the given index in the specified path and
// if found it will return a v1.IndexManifest.
// It is assumed that the local index file name is derived using makeFileSafeName()
func GetIndexManifest(repoName string, path string) (v1.IndexManifest, error) {
var manifest v1.IndexManifest

_, err := name.ParseReference(repoName)
if err != nil {
return manifest, err
}

manifestDir := filepath.Join(path, makeFileSafeName(repoName))

jsonFile, err := os.ReadFile(manifestDir)
if err != nil {
return manifest, errors.Wrapf(err, "Reading local index %q in path %q", repoName, path)
}

err = json.Unmarshal(jsonFile, &manifest)
if err != nil {
return manifest, errors.Wrapf(err, "Decoding local index %q", repoName)
}

return manifest, nil
}
30 changes: 30 additions & 0 deletions local/index_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package local

import (
v1 "github.com/google/go-containerregistry/pkg/v1"

"github.com/buildpacks/imgutil"
)

type ImageIndexOption func(*indexOptions) error

type indexOptions struct {
mediaTypes imgutil.MediaTypes
manifest v1.IndexManifest
}

// WithIndexMediaTypes lets a caller set the desired media types for the index manifest
func WithIndexMediaTypes(requested imgutil.MediaTypes) ImageIndexOption {
return func(opts *indexOptions) error {
opts.mediaTypes = requested
return nil
}
}

// WithManifest uses an existing v1.IndexManifest as a base to create the index
func WithManifest(manifest v1.IndexManifest) ImageIndexOption {
return func(opts *indexOptions) error {
opts.manifest = manifest
return nil
}
}
Loading