Skip to content

Commit

Permalink
Merge pull request #234 from buildpack/feature/106-zipped-app
Browse files Browse the repository at this point in the history
Add support for zip-formatted files for the app path
  • Loading branch information
ameyer-pivotal authored Jul 10, 2019
2 parents 6377274 + ad9b416 commit 49dc406
Show file tree
Hide file tree
Showing 19 changed files with 332 additions and 105 deletions.
6 changes: 5 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,8 @@ jobs:
- NO_DOCKER=true
- GO111MODULE=on
script: go test -mod=vendor -count=1 -parallel=1 -v ./...
after_success: go build -mod=vendor -o pack ./cmd/pack
after_success: go build -mod=vendor -o pack ./cmd/pack

branches:
only:
- master
28 changes: 22 additions & 6 deletions acceptance/acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) {
h.Run(t, packCmd("set-default-builder", builder))
})

it("creates image on the daemon", func() {
t.Log("no previous image exists")
it("creates a runnable, rebuildable image on daemon from app dir", func() {
appPath := filepath.Join("testdata", "mock_app")
cmd := packCmd(
"build", repoName,
"-p", filepath.Join("testdata", "mock_app"),
"-p", appPath,
)
output := h.Run(t, cmd)
h.AssertContains(t, output, fmt.Sprintf("Successfully built image '%s'", repoName))
Expand Down Expand Up @@ -200,7 +200,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) {
h.Run(t, cmd)

t.Log("rebuild")
cmd = packCmd("build", repoName, "-p", filepath.Join("testdata", "mock_app"))
cmd = packCmd("build", repoName, "-p", appPath)
output = h.Run(t, cmd)
h.AssertContains(t, output, fmt.Sprintf("Successfully built image '%s'", repoName))
imgId, err = imgIdFromOutput(output, repoName)
Expand All @@ -225,7 +225,7 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) {
h.AssertContainsMatch(t, output, `(?i)\[cacher] reusing layer 'simple/layers:cached-launch-layer'`)

t.Log("rebuild with --clear-cache")
cmd = packCmd("build", repoName, "-p", "testdata/mock_app/.", "--clear-cache")
cmd = packCmd("build", repoName, "-p", appPath, "--clear-cache")
output = h.Run(t, cmd)
h.AssertContains(t, output, fmt.Sprintf("Successfully built image '%s'", repoName))

Expand All @@ -244,6 +244,22 @@ func testAcceptance(t *testing.T, when spec.G, it spec.S) {
h.AssertContainsMatch(t, output, `(?i)\[cacher] (Caching|adding) layer 'simple/layers:cached-launch-layer'`)
})

it("supports building app from a zip file", func() {
appPath := filepath.Join("testdata", "mock_app.zip")
cmd := packCmd(
"build", repoName,
"-p", appPath,
)
output := h.Run(t, cmd)
h.AssertContains(t, output, fmt.Sprintf("Successfully built image '%s'", repoName))
imgId, err := imgIdFromOutput(output, repoName)
if err != nil {
t.Log(output)
t.Fatal("Could not determine image id for built image")
}
defer h.DockerRmi(dockerCli, imgId)
})

when("--buildpack", func() {
when("the argument is a tgz or id", func() {
var notBuilderTgz string
Expand Down Expand Up @@ -834,7 +850,7 @@ func createStack(t *testing.T, dockerCli *client.Client) {

func createStackImage(t *testing.T, dockerCli *client.Client, repoName string, dir string) {
ctx := context.Background()
buildContext, _ := archive.CreateTarReader(dir, "/", 0, 0, -1)
buildContext := archive.ReadDirAsTar(dir, "/", 0, 0, -1)

res, err := dockerCli.ImageBuild(ctx, buildContext, dockertypes.ImageBuildOptions{
Tags: []string{repoName},
Expand Down
Binary file added acceptance/testdata/mock_app.zip
Binary file not shown.
58 changes: 37 additions & 21 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import (
"runtime"
"strings"

"github.com/docker/docker/api/types"

"github.com/buildpack/imgutil"
"github.com/docker/docker/api/types"
"github.com/google/go-containerregistry/pkg/name"
"github.com/pkg/errors"

"github.com/buildpack/pack/build"
"github.com/buildpack/pack/builder"
"github.com/buildpack/pack/buildpack"
"github.com/buildpack/pack/internal/archive"
"github.com/buildpack/pack/style"
)

Expand All @@ -28,7 +28,7 @@ type Lifecycle interface {
type BuildOptions struct {
Image string // required
Builder string // required
AppDir string // defaults to current working directory
AppPath string // defaults to current working directory
RunImage string // defaults to the best mirror from the builder metadata or AdditionalMirrors
AdditionalMirrors map[string][]string // only considered if RunImage is not provided
Env map[string]string
Expand All @@ -51,9 +51,9 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
return errors.Wrapf(err, "invalid image name '%s'", opts.Image)
}

appDir, err := c.processAppDir(opts.AppDir)
appPath, err := c.processAppPath(opts.AppPath)
if err != nil {
return errors.Wrapf(err, "invalid app dir '%s'", opts.AppDir)
return errors.Wrapf(err, "invalid app path '%s'", opts.AppPath)
}

proxyConfig := c.processProxyConfig(opts.ProxyConfig)
Expand Down Expand Up @@ -91,7 +91,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
defer c.docker.ImageRemove(context.Background(), ephemeralBuilder.Name(), types.ImageRemoveOptions{Force: true})

return c.lifecycle.Execute(ctx, build.LifecycleOptions{
AppDir: appDir,
AppPath: appPath,
Image: imageRef,
Builder: ephemeralBuilder,
RunImage: runImage,
Expand Down Expand Up @@ -139,33 +139,49 @@ func (c *Client) validateRunImage(context context.Context, name string, noPull b
return img, nil
}

func (c *Client) processAppDir(appDir string) (string, error) {
func (c *Client) processAppPath(appPath string) (string, error) {
var (
resolvedAppDir = appDir
err error
resolvedAppPath = appPath
err error
)

if appDir == "" {
if appDir, err = os.Getwd(); err != nil {
return "", err
if appPath == "" {
if appPath, err = os.Getwd(); err != nil {
return "", errors.Wrap(err, "get working dir")
}
}

if resolvedAppDir, err = filepath.EvalSymlinks(appDir); err != nil {
return "", err
if resolvedAppPath, err = filepath.EvalSymlinks(appPath); err != nil {
return "", errors.Wrap(err, "evaluate symlink")
}

if resolvedAppDir, err = filepath.Abs(resolvedAppDir); err != nil {
return "", err
if resolvedAppPath, err = filepath.Abs(resolvedAppPath); err != nil {
return "", errors.Wrap(err, "resolve absolute path")
}

fi, err := os.Stat(resolvedAppPath)
if err != nil {
return "", errors.Wrap(err, "stat file")
}

if fi, err := os.Stat(resolvedAppDir); err != nil {
return "", err
} else if !fi.IsDir() {
return "", fmt.Errorf("%s is not a directory", appDir)
if !fi.IsDir() {
fh, err := os.Open(resolvedAppPath)
if err != nil {
return "", errors.Wrap(err, "read file")
}
defer fh.Close()

isZip, err := archive.IsZip(fh)
if err != nil {
return "", errors.Wrap(err, "check zip")
}

if !isZip {
return "", errors.New("app path must be a directory or zip")
}
}

return resolvedAppDir, nil
return resolvedAppPath, nil
}

func (c *Client) processProxyConfig(config *ProxyConfig) ProxyConfig {
Expand Down
6 changes: 3 additions & 3 deletions build/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type Lifecycle struct {
builder *builder.Builder
logger logging.Logger
docker *client.Client
appDir string
appPath string
appOnce *sync.Once
httpProxy string
httpsProxy string
Expand All @@ -45,7 +45,7 @@ func NewLifecycle(docker *client.Client, logger logging.Logger) *Lifecycle {
}

type LifecycleOptions struct {
AppDir string
AppPath string
Image name.Reference
Builder *builder.Builder
RunImage string
Expand Down Expand Up @@ -132,7 +132,7 @@ func (l *Lifecycle) Execute(ctx context.Context, opts LifecycleOptions) error {
func (l *Lifecycle) Setup(opts LifecycleOptions) {
l.LayersVolume = "pack-layers-" + randString(10)
l.AppVolume = "pack-app-" + randString(10)
l.appDir = opts.AppDir
l.appPath = opts.AppPath
l.appOnce = &sync.Once{}
l.builder = opts.Builder
l.httpProxy = opts.HTTPProxy
Expand Down
43 changes: 32 additions & 11 deletions build/phase.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package build
import (
"context"
"fmt"
"io"
"os"
"runtime"
"sync"

Expand All @@ -26,7 +28,7 @@ type Phase struct {
hostConf *dcontainer.HostConfig
ctr dcontainer.ContainerCreateCreatedBody
uid, gid int
appDir string
appPath string
appOnce *sync.Once
}

Expand All @@ -50,7 +52,7 @@ func (l *Lifecycle) NewPhase(name string, ops ...func(*Phase) (*Phase, error)) (
logger: l.logger,
uid: l.builder.UID,
gid: l.builder.GID,
appDir: l.appDir,
appPath: l.appPath,
appOnce: l.appOnce,
}

Expand Down Expand Up @@ -113,29 +115,30 @@ func WithRegistryAccess(repos ...string) func(*Phase) (*Phase, error) {

func (p *Phase) Run(context context.Context) error {
var err error

p.ctr, err = p.docker.ContainerCreate(context, p.ctrConf, p.hostConf, nil, "")
if err != nil {
return errors.Wrapf(err, "failed to create '%s' container", p.name)
}

p.appOnce.Do(func() {
var mode int64 = -1
if runtime.GOOS == "windows" {
mode = 0777
var appReader io.ReadCloser
appReader, err = p.createAppReader()
if err != nil {
err = errors.Wrapf(err, "create tar archive from '%s'", p.appPath)
return
}
defer appReader.Close()

appReader, errChan := archive.CreateTarReader(p.appDir, appDir, p.uid, p.gid, mode)
if err = p.docker.CopyToContainer(context, p.ctr.ID, "/", appReader, types.CopyToContainerOptions{}); err != nil {
err = errors.Wrapf(err, "failed to copy files to '%s' container", p.name)
}

err = <-errChan
if err != nil {
err = errors.Wrapf(err, "create tar archive from '%s'", p.appDir)
return
}
})
if err != nil {
return errors.Wrapf(err, "run %s container", p.name)
}

return container.Run(
context,
p.docker,
Expand All @@ -148,3 +151,21 @@ func (p *Phase) Run(context context.Context) error {
func (p *Phase) Cleanup() error {
return p.docker.ContainerRemove(context.Background(), p.ctr.ID, types.ContainerRemoveOptions{Force: true})
}

func (p *Phase) createAppReader() (io.ReadCloser, error) {
fi, err := os.Stat(p.appPath)
if err != nil {
return nil, err
}

if fi.IsDir() {
var mode int64 = -1
if runtime.GOOS == "windows" {
mode = 0777
}

return archive.ReadDirAsTar(p.appPath, appDir, p.uid, p.gid, mode), nil
}

return archive.ReadZipAsTar(p.appPath, appDir, p.uid, p.gid, -1), nil
}
8 changes: 4 additions & 4 deletions build/phase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ func TestPhase(t *testing.T) {
dockerCli, err = client.NewClientWithOpts(client.FromEnv, client.WithVersion("1.38"))
h.AssertNil(t, err)

repoName = "lifecycle.test." + h.RandString(10)
repoName = "phase.test." + h.RandString(10)
CreateFakeLifecycleImage(t, dockerCli, repoName)
defer h.DockerRmi(dockerCli, repoName)

spec.Run(t, "lifecycle", testPhase, spec.Report(report.Terminal{}), spec.Parallel())
spec.Run(t, "phase", testPhase, spec.Report(report.Terminal{}), spec.Parallel())
}

func testPhase(t *testing.T, when spec.G, it spec.S) {
Expand Down Expand Up @@ -308,7 +308,7 @@ func CreateFakeLifecycleImage(t *testing.T, dockerCli *client.Client, repoName s

wd, err := os.Getwd()
h.AssertNil(t, err)
buildContext, _ := archive.CreateTarReader(filepath.Join(wd, "testdata", "fake-lifecycle"), "/", 0, 0, -1)
buildContext := archive.ReadDirAsTar(filepath.Join(wd, "testdata", "fake-lifecycle"), "/", 0, 0, -1)

res, err := dockerCli.ImageBuild(ctx, buildContext, dockertypes.ImageBuildOptions{
Tags: []string{repoName},
Expand All @@ -334,7 +334,7 @@ func CreateFakeLifecycle(appDir string, docker *client.Client, logger logging.Lo
}

subject.Setup(build.LifecycleOptions{
AppDir: appDir,
AppPath: appDir,
Builder: bldr,
HTTPProxy: "some-http-proxy",
HTTPSProxy: "some-https-proxy",
Expand Down
Loading

0 comments on commit 49dc406

Please sign in to comment.