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

Layout wraps v1 image #243

Merged
merged 15 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ jobs:
test-and-build-windows:
runs-on: windows-2019
steps:
- name: Set git to use LF and symlinks
run: |
git config --global core.autocrlf false
git config --global core.eol lf
git config --global core.symlinks true
- uses: actions/checkout@v4
- name: Set up go
uses: actions/setup-go@v5
Expand Down
152 changes: 83 additions & 69 deletions cnb_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package imgutil
import (
"errors"
"fmt"
"io"
"strings"
"time"

Expand All @@ -18,36 +19,20 @@ import (
// The working image could be any v1.Image,
// but in practice will start off as a pointer to a locallayout.v1ImageFacade (or similar).
type CNBImageCore struct {
v1.Image // the working image
Store ImageStore
// required
repoName string
v1.Image // the working image
// optional
createdAt time.Time
preferredMediaTypes MediaTypes
preserveHistory bool
previousImage v1.Image
}

type ImageStore interface {
Contains(identifier string) bool
Delete(identifier string) error
Save(image IdentifiableV1Image, withName string, withAdditionalNames ...string) (string, error)
SaveFile(image IdentifiableV1Image, withName string) (string, error)

DownloadLayersFor(identifier string) error
Layers() []v1.Layer
}
Comment on lines -31 to -39
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I thought this could be an interface that all packages could implement, but since most packages would override the methods that call this interface it's not so useful


type IdentifiableV1Image interface {
v1.Image
Identifier() (Identifier, error)
}

var _ v1.Image = &CNBImageCore{}

// FIXME: mark deprecated methods as deprecated on the interface when other packages (remote, layout) expose a v1.Image

// Deprecated: Architecture
// TBD Deprecated: Architecture
func (i *CNBImageCore) Architecture() (string, error) {
configFile, err := getConfigFile(i.Image)
if err != nil {
Expand All @@ -56,7 +41,7 @@ func (i *CNBImageCore) Architecture() (string, error) {
return configFile.Architecture, nil
}

// Deprecated: CreatedAt
// TBD Deprecated: CreatedAt
func (i *CNBImageCore) CreatedAt() (time.Time, error) {
configFile, err := getConfigFile(i.Image)
if err != nil {
Expand All @@ -65,7 +50,7 @@ func (i *CNBImageCore) CreatedAt() (time.Time, error) {
return configFile.Created.Time, nil
}

// Deprecated: Entrypoint
// TBD Deprecated: Entrypoint
func (i *CNBImageCore) Entrypoint() ([]string, error) {
configFile, err := getConfigFile(i.Image)
if err != nil {
Expand Down Expand Up @@ -96,25 +81,28 @@ func (i *CNBImageCore) GetAnnotateRefName() (string, error) {
return manifest.Annotations["org.opencontainers.image.ref.name"], nil
}

// Deprecated: History
func (i *CNBImageCore) History() ([]v1.History, error) {
configFile, err := getConfigFile(i.Image)
func (i *CNBImageCore) GetLayer(diffID string) (io.ReadCloser, error) {
hash, err := v1.NewHash(diffID)
if err != nil {
return nil, err
}
return configFile.History, nil
layer, err := i.LayerByDiffID(hash)
if err != nil {
return nil, err
}
return layer.Uncompressed()
}

func (i *CNBImageCore) Kind() string {
storeType := fmt.Sprintf("%T", i.Store)
parts := strings.Split(storeType, ".")
if len(parts) < 2 {
return storeType
// TBD Deprecated: History
func (i *CNBImageCore) History() ([]v1.History, error) {
configFile, err := getConfigFile(i.Image)
if err != nil {
return nil, err
}
return strings.TrimPrefix(parts[0], "*")
return configFile.History, nil
}

// Deprecated: Label
// TBD Deprecated: Label
func (i *CNBImageCore) Label(key string) (string, error) {
configFile, err := getConfigFile(i.Image)
if err != nil {
Expand All @@ -123,7 +111,7 @@ func (i *CNBImageCore) Label(key string) (string, error) {
return configFile.Config.Labels[key], nil
}

// Deprecated: Labels
// TBD Deprecated: Labels
func (i *CNBImageCore) Labels() (map[string]string, error) {
configFile, err := getConfigFile(i.Image)
if err != nil {
Expand All @@ -132,16 +120,12 @@ func (i *CNBImageCore) Labels() (map[string]string, error) {
return configFile.Config.Labels, nil
}

// Deprecated: ManifestSize
// TBD Deprecated: ManifestSize
func (i *CNBImageCore) ManifestSize() (int64, error) {
return i.Image.Size()
}

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

// Deprecated: OS
// TBD Deprecated: OS
func (i *CNBImageCore) OS() (string, error) {
configFile, err := getConfigFile(i.Image)
if err != nil {
Expand All @@ -150,7 +134,7 @@ func (i *CNBImageCore) OS() (string, error) {
return configFile.OS, nil
}

// Deprecated: OSVersion
// TBD Deprecated: OSVersion
func (i *CNBImageCore) OSVersion() (string, error) {
configFile, err := getConfigFile(i.Image)
if err != nil {
Expand All @@ -165,7 +149,7 @@ func (i *CNBImageCore) TopLayer() (string, error) {
return "", err
}
if len(layers) == 0 {
return "", fmt.Errorf("image %q has no layers", i.Name())
return "", errors.New("image has no layers")
}
topLayer := layers[len(layers)-1]
hex, err := topLayer.DiffID()
Expand All @@ -185,7 +169,7 @@ func (i *CNBImageCore) Valid() bool {
return err == nil
}

// Deprecated: Variant
// TBD Deprecated: Variant
func (i *CNBImageCore) Variant() (string, error) {
configFile, err := getConfigFile(i.Image)
if err != nil {
Expand All @@ -194,7 +178,7 @@ func (i *CNBImageCore) Variant() (string, error) {
return configFile.Variant, nil
}

// Deprecated: WorkingDir
// TBD Deprecated: WorkingDir
func (i *CNBImageCore) WorkingDir() (string, error) {
configFile, err := getConfigFile(i.Image)
if err != nil {
Expand All @@ -208,6 +192,9 @@ func (i *CNBImageCore) AnnotateRefName(refName string) error {
if err != nil {
return err
}
if manifest.Annotations == nil {
manifest.Annotations = make(map[string]string)
}
manifest.Annotations["org.opencontainers.image.ref.name"] = refName
mutated := mutate.Annotations(i.Image, manifest.Annotations)
image, ok := mutated.(v1.Image)
Expand All @@ -218,25 +205,21 @@ func (i *CNBImageCore) AnnotateRefName(refName string) error {
return nil
}

func (i *CNBImageCore) Rename(name string) {
i.repoName = name
}

// Deprecated: SetArchitecture
// TBD Deprecated: SetArchitecture
func (i *CNBImageCore) SetArchitecture(architecture string) error {
return i.MutateConfigFile(func(c *v1.ConfigFile) {
c.Architecture = architecture
})
}

// Deprecated: SetCmd
// TBD Deprecated: SetCmd
func (i *CNBImageCore) SetCmd(cmd ...string) error {
return i.MutateConfigFile(func(c *v1.ConfigFile) {
c.Config.Cmd = cmd
})
}

// Deprecated: SetEntrypoint
// TBD Deprecated: SetEntrypoint
func (i *CNBImageCore) SetEntrypoint(ep ...string) error {
return i.MutateConfigFile(func(c *v1.ConfigFile) {
c.Config.Entrypoint = ep
Expand Down Expand Up @@ -266,7 +249,7 @@ func (i *CNBImageCore) SetEnv(key, val string) error {
})
}

// Deprecated: SetHistory
// TBD Deprecated: SetHistory
func (i *CNBImageCore) SetHistory(histories []v1.History) error {
return i.MutateConfigFile(func(c *v1.ConfigFile) {
c.History = histories
Expand All @@ -288,21 +271,21 @@ func (i *CNBImageCore) SetOS(osVal string) error {
})
}

// Deprecated: SetOSVersion
// TBD Deprecated: SetOSVersion
func (i *CNBImageCore) SetOSVersion(osVersion string) error {
return i.MutateConfigFile(func(c *v1.ConfigFile) {
c.OSVersion = osVersion
})
}

// Deprecated: SetVariant
// TBD Deprecated: SetVariant
func (i *CNBImageCore) SetVariant(variant string) error {
return i.MutateConfigFile(func(c *v1.ConfigFile) {
c.Variant = variant
})
}

// Deprecated: SetWorkingDir
// TBD Deprecated: SetWorkingDir
func (i *CNBImageCore) SetWorkingDir(dir string) error {
return i.MutateConfigFile(func(c *v1.ConfigFile) {
c.Config.WorkingDir = dir
Expand All @@ -322,18 +305,22 @@ func (i *CNBImageCore) AddLayerWithDiffID(path, _ string) error {
}

func (i *CNBImageCore) AddLayerWithDiffIDAndHistory(path, _ string, history v1.History) error {
// ensure existing history
if err := i.MutateConfigFile(func(c *v1.ConfigFile) {
c.History = NormalizedHistory(c.History, len(c.RootFS.DiffIDs))
}); err != nil {
return err
}

layer, err := tarball.LayerFromFile(path)
if err != nil {
return err
}
if !i.preserveHistory {
history = emptyHistory
}
configFile, err := getConfigFile(i)
if err != nil {
return err
}
history.Created = configFile.Created
history.Created = v1.Time{Time: i.createdAt}

i.Image, err = mutate.Append(
i.Image,
mutate.Addendum{
Expand All @@ -346,9 +333,6 @@ func (i *CNBImageCore) AddLayerWithDiffIDAndHistory(path, _ string, history v1.H
}

func (i *CNBImageCore) Rebase(baseTopLayerDiffID string, withNewBase Image) error {
if i.Kind() != withNewBase.Kind() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If my underlying image i is a remote image, does it mean I can rebase it with a local or layout image?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, in theory - I haven't tested it, but probably you can

return fmt.Errorf("expected new base to be a %s image; got %s", i.Kind(), withNewBase.Kind())
}
newBase := withNewBase.UnderlyingImage() // FIXME: when all imgutil.Images are v1.Images, we can remove this part
var err error
i.Image, err = mutate.Rebase(i.Image, i.newV1ImageFacade(baseTopLayerDiffID), newBase)
Expand Down Expand Up @@ -409,23 +393,23 @@ func (i *CNBImageCore) ReuseLayer(diffID string) error {
}
idx, err := getLayerIndex(diffID, i.previousImage)
if err != nil {
return err
return fmt.Errorf("failed to get layer index: %w", err)
}
previousHistory, err := getHistory(idx, i.previousImage)
if err != nil {
return err
return fmt.Errorf("failed to get history: %w", err)
}
return i.ReuseLayerWithHistory(diffID, previousHistory)
}

func getLayerIndex(forDiffID string, fromImage v1.Image) (int, error) {
layerHash, err := v1.NewHash(forDiffID)
if err != nil {
return -1, err
return -1, fmt.Errorf("failed to get layer hash: %w", err)
}
configFile, err := getConfigFile(fromImage)
if err != nil {
return -1, err
return -1, fmt.Errorf("failed to get config file: %w", err)
}
for idx, configHash := range configFile.RootFS.DiffIDs {
if layerHash.String() == configHash.String() {
Expand All @@ -449,13 +433,15 @@ func getHistory(forIndex int, fromImage v1.Image) (v1.History, error) {
func (i *CNBImageCore) ReuseLayerWithHistory(diffID string, history v1.History) error {
layerHash, err := v1.NewHash(diffID)
if err != nil {
return err
return fmt.Errorf("failed to get layer hash: %w", err)
}
layer, err := i.previousImage.LayerByDiffID(layerHash)
if err != nil {
return err
return fmt.Errorf("failed to get layer by diffID: %w", err)
}
if !i.preserveHistory {
if i.preserveHistory {
history.Created = v1.Time{Time: i.createdAt}
} else {
history = emptyHistory
}
i.Image, err = mutate.Append(
Expand All @@ -482,6 +468,34 @@ func (i *CNBImageCore) MutateConfigFile(withFunc func(c *v1.ConfigFile)) error {
return err
}

func (i *CNBImageCore) SetCreatedAtAndHistory() error {
var err error
// set created at
if err = i.MutateConfigFile(func(c *v1.ConfigFile) {
c.Created = v1.Time{Time: i.createdAt}
c.Container = ""
}); err != nil {
return err
}
// set history
if i.preserveHistory {
// set created at for each history
err = i.MutateConfigFile(func(c *v1.ConfigFile) {
for j := range c.History {
c.History[j].Created = v1.Time{Time: i.createdAt}
}
})
} else {
// zero history
err = i.MutateConfigFile(func(c *v1.ConfigFile) {
for j := range c.History {
c.History[j] = v1.History{Created: v1.Time{Time: i.createdAt}}
}
})
}
return err
}

func getConfigFile(image v1.Image) (*v1.ConfigFile, error) {
configFile, err := image.ConfigFile()
if err != nil {
Expand Down
Loading
Loading