Skip to content

Commit

Permalink
Implement multi-platform support
Browse files Browse the repository at this point in the history
  • Loading branch information
jonjohnsonjr committed Aug 12, 2020
1 parent 618b163 commit 026422b
Show file tree
Hide file tree
Showing 27 changed files with 493 additions and 302 deletions.
14 changes: 12 additions & 2 deletions pkg/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"

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

// Interface abstracts different methods for turning a supported importpath
Expand All @@ -28,6 +29,15 @@ type Interface interface {
// TODO(mattmoor): Verify that some base repo: foo.io/bar can be suffixed with this reference and parsed.
IsSupportedReference(string) bool

// Build turns the given importpath reference into a v1.Image containing the Go binary.
Build(context.Context, string) (v1.Image, error)
// Build turns the given importpath reference into a v1.Image containing the Go binary
// (or a set of images as a v1.ImageIndex).
Build(context.Context, string) (Result, error)
}

// Result represents the product of a Build. This is usually a v1.Image or v1.ImageIndex.
type Result interface {
MediaType() (types.MediaType, error)
Size() (int64, error)
Digest() (v1.Hash, error)
RawManifest() ([]byte, error)
}
8 changes: 3 additions & 5 deletions pkg/build/future.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@ package build

import (
"sync"

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

func newFuture(work func() (v1.Image, error)) *future {
func newFuture(work func() (Result, error)) *future {
// Create a channel on which to send the result.
ch := make(chan *result)
// Initiate the actual work, sending its result
Expand All @@ -40,7 +38,7 @@ func newFuture(work func() (v1.Image, error)) *future {
}

type result struct {
img v1.Image
img Result
err error
}

Expand All @@ -52,7 +50,7 @@ type future struct {
}

// Get blocks on the result of the future.
func (f *future) Get() (v1.Image, error) {
func (f *future) Get() (Result, error) {
// Block on the promise of a result until we get one.
result, ok := <-f.promise
if ok {
Expand Down
7 changes: 3 additions & 4 deletions pkg/build/future_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ package build
import (
"testing"

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

func makeImage() (v1.Image, error) {
return random.Image(256, 8)
func makeImage() (Result, error) {
return random.Index(256, 8, 1)
}

func digest(t *testing.T, img v1.Image) string {
func digest(t *testing.T, img Result) string {
d, err := img.Digest()
if err != nil {
t.Fatalf("Digest() = %v", err)
Expand Down
82 changes: 73 additions & 9 deletions pkg/build/gobuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,20 @@ import (
"strings"

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"
)

const (
appDir = "/ko-app"
defaultAppFilename = "ko-app"
)

// GetBase takes an importpath and returns a base v1.Image.
type GetBase func(string) (v1.Image, error)
// GetBase takes an importpath and returns a base image.
type GetBase func(string) (Result, error)

type builder func(context.Context, string, v1.Platform, bool) (string, error)

type buildContext interface {
Expand Down Expand Up @@ -442,15 +445,9 @@ func (g *gobuild) tarKoData(ref reference) (*bytes.Buffer, error) {
return buf, walkRecursive(tw, root, kodataRoot)
}

// Build implements build.Interface
func (gb *gobuild) Build(ctx context.Context, s string) (v1.Image, error) {
func (gb *gobuild) buildOne(ctx context.Context, s string, base v1.Image) (v1.Image, error) {
ref := newRef(s)

// Determine the appropriate base image for this import path.
base, err := gb.getBase(ref.Path())
if err != nil {
return nil, err
}
cf, err := base.ConfigFile()
if err != nil {
return nil, err
Expand Down Expand Up @@ -563,3 +560,70 @@ func updatePath(cf *v1.ConfigFile) {
// If we get here, we never saw PATH.
cf.Config.Env = append(cf.Config.Env, "PATH="+appDir)
}

// Build implements build.Interface
func (gb *gobuild) Build(ctx context.Context, s string) (Result, error) {
// Determine the appropriate base image for this import path.
base, err := gb.getBase(s)
if err != nil {
return nil, err
}

mt, err := base.MediaType()
if err != nil {
return nil, err
}

switch mt {
case types.OCIImageIndex, types.DockerManifestList:
base, ok := base.(v1.ImageIndex)
if !ok {
return nil, fmt.Errorf("failed to interpret base as index: %v", base)
}
return gb.buildAll(ctx, s, base)
case types.OCIManifestSchema1, types.DockerManifestSchema2:
base, ok := base.(v1.Image)
if !ok {
return nil, fmt.Errorf("failed to interpret base as image: %v", base)
}
return gb.buildOne(ctx, s, base)
default:
return nil, fmt.Errorf("base image media type: %s", mt)
}
}

func (gb *gobuild) buildAll(ctx context.Context, s string, base v1.ImageIndex) (v1.ImageIndex, error) {
im, err := base.IndexManifest()
if err != nil {
return nil, err
}

adds := []mutate.IndexAddendum{}
for _, desc := range im.Manifests {
// This will fail if it's not an image, which is fine for now.
base, err := base.Image(desc.Digest)
if err != nil {
return nil, err
}
img, err := gb.buildOne(ctx, s, base)
if err != nil {
return nil, err
}
adds = append(adds, mutate.IndexAddendum{
Add: img,
Descriptor: v1.Descriptor{
URLs: desc.URLs,
MediaType: desc.MediaType,
Annotations: desc.Annotations,
Platform: desc.Platform,
},
})
}

baseType, err := base.MediaType()
if err != nil {
return nil, err
}

return mutate.IndexMediaType(mutate.AppendManifests(empty.Index, adds...), baseType), nil
}
Loading

0 comments on commit 026422b

Please sign in to comment.