From bf351d8427fd9ba6288122e8aa63c67e946e24b8 Mon Sep 17 00:00:00 2001 From: Stephen Levine Date: Mon, 15 May 2017 18:35:07 -0400 Subject: [PATCH] Add support for pushing JAR/WAR/ZIP files --- README.md | 27 +++++++++++----------- VERSION | 2 +- cf/cmd/stage.go | 5 ++-- cf/cmd/stage_test.go | 5 ++-- fs/fs.go | 55 ++++++++++++++++++++++++++++++++++++++++---- plugin/usage.go | 8 ++++++- 6 files changed, 78 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index d37b982..af7ccdf 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ Notably, CF Local: ``` USAGE: - cf local stage [ (-b | -b ) (-s | -f ) ] + cf local stage [ (-b | -b ) (-p | -p ) ] + [ (-s | -f ) ] cf local run [ (-i ) (-p ) (-d ) ] [ (-s ) (-f ) ] cf local export [ (-r ) ] @@ -44,6 +45,10 @@ STAGE OPTIONS: Default: (uses detection) -b Use a buildpack specified by URL (git or zip-over-HTTP). Default: (uses detection) + -p Use the specified directory as the app directory. + Default: current working directory + -p Use the specified zip file contents as the app directory. + Default: current working directory -s Use the service bindings from the specified remote CF app instead of the service bindings in local.yml. Default: (uses local.yml) @@ -124,20 +129,20 @@ applications: ## Install ```bash -$ ./cflocal-v0.9.0-macos -Plugin successfully installed. Current version: 0.9.0 +$ ./cflocal-v0.11.0-macos +Plugin successfully installed. Current version: 0.11.0 ``` ***Or*** ```bash -$ cf install-plugin cflocal-0.9.0-macos +$ cf install-plugin cflocal-0.11.0-macos **Attention: Plugins are binaries written by potentially untrusted authors. Install and use plugins at your own risk.** -Do you want to install the plugin cflocal-0.9.0-macos?> y +Do you want to install the plugin cflocal-0.11.0-macos?> y -Installing plugin cflocal-0.9.0-macos... +Installing plugin cflocal-0.11.0-macos... OK -Plugin cflocal v0.9.0 successfully installed. +Plugin cflocal v0.11.0 successfully installed. ``` ***Or*** ```bash @@ -148,9 +153,9 @@ $ cf install-plugin -r CF-Community cflocal Do you want to install the plugin cflocal?> y Looking up 'cflocal' from repository 'CF-Community' 11354404 bytes downloaded... -Installing plugin cflocal-0.8.0-macos... +Installing plugin cflocal-0.11.0-macos... OK -Plugin cflocal v0.8.0 successfully installed. +Plugin cflocal v0.11.0 successfully installed. ``` Note: The version available in the 'CF-Community' plugin repo may not always be the latest available. @@ -163,10 +168,6 @@ OK Plugin cflocal successfully uninstalled. ``` -## Known Issues - -* JAR files currently must be unzipped to push - ## Security Notes * Service forwarding tunnels are not active during staging diff --git a/VERSION b/VERSION index 78bc1ab..d9df1bb 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.10.0 +0.11.0 diff --git a/cf/cmd/stage.go b/cf/cmd/stage.go index 5fa4a90..9060011 100644 --- a/cf/cmd/stage.go +++ b/cf/cmd/stage.go @@ -20,7 +20,7 @@ type Stage struct { } type stageOptions struct { - name, buildpack string + name, buildpack, app string serviceApp, forwardApp string } @@ -42,7 +42,7 @@ func (s *Stage) Run(args []string) error { if err != nil { return err } - appTar, err := s.FS.TarApp(".") + appTar, err := s.FS.TarApp(options.app) if err != nil { return err } @@ -91,6 +91,7 @@ func (*Stage) options(args []string) (*stageOptions, error) { return options, parseOptions(args, func(name string, set *flag.FlagSet) { options.name = name + set.StringVar(&options.app, "p", ".", "") set.StringVar(&options.buildpack, "b", "", "") set.StringVar(&options.serviceApp, "s", "", "") set.StringVar(&options.forwardApp, "f", "", "") diff --git a/cf/cmd/stage_test.go b/cf/cmd/stage_test.go index 7a0ba47..d540fda 100644 --- a/cf/cmd/stage_test.go +++ b/cf/cmd/stage_test.go @@ -86,7 +86,7 @@ var _ = Describe("Stage", func() { } mockConfig.EXPECT().Load().Return(localYML, nil) - mockFS.EXPECT().TarApp(".").Return(appTar, nil) + mockFS.EXPECT().TarApp("some-app-dir").Return(appTar, nil) mockApp.EXPECT().Services("some-service-app").Return(services, nil) mockApp.EXPECT().Forward("some-forward-app", services).Return(forwardedServices, forwardConfig, nil) mockFS.EXPECT().OpenFile("./.some-app.cache").Return(cache, int64(100), nil) @@ -109,7 +109,7 @@ var _ = Describe("Stage", func() { mockFS.EXPECT().WriteFile("./some-app.droplet").Return(dropletFile, nil), ) - Expect(cmd.Run([]string{"stage", "some-app", "-b", "some-buildpack", "-s", "some-service-app", "-f", "some-forward-app"})).To(Succeed()) + Expect(cmd.Run([]string{"stage", "some-app", "-b", "some-buildpack", "-p", "some-app-dir", "-s", "some-service-app", "-f", "some-forward-app"})).To(Succeed()) Expect(appTar.Result()).To(BeEmpty()) Expect(droplet.Result()).To(BeEmpty()) Expect(dropletFile.Result()).To(Equal("some-droplet")) @@ -119,6 +119,7 @@ var _ = Describe("Stage", func() { }) // TODO: test not providing a buildpack + // TODO: test not providing an app dir // TODO: test with empty cache }) }) diff --git a/fs/fs.go b/fs/fs.go index a045f1f..4f4a94e 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -2,6 +2,7 @@ package fs import ( "io" + "io/ioutil" "os" "path/filepath" "regexp" @@ -12,23 +13,67 @@ import ( type FS struct{} -func (f *FS) TarApp(path string) (io.ReadCloser, error) { - absPath, err := filepath.Abs(path) +func (f *FS) TarApp(path string) (app io.ReadCloser, err error) { + var absPath, appDir string + + absPath, err = filepath.Abs(path) if err != nil { return nil, err } - files, err := appFiles(absPath) + + zipper := appfiles.ApplicationZipper{} + if zipper.IsZipFile(absPath) { + appDir, err = ioutil.TempDir("", "cflocal-zip") + if err != nil { + return nil, err + } + defer func() { + if err != nil { + os.RemoveAll(appDir) + return + } + app = &closeWrapper{ + ReadCloser: app, + After: func() error { + return os.RemoveAll(appDir) + }, + } + }() + if err := zipper.Unzip(absPath, appDir); err != nil { + return nil, err + } + } else { + appDir, err = filepath.EvalSymlinks(absPath) + if err != nil { + return nil, err + } + } + files, err := appFiles(appDir) if err != nil { return nil, err } - return archive.TarWithOptions(absPath, &archive.TarOptions{ + return archive.TarWithOptions(appDir, &archive.TarOptions{ IncludeFiles: files, }) } +type closeWrapper struct { + io.ReadCloser + After func() error +} + +func (c *closeWrapper) Close() (err error) { + defer func() { + if afterErr := c.After(); err == nil { + err = afterErr + } + }() + return c.ReadCloser.Close() +} + func appFiles(path string) ([]string, error) { var files []string - err := appfiles.ApplicationFiles{}.WalkAppFiles(path, func(relpath string, fullpath string) error { + err := appfiles.ApplicationFiles{}.WalkAppFiles(path, func(relpath, _ string) error { filename := filepath.Base(relpath) switch { case diff --git a/plugin/usage.go b/plugin/usage.go index 726fcb2..f6e032b 100644 --- a/plugin/usage.go +++ b/plugin/usage.go @@ -3,7 +3,8 @@ package plugin const Usage = ShortUsage + "\n" + LongUsage const ShortUsage = ` - cf local stage [ (-b | -b ) (-s | -f ) ] + cf local stage [ (-b | -b ) (-p | -p ) ] + [ (-s | -f ) ] cf local run [ (-i ) (-p ) (-d ) ] [ (-s ) (-f ) ] cf local export [ (-r ) ] @@ -23,6 +24,11 @@ STAGE OPTIONS: Default: (uses detection) -b Use a buildpack specified by URL (git or zip-over-HTTP). Default: (uses detection) + -p Use the specified directory as the app directory. + Default: current working directory + -p Use the specified ZIP file contents as the app directory. + Note that JAR and WAR files use ZIP file format. + Default: current working directory -s Use the service bindings from the specified remote CF app instead of the service bindings in local.yml. Default: (uses local.yml)