Skip to content

Commit

Permalink
improved image OCI conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
djcass44 committed Oct 15, 2023
1 parent 03c207d commit 1ec4e44
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 4 deletions.
2 changes: 1 addition & 1 deletion cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func build(cmd *cobra.Command, _ []string) error {
}

// pull the base image
baseImg, err := containerutil.Pull(cmd.Context(), baseImage)
baseImg, err := containerutil.Get(cmd.Context(), baseImage)
if err != nil {
return err
}
Expand Down
7 changes: 4 additions & 3 deletions pkg/containerutil/append.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package containerutil
import (
"context"
"fmt"
"path/filepath"
"strings"

"github.com/chainguard-dev/go-apk/pkg/fs"
"github.com/go-logr/logr"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/types"
"path/filepath"
"strings"
)

const MagicImageScratch = "scratch"
Expand Down Expand Up @@ -88,7 +89,7 @@ func (ib *Image) Append(ctx context.Context, fs fs.FullFS, platform *v1.Platform
if mt, err := ib.baseImage.MediaType(); err == nil {
log.V(1).Info("detected base image media type", "mediaType", mt)
}
baseImage := mutate.MediaType(ib.baseImage, types.OCIManifestSchema1)
baseImage := ib.baseImage

// append our layer
layers := []mutate.Addendum{
Expand Down
46 changes: 46 additions & 0 deletions pkg/containerutil/get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package containerutil

import (
"context"
"fmt"

"github.com/djcass44/ci-tools/pkg/ociutil"
"github.com/go-logr/logr"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/remote"
)

func Get(ctx context.Context, ref string) (v1.Image, error) {
log := logr.FromContextOrDiscard(ctx).WithValues("ref", ref)
log.Info("getting image")

if ref == MagicImageScratch {
return empty.Image, nil
}

remoteRef, err := name.ParseReference(ref)
if err != nil {
return nil, fmt.Errorf("parsing name %s: %w", ref, err)
}

// fetch the image without actually
// pulling it
rmt, err := remote.Get(remoteRef, remote.WithContext(ctx), remote.WithAuthFromKeychain(ociutil.KeyChain(ociutil.Auth{})))
if err != nil {
return nil, fmt.Errorf("getting %s: %w", ref, err)
}

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

// normalise the image
img, err = NormaliseImage(ctx, img)
if err != nil {
return nil, fmt.Errorf("normalising %s: %w", ref, err)
}
return img, nil
}
32 changes: 32 additions & 0 deletions pkg/containerutil/get_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package containerutil

import (
"context"
"testing"

"github.com/go-logr/logr"
"github.com/go-logr/logr/testr"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/stretchr/testify/assert"
)

func TestGet(t *testing.T) {
ctx := logr.NewContext(context.TODO(), testr.NewWithOptions(t, testr.Options{Verbosity: 10}))

t.Run("real image", func(t *testing.T) {
img, err := Get(ctx, "busybox")
assert.NoError(t, err)
size, err := img.Size()
assert.NoError(t, err)
assert.NotZero(t, size)
})
t.Run("scratch image", func(t *testing.T) {
img, err := Get(ctx, "scratch")
assert.NoError(t, err)
size, err := img.Size()
assert.NoError(t, err)
assert.NotZero(t, size)

assert.Equal(t, empty.Image, img)
})
}
106 changes: 106 additions & 0 deletions pkg/containerutil/mutate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package containerutil

import (
"context"
"fmt"

"github.com/go-logr/logr"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/google/go-containerregistry/pkg/v1/types"
)

// Drops docker specific properties
// See: https://github.com/opencontainers/image-spec/blob/main/config.md
func toOCIV1Config(config v1.Config) v1.Config {
return v1.Config{
User: config.User,
ExposedPorts: config.ExposedPorts,
Env: config.Env,
Entrypoint: config.Entrypoint,
Cmd: config.Cmd,
Volumes: config.Volumes,
WorkingDir: config.WorkingDir,
Labels: config.Labels,
StopSignal: config.StopSignal,
}
}

func toOCIV1ConfigFile(cf *v1.ConfigFile) *v1.ConfigFile {
return &v1.ConfigFile{
Created: cf.Created,
Author: cf.Author,
Architecture: cf.Architecture,
OS: cf.OS,
OSVersion: cf.OSVersion,
History: cf.History,
RootFS: cf.RootFS,
Config: toOCIV1Config(cf.Config),
}
}

// NormaliseImage mutates the provided v1.Image to be OCI compliant v1.Image.
//
// Check image-spec to see which properties are ported and which are dropped.
// https://github.com/opencontainers/image-spec/blob/main/config.md
func NormaliseImage(ctx context.Context, base v1.Image) (v1.Image, error) {
log := logr.FromContextOrDiscard(ctx)
log.V(1).Info("normalising base image")
// get the original manifest
m, err := base.Manifest()
if err != nil {
return nil, err
}
// convert config
cfg, err := base.ConfigFile()
if err != nil {
return nil, err
}
cfg = toOCIV1ConfigFile(cfg)

layers, err := base.Layers()
if err != nil {
return nil, err
}

newLayers := []v1.Layer{}

// go through each layer and convert it to
// OCI format
for _, layer := range layers {
mediaType, err := layer.MediaType()
if err != nil {
return nil, err
}
log.V(2).Info("checking layer", "mediaType", mediaType)
switch mediaType {
case types.DockerLayer:
layer, err = tarball.LayerFromOpener(layer.Compressed, tarball.WithMediaType(types.OCILayer))
if err != nil {
return nil, fmt.Errorf("building layer: %w", err)
}
case types.DockerUncompressedLayer:
layer, err = tarball.LayerFromOpener(layer.Uncompressed, tarball.WithMediaType(types.OCIUncompressedLayer))
if err != nil {
return nil, fmt.Errorf("building layer: %w", err)
}
}
newLayers = append(newLayers, layer)
}

base, err = mutate.AppendLayers(empty.Image, newLayers...)
if err != nil {
return nil, err
}

base = mutate.MediaType(base, types.OCIManifestSchema1)
base = mutate.ConfigMediaType(base, types.OCIConfigJSON)
base = mutate.Annotations(base, m.Annotations).(v1.Image)
base, err = mutate.ConfigFile(base, cfg)
if err != nil {
return nil, err
}
return base, nil
}

0 comments on commit 1ec4e44

Please sign in to comment.