Skip to content

Commit

Permalink
feat(*): add digest to invocation image on build
Browse files Browse the repository at this point in the history
resolves cnabio#690
  • Loading branch information
Michelle Noorali committed Apr 5, 2019
1 parent e40ac08 commit dfd15f8
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 115 deletions.
2 changes: 1 addition & 1 deletion cmd/duffle/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func (b *buildCmd) run() (err error) {
return fmt.Errorf("cannot prepare build: %v", err)
}

if err := bldr.Build(ctx, app); err != nil {
if err := bldr.Build(ctx, app, bf); err != nil {
return err
}

Expand Down
53 changes: 14 additions & 39 deletions pkg/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"time"

"github.com/Masterminds/semver"
"github.com/pkg/errors"
Expand Down Expand Up @@ -87,11 +85,6 @@ func (b *Builder) PrepareBuild(bldr *Builder, mfst *manifest.Manifest, appDir st
return nil, nil, err
}

ii := bundle.InvocationImage{}
ii.Image = imb.URI()
ii.ImageType = imb.Type()
bf.InvocationImages = append(bf.InvocationImages, ii)

baseVersion := mfst.Version
if baseVersion == "" {
baseVersion = "0.1.0"
Expand Down Expand Up @@ -131,45 +124,27 @@ func (b *Builder) version(baseVersion, sha string) (string, error) {
}

// Build passes the context of each component to its respective builder
func (b *Builder) Build(ctx context.Context, app *AppContext) error {
if err := buildInvocationImages(ctx, b.ImageBuilders, app); err != nil {
func (b *Builder) Build(ctx context.Context, app *AppContext, bf *bundle.Bundle) error {
if err := b.buildInvocationImages(ctx, app, bf); err != nil {
return fmt.Errorf("error building image: %v", err)
}
return nil
}

func buildInvocationImages(ctx context.Context, imageBuilders []imagebuilder.ImageBuilder, app *AppContext) (err error) {
errc := make(chan error)

go func() {
defer close(errc)
var wg sync.WaitGroup
wg.Add(len(imageBuilders))

for _, c := range imageBuilders {
go func(c imagebuilder.ImageBuilder) {
defer wg.Done()
err = c.Build(ctx, app.Log)
if err != nil {
errc <- fmt.Errorf("error building image %v: %v", c.Name(), err)
}
}(c)
}

wg.Wait()
}()

for errc != nil {
select {
case err, ok := <-errc:
if !ok {
errc = nil
continue
}
func (b *Builder) buildInvocationImages(ctx context.Context, app *AppContext, bf *bundle.Bundle) (err error) {
built := []bundle.InvocationImage{}
for _, c := range b.ImageBuilders {
digest, err := c.Build(ctx, app.Log)
if err != nil {
return err
default:
time.Sleep(time.Second)
}
ii := bundle.InvocationImage{}
ii.Image = c.URI()
ii.ImageType = c.Type()
ii.Digest = digest
built = append(built, ii)
}
bf.InvocationImages = built

return nil
}
99 changes: 52 additions & 47 deletions pkg/builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,13 @@ package builder
import (
"context"
"io"
"reflect"
"testing"

"github.com/deislabs/duffle/pkg/bundle"
"github.com/deislabs/duffle/pkg/duffle/manifest"
"github.com/deislabs/duffle/pkg/imagebuilder"
)

// testImage represents a mock invocation image
type testImage struct {
Nam string
Typ string
UR string
Diges string
}

// Name represents the name of a mock invocation image
func (tc testImage) Name() string {
return tc.Nam
}

// Type represents the type of a mock invocation image
func (tc testImage) Type() string {
return tc.Typ
}

// URI represents the URI of the artefact of a mock invocation image
func (tc testImage) URI() string {
return tc.UR
}

// Digest represents the digest of a mock invocation image
func (tc testImage) Digest() string {
return tc.Diges
}

// PrepareBuild is no-op for a mock invocation image
func (tc *testImage) PrepareBuild(appDir, registry, name string) error {
return nil
}

// Build is no-op for a mock invocation image
func (tc testImage) Build(ctx context.Context, log io.WriteCloser) error {
return nil
}

func TestPrepareBuild(t *testing.T) {
mfst := &manifest.Manifest{
Name: "foo",
Expand Down Expand Up @@ -85,14 +46,58 @@ func TestPrepareBuild(t *testing.T) {
t.Error(err)
}

if len(b.InvocationImages) != 1 {
t.Fatalf("expected there to be 1 image, got %d. Full output: %v", len(b.Images), b)
if len(b.InvocationImages) != 0 {
t.Errorf("Expected 0 invocation images (no info set until images are built), got %v", len(b.InvocationImages))
}

expected := bundle.InvocationImage{}
expected.Image = "cnab:0.1.0"
expected.ImageType = "docker"
if !reflect.DeepEqual(b.InvocationImages[0], expected) {
t.Errorf("expected %v, got %v", expected, b.InvocationImages[0])
if len(bldr.ImageBuilders) != 1 {
t.Fatalf("Expected 1 image builder to be set, got %v", len(bldr.ImageBuilders))
}
ib := bldr.ImageBuilders[0]
if ib.Name() != "cnab" {
t.Errorf("Expected name of invocation image to be cnab, got %s", ib.Name())
}
if ib.Type() != "docker" {
t.Errorf("Expected type of invocation image to be docker, got %s", ib.Type())
}
if ib.URI() != "cnab:0.1.0" {
t.Errorf("Expected URI of invocation image to be cnab:0.1.0, got %s", ib.URI())
}
}

// testImage represents a mock invocation image
type testImage struct {
Nam string
Typ string
UR string
Diges string
}

// Name represents the name of a mock invocation image
func (tc testImage) Name() string {
return tc.Nam
}

// Type represents the type of a mock invocation image
func (tc testImage) Type() string {
return tc.Typ
}

// URI represents the URI of the artefact of a mock invocation image
func (tc testImage) URI() string {
return tc.UR
}

// Digest represents the digest of a mock invocation image
func (tc testImage) Digest() string {
return tc.Diges
}

// PrepareBuild is no-op for a mock invocation image
func (tc *testImage) PrepareBuild(appDir, registry, name string) error {
return nil
}

// Build is no-op for a mock invocation image
func (tc testImage) Build(ctx context.Context, log io.WriteCloser) (string, error) {
return "", nil
}
39 changes: 20 additions & 19 deletions pkg/imagebuilder/docker/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import (
"os"
"path"
"path/filepath"
"strings"

"github.com/deislabs/duffle/pkg/duffle/manifest"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
Expand All @@ -22,10 +19,11 @@ import (
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/term"

"github.com/sirupsen/logrus"

"golang.org/x/net/context"

"github.com/deislabs/duffle/pkg/duffle/manifest"
"github.com/deislabs/duffle/pkg/imagebuilder/docker/digester"
)

const (
Expand Down Expand Up @@ -63,13 +61,6 @@ func (db Builder) URI() string {
return db.Image
}

// Digest returns the name of a Docker Builder, which will give the image name
//
// TODO - return the actual digest
func (db Builder) Digest() string {
return strings.Split(db.Image, ":")[1]
}

// NewBuilder returns a new Docker builder based on the manifest
func NewBuilder(c *manifest.InvocationImage, cli *command.DockerCli) *Builder {
return &Builder{
Expand Down Expand Up @@ -109,7 +100,7 @@ func (db *Builder) PrepareBuild(appDir, registry, name string) error {
}

// Build builds the docker images.
func (db Builder) Build(ctx context.Context, log io.WriteCloser) error {
func (db *Builder) Build(ctx context.Context, log io.WriteCloser) (string, error) {
defer db.BuildContext.Close()
buildOpts := types.ImageBuildOptions{
Tags: []string{db.Image},
Expand All @@ -118,23 +109,33 @@ func (db Builder) Build(ctx context.Context, log io.WriteCloser) error {

resp, err := db.dockerBuilder.DockerClient.Client().ImageBuild(ctx, db.BuildContext, buildOpts)
if err != nil {
return fmt.Errorf("error building image builder %v with builder %v: %v", db.Name(), db.Type(), err)
return "", fmt.Errorf("error building image builder %v with builder %v: %v", db.Name(), db.Type(), err)
}

defer resp.Body.Close()

outFd, isTerm := term.GetFdInfo(db.BuildContext)
if err := jsonmessage.DisplayJSONMessagesStream(resp.Body, log, outFd, isTerm, nil); err != nil {
return fmt.Errorf("error streaming messages for image builder %v with builder %v: %v", db.Name(), db.Type(), err)
return "", fmt.Errorf("error streaming messages for image builder %v with builder %v: %v", db.Name(), db.Type(), err)
}

if _, _, err = db.dockerBuilder.DockerClient.Client().ImageInspectWithRaw(ctx, db.Image); err != nil {
if dockerclient.IsErrNotFound(err) {
return fmt.Errorf("could not locate image for %s: %v", db.Name(), err)
return "", fmt.Errorf("could not locate image for %s: %v", db.Name(), err)
}
return fmt.Errorf("imageInspectWithRaw error for image builder %v: %v", db.Name(), err)
return "", fmt.Errorf("imageInspectWithRaw error for image builder %v: %v", db.Name(), err)
}

return nil
d := digester.NewDigester(
ctx,
db.dockerBuilder.DockerClient.Client(),
db.Image,
)
digestStr, err := d.Digest()
if err != nil {
return "", fmt.Errorf("Failed to calculate digest for image: %s", err)
}

return digestStr, nil
}

func archiveSrc(contextPath string, b *Builder) error {
Expand Down
42 changes: 42 additions & 0 deletions pkg/imagebuilder/docker/digester/digester.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package digester

import (
"golang.org/x/net/context"

"github.com/docker/docker/client"
"github.com/opencontainers/go-digest"
)

type Digester struct {
Client client.ImageAPIClient
Image string
Context context.Context
}

// NewDigester returns a Digester given the args client, image, ctx
//
// ctx is context to use and pass to Docker client
// client allows us to talk to the Docker client
// image is a string identifier of the image we want to compute digest of
func NewDigester(ctx context.Context, client client.ImageAPIClient, image string) *Digester {
return &Digester{
Client: client,
Image: image,
Context: ctx,
}
}

// Digest returns the digest of the image tar
func (d *Digester) Digest() (string, error) {
reader, err := d.Client.ImageSave(d.Context, []string{d.Image})
if err != nil {
return "", err
}
computedDigest, err := digest.Canonical.FromReader(reader)
if err != nil {
return "", err
}
defer reader.Close()

return computedDigest.String(), nil
}
Loading

0 comments on commit dfd15f8

Please sign in to comment.