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

Add support for zip-formatted files for the app path #234

Merged
merged 1 commit into from
Jul 10, 2019
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
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
ameyer-pivotal marked this conversation as resolved.
Show resolved Hide resolved
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)
jromero marked this conversation as resolved.
Show resolved Hide resolved
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)
ameyer-pivotal marked this conversation as resolved.
Show resolved Hide resolved
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) {
ameyer-pivotal marked this conversation as resolved.
Show resolved Hide resolved
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